carray 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +824 -0
- data/README.md +5 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +258 -4
- data/ext/ca_axis_group.c +368 -184
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +90 -41
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +960 -245
- data/ext/ca_kernel_iterator.h +352 -181
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +194 -42
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +5 -4
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +180 -50
- data/ext/ca_obj_face.c +428 -62
- data/ext/ca_obj_face.h +107 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +39 -26
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +235 -55
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +551 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +88 -75
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -123
- data/ext/ca_obj_stride.c +267 -35
- data/ext/ca_obj_string.c +25 -22
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +157 -29
- data/ext/ca_op_ipower.c +25 -13
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +385 -198
- data/ext/ca_sweep_engine.h +31 -23
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +463 -0
- data/ext/carray.h +90 -28
- data/ext/carray_access.c +487 -231
- data/ext/carray_address_basis.c +731 -0
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +49 -17
- data/ext/carray_call_cfunc.c +667 -511
- data/ext/carray_cast.c +1556 -247
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +84 -38
- data/ext/carray_core.c +591 -119
- data/ext/carray_count.c +148 -96
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +604 -291
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +400 -1
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +88 -66
- data/ext/carray_mask.c +705 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +263 -63
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1093 -363
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +582 -204
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +183 -70
- data/ext/carray_sort.c +235 -240
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +103 -118
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1349 -851
- data/ext/ruby_carray.c +17 -2
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +18 -0
- data/lib/carray/axis_group.rb +245 -20
- data/lib/carray/basics.rb +95 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +200 -54
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +244 -59
- data/lib/carray/categorical_iterator.rb +391 -757
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +177 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +590 -76
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +205 -35
- data/lib/carray/frame/group.rb +199 -40
- data/lib/carray/frame/io.rb +452 -60
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +54 -19
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +726 -56
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +71 -20
- data/lib/carray/iterator.rb +157 -2
- data/lib/carray/lazy.rb +54 -67
- data/lib/carray/mask_gap_fill.rb +66 -50
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +130 -6
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +235 -21
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +91 -0
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +52 -3
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +109 -40
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +9 -4
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +535 -105
- data/lib/carray/window_iterator.rb +259 -82
- data/lib/carray.rb +4 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_block.rb +2 -7
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +11 -4
- data/yard-stubs/carray_access.rb +6 -5
- data/yard-stubs/carray_broadcast.rb +2 -2
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -83
- data/yard-stubs/carray_count.rb +16 -2
- data/yard-stubs/carray_lazy.rb +210 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +1499 -4
- data/yard-stubs/carray_median_percentile.rb +26 -8
- data/yard-stubs/carray_order.rb +43 -82
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +11 -8
- data/yard-stubs/carray_sort.rb +50 -8
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -25837
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14593
- data/ext/carray_kernels_reduce_extreme.c +0 -16948
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32138
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/lib/carray/time.rb
CHANGED
|
@@ -55,6 +55,21 @@ module CATimeCivil
|
|
|
55
55
|
doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
|
|
56
56
|
era * 146097 + doe - 719468
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
# Same algebra for one date, on plain Integers. A literal is parsed one
|
|
60
|
+
# at a time, and routing each one through the vectorized form costs a
|
|
61
|
+
# one-cell CArray and a kernel call per literal -- which is where nearly
|
|
62
|
+
# all the time in a bulk column parse used to go (170k rows: 3.5 s, of
|
|
63
|
+
# which 3.1 s was this). Ruby's Integer `/` floors, as CArray's does,
|
|
64
|
+
# so the two forms read alike and agree everywhere.
|
|
65
|
+
def days_from_civil_1 (y, m, d)
|
|
66
|
+
y -= 1 if m <= 2
|
|
67
|
+
era = y / 400
|
|
68
|
+
yoe = y - era * 400
|
|
69
|
+
doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + (d - 1)
|
|
70
|
+
doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
|
|
71
|
+
era * 146097 + doe - 719468
|
|
72
|
+
end
|
|
58
73
|
end
|
|
59
74
|
|
|
60
75
|
# Unit algebra for the time surface: how two units relate (same group, which
|
|
@@ -78,6 +93,19 @@ module CATimeUnitAlgebra
|
|
|
78
93
|
|
|
79
94
|
module_function
|
|
80
95
|
|
|
96
|
+
# An operand of a time or duration operator, taken through its to_ca when
|
|
97
|
+
# it exports MemoryView and defines one (an Apache Arrow duration array, given
|
|
98
|
+
# a bridge that adds to_ca), as the core binary operators take it; other
|
|
99
|
+
# objects are returned as they are, for the operator to accept or refuse.
|
|
100
|
+
def operand(other)
|
|
101
|
+
if !other.is_a?(CArray) && other.respond_to?(:to_ca) &&
|
|
102
|
+
CArray.memory_view_available?(other)
|
|
103
|
+
other.to_ca
|
|
104
|
+
else
|
|
105
|
+
other
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
81
109
|
# seconds- (fixed) or months- (calendar) per base tick.
|
|
82
110
|
def base_ratio(base)
|
|
83
111
|
FIXED[base] || CALENDAR[base]
|
|
@@ -110,6 +138,8 @@ module CATimeUnitAlgebra
|
|
|
110
138
|
g = rgcd(a.tick_ratio, b.tick_ratio)
|
|
111
139
|
CATime::Resolution.new(Integer(g / base_ratio(fb)), fb)
|
|
112
140
|
end
|
|
141
|
+
# Alias of {#common}, used where the caller reads the result as "the finer
|
|
142
|
+
# of the two grids" rather than "the grid they share".
|
|
113
143
|
alias_method :finer, :common
|
|
114
144
|
module_function :finer
|
|
115
145
|
|
|
@@ -191,7 +221,7 @@ module CATimeUnitAlgebra
|
|
|
191
221
|
# fine->coarse divides only when every value is exact; cross-group ALWAYS
|
|
192
222
|
# raises -- a :M / :Y duration has no fixed ratio to days (a month is
|
|
193
223
|
# calendar-variable), so it genuinely cannot scale to seconds.
|
|
194
|
-
def convert_scale!(storage, from, to)
|
|
224
|
+
def convert_scale!(storage, from, to, instant: false)
|
|
195
225
|
a = res(from); b = res(to)
|
|
196
226
|
return storage if a == b
|
|
197
227
|
r = ratio(a, b)
|
|
@@ -204,6 +234,11 @@ module CATimeUnitAlgebra
|
|
|
204
234
|
scaled = widen(storage, r.numerator) # coarse -> fine: lossless multiply
|
|
205
235
|
return scaled if r.denominator == 1
|
|
206
236
|
unless (scaled % r.denominator).eq(0).all
|
|
237
|
+
if instant
|
|
238
|
+
raise ArgumentError,
|
|
239
|
+
"cannot convert times in #{a} to #{b} without loss: " \
|
|
240
|
+
"some are not on the #{b} grid (they would be moved to it)"
|
|
241
|
+
end
|
|
207
242
|
raise ArgumentError,
|
|
208
243
|
"cannot scale duration #{a} to #{b} without loss: " \
|
|
209
244
|
"some values are not a whole multiple of #{b} " \
|
|
@@ -254,7 +289,7 @@ module CATimeUnitAlgebra
|
|
|
254
289
|
def convert_instant!(storage, from, to)
|
|
255
290
|
a = res(from); b = res(to)
|
|
256
291
|
return storage if a == b
|
|
257
|
-
return convert_scale!(storage, a, b) if ratio(a, b) # same group
|
|
292
|
+
return convert_scale!(storage, a, b, instant: true) if ratio(a, b) # same group
|
|
258
293
|
if CALENDAR.key?(a.base)
|
|
259
294
|
convert_instant_calendar_to_fixed(storage, a, b) # widen
|
|
260
295
|
else
|
|
@@ -368,7 +403,48 @@ module CATimeUnitAlgebra
|
|
|
368
403
|
end
|
|
369
404
|
end
|
|
370
405
|
|
|
406
|
+
# A reduction of a time Face takes `fill_value:` as a value of the Face (an
|
|
407
|
+
# Element, or what an assignment to the Face accepts), the same as
|
|
408
|
+
# `strip_mask` and `[]=` read it. The reduction runs on the storage, so the
|
|
409
|
+
# fill goes in after the result is a Face again: a whole reduction keeps its
|
|
410
|
+
# axes for that, so the fill lands in a typed cell, and is then taken out.
|
|
411
|
+
module CATimeSurfaceFill
|
|
412
|
+
|
|
413
|
+
private
|
|
414
|
+
|
|
415
|
+
def with_surface_fill (opts)
|
|
416
|
+
fill = opts[:fill_value]
|
|
417
|
+
return yield(opts) if fill.nil? || fill.equal?(UNDEF)
|
|
418
|
+
o = opts.reject { |k, _| k == :fill_value }
|
|
419
|
+
o[:keep_axis] = true
|
|
420
|
+
r = yield(o)
|
|
421
|
+
kept = nil
|
|
422
|
+
unless opts[:keep_axis]
|
|
423
|
+
axes = opts[:axis].nil? ? (0...ndim).to_a :
|
|
424
|
+
Array(opts[:axis]).map { |a| a < 0 ? a + ndim : a }
|
|
425
|
+
kept = shape.each_with_index.reject { |_, i| axes.include?(i) }.map(&:first)
|
|
426
|
+
end
|
|
427
|
+
surface_fill(r, fill, kept)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# `kept` is the shape to return to, or nil to keep the reduced axes.
|
|
431
|
+
def surface_fill (r, fill, kept)
|
|
432
|
+
case r
|
|
433
|
+
when Array then r.map { |x| surface_fill(x, fill, kept) }
|
|
434
|
+
when CArray
|
|
435
|
+
r = r.strip_mask(fill) if r.has_mask?
|
|
436
|
+
if kept.nil? then r
|
|
437
|
+
elsif kept.empty? then r[*Array.new(r.ndim, 0)]
|
|
438
|
+
else r.reshape(*kept)
|
|
439
|
+
end
|
|
440
|
+
else r
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
end
|
|
445
|
+
|
|
371
446
|
class CATime
|
|
447
|
+
include CATimeSurfaceFill
|
|
372
448
|
# @overload new(*shape, unit: :ns)
|
|
373
449
|
# Allocates a new int64 storage CArray of the given `shape` and
|
|
374
450
|
# wraps it as a CATime Face with the given `unit`. The
|
|
@@ -455,7 +531,7 @@ class CATime
|
|
|
455
531
|
# @return [String]
|
|
456
532
|
def to_s
|
|
457
533
|
case unit.base
|
|
458
|
-
when :Y then
|
|
534
|
+
when :Y then to_date.strftime("%Y")
|
|
459
535
|
when :M then to_date.strftime("%Y-%m")
|
|
460
536
|
when :W, :D then to_date.strftime("%Y-%m-%d")
|
|
461
537
|
else to_time.iso8601(fractional_second_digits) # :h .. :as (time shown)
|
|
@@ -585,6 +661,7 @@ class CATime
|
|
|
585
661
|
end
|
|
586
662
|
|
|
587
663
|
class CATimedelta
|
|
664
|
+
include CATimeSurfaceFill
|
|
588
665
|
# @overload new(*shape, unit: :ns)
|
|
589
666
|
# Allocates a new int64 storage CArray of the given `shape` and
|
|
590
667
|
# wraps it as a CATimedelta Face with the given `unit`.
|
|
@@ -799,16 +876,19 @@ class CATime
|
|
|
799
876
|
# cross-group calendar duration raises). Adding two datetimes is
|
|
800
877
|
# ill-defined.
|
|
801
878
|
# @param other [CATimedelta]
|
|
879
|
+
# (or a MemoryView producer whose +to_ca+ gives one, such as an Arrow
|
|
880
|
+
# duration array)
|
|
802
881
|
# @return [CATime]
|
|
803
882
|
# @raise [TypeError] on a non-timedelta operand.
|
|
804
883
|
def +(other)
|
|
884
|
+
given, other = other, CATimeUnitAlgebra.operand(other)
|
|
805
885
|
case other
|
|
806
886
|
when CATimedelta
|
|
807
887
|
(parent + CATimeUnitAlgebra.convert_scale_trunc(other.parent, other.unit, unit)).time(unit: unit)
|
|
808
888
|
when CATime
|
|
809
889
|
raise TypeError, "CATime + CATime is ill-defined"
|
|
810
890
|
else
|
|
811
|
-
raise TypeError, "CATime + #{
|
|
891
|
+
raise TypeError, "CATime + #{given.class} is not allowed (use CATimedelta)"
|
|
812
892
|
end
|
|
813
893
|
end
|
|
814
894
|
|
|
@@ -818,9 +898,12 @@ class CATime
|
|
|
818
898
|
# another {CATime} yields a {CATimedelta} at the finer of the two
|
|
819
899
|
# units (cross-group falls to the fixed unit).
|
|
820
900
|
# @param other [CATimedelta, CATime]
|
|
901
|
+
# (or a MemoryView producer whose +to_ca+ gives one, such as an Arrow
|
|
902
|
+
# duration or timestamp array)
|
|
821
903
|
# @return [CATime, CATimedelta]
|
|
822
904
|
# @raise [TypeError] on an unsupported operand.
|
|
823
905
|
def -(other)
|
|
906
|
+
given, other = other, CATimeUnitAlgebra.operand(other)
|
|
824
907
|
case other
|
|
825
908
|
when CATimedelta
|
|
826
909
|
(parent - CATimeUnitAlgebra.convert_scale_trunc(other.parent, other.unit, unit)).time(unit: unit)
|
|
@@ -830,7 +913,7 @@ class CATime
|
|
|
830
913
|
b = CATimeUnitAlgebra.convert_instant!(other.parent, other.unit, u)
|
|
831
914
|
(a - b).timedelta(unit: u)
|
|
832
915
|
else
|
|
833
|
-
raise TypeError, "CATime - #{
|
|
916
|
+
raise TypeError, "CATime - #{given.class} is not allowed"
|
|
834
917
|
end
|
|
835
918
|
end
|
|
836
919
|
|
|
@@ -841,38 +924,19 @@ class CATime
|
|
|
841
924
|
# base CArray#<=> composed from the (gated) > and <.
|
|
842
925
|
|
|
843
926
|
# Element decode convention via the C-layer macro
|
|
844
|
-
# `
|
|
927
|
+
# `CA_FACE_STORAGE_TO_ELEMENT_IF_FACE`: no Ruby `[]` override is needed, a
|
|
845
928
|
# scalar auto-decodes on all scalar-return paths such as `ca[i,j,k]` /
|
|
846
|
-
# `ca[k]`.
|
|
929
|
+
# `ca[k]`. storage_to_element itself lives in C (ext/ca_obj_datetime.c)
|
|
847
930
|
# so the hot path stays off Ruby method dispatch. Its write counterpart
|
|
848
|
-
# is
|
|
931
|
+
# is element_to_storage (below, surface -> storage on store).
|
|
849
932
|
|
|
850
933
|
# @!group Reductions
|
|
851
934
|
|
|
852
|
-
# min / max are order-structure reductions: they ride the core
|
|
853
|
-
# Face gate (ORDERABLE storage descent + output re-lift, see
|
|
854
|
-
# ext/mkkernel.rb face_gate: :relift), so
|
|
855
|
-
#
|
|
856
|
-
#
|
|
857
|
-
# re-lift both extremes here. They are actual elements, so they keep the
|
|
858
|
-
# storage unit (no rounding, unlike mean).
|
|
859
|
-
|
|
860
|
-
# @overload minmax(*axes, **opts)
|
|
861
|
-
# Returns the earliest and latest time as a `[min, max]` pair (§8).
|
|
862
|
-
# @return [Array(Element, Element), Array(CATime, CATime)]
|
|
863
|
-
def minmax(*args, **opts)
|
|
864
|
-
lo, hi = parent.minmax(*args, **opts)
|
|
865
|
-
[relift_extremum(lo), relift_extremum(hi)]
|
|
866
|
-
end
|
|
867
|
-
|
|
868
|
-
def relift_extremum(r)
|
|
869
|
-
case r
|
|
870
|
-
when Integer then Element.new(r, unit)
|
|
871
|
-
when CArray then r.time(unit: unit)
|
|
872
|
-
else r
|
|
873
|
-
end
|
|
874
|
-
end
|
|
875
|
-
private :relift_extremum
|
|
935
|
+
# min / max / minmax are order-structure reductions: they ride the core
|
|
936
|
+
# reduce Face gate (ORDERABLE storage descent + output re-lift, see
|
|
937
|
+
# ext/mkkernel.rb face_gate: :relift), so the inherited methods return the
|
|
938
|
+
# Element / CATime shapes directly. The extremes are actual elements, so
|
|
939
|
+
# they keep the storage unit (no rounding, unlike mean).
|
|
876
940
|
|
|
877
941
|
# The centroid / spread / order reductions report on the array's own grid:
|
|
878
942
|
# they run on the storage ticks and round the result back to the nearest
|
|
@@ -936,22 +1000,43 @@ class CATime
|
|
|
936
1000
|
|
|
937
1001
|
# @overload sum(*)
|
|
938
1002
|
# Not supported; use {#mean} for a centroid.
|
|
939
|
-
# @raise [
|
|
1003
|
+
# @raise [CArray::DataTypeError] always.
|
|
940
1004
|
def sum(*)
|
|
941
|
-
raise
|
|
1005
|
+
raise CArray::DataTypeError, "CATime#sum is ill-defined; use mean for centroid"
|
|
942
1006
|
end
|
|
943
1007
|
|
|
1008
|
+
# @overload accumulate(*)
|
|
1009
|
+
# Not supported, for the same reason as {#sum}.
|
|
1010
|
+
# @raise [CArray::DataTypeError] always.
|
|
1011
|
+
def accumulate(*); raise CArray::DataTypeError, "CATime#accumulate is ill-defined (a sum of instants)"; end
|
|
1012
|
+
|
|
1013
|
+
# @overload cumsum(*)
|
|
1014
|
+
# Not supported, for the same reason as {#sum}. The running position
|
|
1015
|
+
# relative to the first instant is `(t - t[0])` cumulated as durations.
|
|
1016
|
+
# @raise [CArray::DataTypeError] always.
|
|
1017
|
+
def cumsum(*); raise CArray::DataTypeError, "CATime#cumsum is ill-defined (a sum of instants)"; end
|
|
1018
|
+
|
|
1019
|
+
# @overload prod(*)
|
|
1020
|
+
# Not supported: a product of instants has no meaning.
|
|
1021
|
+
# @raise [CArray::DataTypeError] always.
|
|
1022
|
+
def prod(*); raise CArray::DataTypeError, "CATime#prod is ill-defined (a product of instants)"; end
|
|
1023
|
+
|
|
1024
|
+
# @overload cumprod(*)
|
|
1025
|
+
# Not supported, for the same reason as {#prod}.
|
|
1026
|
+
# @raise [CArray::DataTypeError] always.
|
|
1027
|
+
def cumprod(*); raise CArray::DataTypeError, "CATime#cumprod is ill-defined (a product of instants)"; end
|
|
1028
|
+
|
|
944
1029
|
# @overload variance(*)
|
|
945
1030
|
# Not supported: the variance of instants has squared-time units, which
|
|
946
1031
|
# no type represents (ill-defined, like {#sum}). Use {#stddev} for the
|
|
947
1032
|
# spread as a duration.
|
|
948
|
-
# @raise [
|
|
949
|
-
def variance(*); raise
|
|
1033
|
+
# @raise [CArray::DataTypeError] always.
|
|
1034
|
+
def variance(*); raise CArray::DataTypeError, "CATime#variance is ill-defined (squared-time units); use stddev"; end
|
|
950
1035
|
|
|
951
1036
|
# @overload variancep(*)
|
|
952
1037
|
# Not supported, for the same reason as {#variance}. Use {#stddevp}.
|
|
953
|
-
# @raise [
|
|
954
|
-
def variancep(*); raise
|
|
1038
|
+
# @raise [CArray::DataTypeError] always.
|
|
1039
|
+
def variancep(*); raise CArray::DataTypeError, "CATime#variancep is ill-defined (squared-time units); use stddevp"; end
|
|
955
1040
|
# @!endgroup
|
|
956
1041
|
|
|
957
1042
|
# sort / partition ride the core sort Face gate (ORDERABLE storage
|
|
@@ -969,11 +1054,15 @@ class CATime
|
|
|
969
1054
|
# different unit or shape), so the gate routes the operand through here.
|
|
970
1055
|
#
|
|
971
1056
|
# Accepted operands: another {CATime} (unit-rescaled to self),
|
|
972
|
-
# a {Element} (lifted to a length-1 CATime),
|
|
973
|
-
# Ruby `
|
|
974
|
-
#
|
|
975
|
-
#
|
|
976
|
-
#
|
|
1057
|
+
# a {Element} (lifted to a length-1 CATime), and an instant written
|
|
1058
|
+
# outside CArray -- a Ruby `Time`, `Date`, `DateTime`, or a `String` in
|
|
1059
|
+
# the grammar {CArray.time} reads (UTC unless it carries an offset).
|
|
1060
|
+
# Those are read at the precision they carry, not at self's unit, and
|
|
1061
|
+
# then go through the same conversion as a CATime: "2024-01-02 09:00"
|
|
1062
|
+
# against a day-unit array raises rather than matching 2024-01-02. A
|
|
1063
|
+
# bare Integer / other type raises; descend to `ca.parent` to compare
|
|
1064
|
+
# the hidden storage directly. An Array of any of these is a 1-D set
|
|
1065
|
+
# of instants (the value set of {#is_in}).
|
|
977
1066
|
#
|
|
978
1067
|
# The rescale is an INSTANT conversion (convert_instant!), lossless: a
|
|
979
1068
|
# coarser->finer unit always converts; a finer->coarser unit converts only
|
|
@@ -983,10 +1072,24 @@ class CATime
|
|
|
983
1072
|
# :D and finer, and a fixed operand coarsens to :M/:Y only when it sits on
|
|
984
1073
|
# the calendar boundary (:W is the one exception -- month/year starts are
|
|
985
1074
|
# not week-aligned, so :Y/:M <-> :W raises).
|
|
986
|
-
# @param operand [CATime, CATime::Element, Time, DateTime]
|
|
1075
|
+
# @param operand [CATime, CATime::Element, Time, Date, DateTime, String, Array]
|
|
987
1076
|
# @return [CATime] in self's unit.
|
|
988
1077
|
# @raise [TypeError, ArgumentError] on an unreconcilable operand / unit.
|
|
1078
|
+
# Returns `[self, other]` in one unit that holds every value of both
|
|
1079
|
+
# exactly: the finer grid of two same-group units, or the fixed-length one
|
|
1080
|
+
# of a calendar / fixed pair (a month begins on a day). A lookup between
|
|
1081
|
+
# two arrays of different units (is_in, the set operations, locate_addr and
|
|
1082
|
+
# the joins on them) meets there, so it does not depend on which array is
|
|
1083
|
+
# the receiver.
|
|
1084
|
+
# @api private
|
|
1085
|
+
def in_common_unit_with (other)
|
|
1086
|
+
return [self, other] if other.unit == unit
|
|
1087
|
+
u = CATimeUnitAlgebra.diff_unit(unit, other.unit)
|
|
1088
|
+
[to_unit(u), other.to_unit(u)]
|
|
1089
|
+
end
|
|
1090
|
+
|
|
989
1091
|
def to_comparable (operand)
|
|
1092
|
+
require 'date' # Date is one of the operand kinds below
|
|
990
1093
|
case operand
|
|
991
1094
|
when CATime
|
|
992
1095
|
return operand if operand.unit == unit
|
|
@@ -995,12 +1098,19 @@ class CATime
|
|
|
995
1098
|
when CATime::Element
|
|
996
1099
|
lifted = CATime.wrap(CA_INT64([operand.value]), unit: operand.unit)
|
|
997
1100
|
to_comparable(lifted)
|
|
998
|
-
when Time
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1101
|
+
when Time, String, Date
|
|
1102
|
+
to_comparable(CATimeLiteral.exact_instant(operand))
|
|
1103
|
+
when Array
|
|
1104
|
+
# A set of instants written one by one (is_in(["2024-01-02", ...])).
|
|
1105
|
+
CATime.wrap(CA_INT64(operand.map { |v| to_comparable(v).parent[0] }),
|
|
1106
|
+
unit: unit)
|
|
1107
|
+
when CArray
|
|
1108
|
+
# An array that is not a time array: each value read as an instant (an
|
|
1109
|
+
# object column of Time values, a string column), a masked cell kept
|
|
1110
|
+
# masked.
|
|
1111
|
+
require "carray/methods/is_in"
|
|
1112
|
+
send(:values_in_face, self, operand) ||
|
|
1113
|
+
CArray.int64(*operand.shape).fill(UNDEF).time(unit: unit)
|
|
1004
1114
|
else
|
|
1005
1115
|
raise TypeError,
|
|
1006
1116
|
"CATime cannot reconcile #{operand.class} " \
|
|
@@ -1008,23 +1118,22 @@ class CATime
|
|
|
1008
1118
|
end
|
|
1009
1119
|
end
|
|
1010
1120
|
|
|
1011
|
-
# @overload
|
|
1012
|
-
# Write-direction counterpart of
|
|
1121
|
+
# @overload element_to_storage(surface)
|
|
1122
|
+
# Write-direction counterpart of storage_to_element (the store hook fired
|
|
1013
1123
|
# from rb_ca_obj2ptr): brings a surface value object into this Face's
|
|
1014
1124
|
# int64 storage (count in self's unit since the Unix epoch) so a scalar
|
|
1015
|
-
# store round-trips with a fetch. A {Element} / `Time` / `
|
|
1016
|
-
# reconciled to self's unit via {#to_comparable}
|
|
1017
|
-
# discipline: a cross-group unit or a non-exact
|
|
1018
|
-
# raises). A bare Integer (the documented `.parent`
|
|
1019
|
-
#
|
|
1020
|
-
#
|
|
1021
|
-
# @param surface [Element, Time, DateTime, Integer, String]
|
|
1125
|
+
# store round-trips with a fetch. A {Element} / `Time` / `Date` /
|
|
1126
|
+
# `DateTime` / `String` is reconciled to self's unit via {#to_comparable}
|
|
1127
|
+
# (same lossless discipline: a cross-group unit or a non-exact
|
|
1128
|
+
# finer->coarser cast raises). A bare Integer (the documented `.parent`
|
|
1129
|
+
# raw-storage escape) passes through unchanged to the storage cast.
|
|
1130
|
+
# @param surface [Element, Time, Date, DateTime, Integer, String]
|
|
1022
1131
|
# @return [Integer, Object] the storage-domain value, or `surface`
|
|
1023
1132
|
# unchanged for a pass-through type.
|
|
1024
1133
|
# @raise [TypeError, ArgumentError] on an unreconcilable surface / unit.
|
|
1025
|
-
def
|
|
1134
|
+
def element_to_storage (surface)
|
|
1026
1135
|
case surface
|
|
1027
|
-
when Integer
|
|
1136
|
+
when Integer
|
|
1028
1137
|
surface
|
|
1029
1138
|
else
|
|
1030
1139
|
to_comparable(surface).parent[0]
|
|
@@ -1231,7 +1340,9 @@ class CATime
|
|
|
1231
1340
|
# the centroid of month numbers (a caller wanting the day-space answer
|
|
1232
1341
|
# writes to_unit(:D) first) -- and the result is rounded back to a tick.
|
|
1233
1342
|
def reduce_on_own_grid(op, kind, args, opts)
|
|
1234
|
-
|
|
1343
|
+
with_surface_fill(opts) do |o|
|
|
1344
|
+
round_and_relift(parent.public_send(op, *args, **o), kind)
|
|
1345
|
+
end
|
|
1235
1346
|
end
|
|
1236
1347
|
|
|
1237
1348
|
# Round-to-nearest onto the storage grid, then wear the Face again. An
|
|
@@ -1256,6 +1367,41 @@ class CATime
|
|
|
1256
1367
|
end
|
|
1257
1368
|
end
|
|
1258
1369
|
|
|
1370
|
+
# inspect's name for the array: the class and the tick, which the storage
|
|
1371
|
+
# type name (fixlen[8]) does not tell.
|
|
1372
|
+
private def inspect_label
|
|
1373
|
+
"#{self.class}[#{unit}]"
|
|
1374
|
+
end
|
|
1375
|
+
|
|
1376
|
+
# inspect's cell: the instant as Element#to_s writes it, but with the
|
|
1377
|
+
# digits of a second that some cell of the array needs, the same in every
|
|
1378
|
+
# cell so that the cells line up; the unit is in the label.
|
|
1379
|
+
private def inspect_cell_formatter
|
|
1380
|
+
digits = inspect_second_digits
|
|
1381
|
+
lambda do |e|
|
|
1382
|
+
case e.unit.base
|
|
1383
|
+
when :Y, :M, :W, :D then e.to_s
|
|
1384
|
+
else e.to_time.iso8601(digits)
|
|
1385
|
+
end
|
|
1386
|
+
end
|
|
1387
|
+
end
|
|
1388
|
+
|
|
1389
|
+
# The digits of a second inspect writes: 0, 3, 6 or 9, the fewest that
|
|
1390
|
+
# show every present cell exactly (9 at most, as Time holds).
|
|
1391
|
+
private def inspect_second_digits
|
|
1392
|
+
base_digits = { ms: 3, us: 6, ns: 9, ps: 12, fs: 15, as: 18 }[unit.base]
|
|
1393
|
+
return 0 unless base_digits
|
|
1394
|
+
count = unit.count
|
|
1395
|
+
[0, 3, 6, 9].each do |d|
|
|
1396
|
+
break if d >= base_digits
|
|
1397
|
+
m = 10**(base_digits - d)
|
|
1398
|
+
# ticks * count is a multiple of m when ticks is one of m / gcd(m, count),
|
|
1399
|
+
# which does not multiply out of int64.
|
|
1400
|
+
return d if (ticks % (m / m.gcd(count))).eq(0).all
|
|
1401
|
+
end
|
|
1402
|
+
[base_digits, 9].min
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1259
1405
|
end
|
|
1260
1406
|
|
|
1261
1407
|
# ============================================================================
|
|
@@ -1302,9 +1448,12 @@ class CATimedelta
|
|
|
1302
1448
|
# {CATimedelta}; a {CATime} operand delegates to
|
|
1303
1449
|
# {CATime#+} (commutative).
|
|
1304
1450
|
# @param other [CATimedelta, CATime]
|
|
1451
|
+
# (or a MemoryView producer whose +to_ca+ gives one, such as an Arrow
|
|
1452
|
+
# duration or timestamp array)
|
|
1305
1453
|
# @return [CATimedelta, CATime]
|
|
1306
1454
|
# @raise [TypeError] on incompatible operands.
|
|
1307
1455
|
def +(other)
|
|
1456
|
+
given, other = other, CATimeUnitAlgebra.operand(other)
|
|
1308
1457
|
case other
|
|
1309
1458
|
when CATimedelta
|
|
1310
1459
|
u = common_duration_unit(other.unit)
|
|
@@ -1314,7 +1463,7 @@ class CATimedelta
|
|
|
1314
1463
|
when CATime
|
|
1315
1464
|
other + self # commutative -> time's unit
|
|
1316
1465
|
else
|
|
1317
|
-
raise TypeError, "CATimedelta + #{
|
|
1466
|
+
raise TypeError, "CATimedelta + #{given.class} is not allowed"
|
|
1318
1467
|
end
|
|
1319
1468
|
end
|
|
1320
1469
|
|
|
@@ -1322,9 +1471,12 @@ class CATimedelta
|
|
|
1322
1471
|
# Returns the difference of two {CATimedelta} at the finer of the two
|
|
1323
1472
|
# units (a cross-group pair raises).
|
|
1324
1473
|
# @param other [CATimedelta]
|
|
1474
|
+
# (or a MemoryView producer whose +to_ca+ gives one, such as an Arrow
|
|
1475
|
+
# duration array)
|
|
1325
1476
|
# @return [CATimedelta]
|
|
1326
1477
|
# @raise [TypeError] on a non-timedelta operand.
|
|
1327
1478
|
def -(other)
|
|
1479
|
+
given, other = other, CATimeUnitAlgebra.operand(other)
|
|
1328
1480
|
case other
|
|
1329
1481
|
when CATimedelta
|
|
1330
1482
|
u = common_duration_unit(other.unit)
|
|
@@ -1332,7 +1484,7 @@ class CATimedelta
|
|
|
1332
1484
|
b = CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u)
|
|
1333
1485
|
(a - b).timedelta(unit: u)
|
|
1334
1486
|
else
|
|
1335
|
-
raise TypeError, "CATimedelta - #{
|
|
1487
|
+
raise TypeError, "CATimedelta - #{given.class} is not allowed"
|
|
1336
1488
|
end
|
|
1337
1489
|
end
|
|
1338
1490
|
|
|
@@ -1368,9 +1520,12 @@ class CATimedelta
|
|
|
1368
1520
|
# {CATimedelta}, by another {CATimedelta} with matching `unit`
|
|
1369
1521
|
# yields a dimensionless CArray.
|
|
1370
1522
|
# @param other [Integer, CATimedelta]
|
|
1523
|
+
# (or a MemoryView producer whose +to_ca+ gives one, such as an Arrow
|
|
1524
|
+
# duration array)
|
|
1371
1525
|
# @return [CATimedelta, CArray]
|
|
1372
1526
|
# @raise [TypeError] on incompatible operands.
|
|
1373
1527
|
def /(other)
|
|
1528
|
+
given, other = other, CATimeUnitAlgebra.operand(other)
|
|
1374
1529
|
case other
|
|
1375
1530
|
when Integer
|
|
1376
1531
|
CATimeUnitAlgebra.trunc_divide(parent, other).timedelta(unit: unit)
|
|
@@ -1379,7 +1534,7 @@ class CATimedelta
|
|
|
1379
1534
|
CATimeUnitAlgebra.trunc_divide(
|
|
1380
1535
|
CATimeUnitAlgebra.convert_scale!(parent, unit, u),
|
|
1381
1536
|
CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u))
|
|
1382
|
-
else raise TypeError, "CATimedelta / #{
|
|
1537
|
+
else raise TypeError, "CATimedelta / #{given.class} is not allowed"
|
|
1383
1538
|
end
|
|
1384
1539
|
end
|
|
1385
1540
|
|
|
@@ -1395,14 +1550,41 @@ class CATimedelta
|
|
|
1395
1550
|
# or as a {CATimedelta} view for per-axis reduction.
|
|
1396
1551
|
# @return [Element, CATimedelta]
|
|
1397
1552
|
def sum(*args, **opts)
|
|
1398
|
-
round_and_relift(parent.sum(*args, **
|
|
1553
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.sum(*args, **o)) }
|
|
1554
|
+
end
|
|
1555
|
+
|
|
1556
|
+
# @overload accumulate(*args, **opts)
|
|
1557
|
+
# Returns the sum of durations in the storage's own integer type, as
|
|
1558
|
+
# {#sum} does in float64.
|
|
1559
|
+
# @return [Element, CATimedelta]
|
|
1560
|
+
def accumulate(*args, **opts)
|
|
1561
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.accumulate(*args, **o)) }
|
|
1399
1562
|
end
|
|
1400
1563
|
|
|
1564
|
+
# @overload cumsum(axis: nil)
|
|
1565
|
+
# Returns the running sum of durations as a {CATimedelta} on `self`'s
|
|
1566
|
+
# unit. A masked cell holds the running sum, as {CArray#cumsum} does.
|
|
1567
|
+
# @return [CATimedelta]
|
|
1568
|
+
def cumsum(*args, **opts)
|
|
1569
|
+
round_and_relift(parent.cumsum(*args, **opts))
|
|
1570
|
+
end
|
|
1571
|
+
|
|
1572
|
+
# @overload prod(*)
|
|
1573
|
+
# Not supported: a product of durations has the units of a power of
|
|
1574
|
+
# time, which no type represents.
|
|
1575
|
+
# @raise [CArray::DataTypeError] always.
|
|
1576
|
+
def prod(*); raise CArray::DataTypeError, "CATimedelta#prod is ill-defined (powers of time units)"; end
|
|
1577
|
+
|
|
1578
|
+
# @overload cumprod(*)
|
|
1579
|
+
# Not supported, for the same reason as {#prod}.
|
|
1580
|
+
# @raise [CArray::DataTypeError] always.
|
|
1581
|
+
def cumprod(*); raise CArray::DataTypeError, "CATimedelta#cumprod is ill-defined (powers of time units)"; end
|
|
1582
|
+
|
|
1401
1583
|
# @overload mean(axis: nil, **opts)
|
|
1402
1584
|
# Returns the mean duration rounded to the nearest unit count.
|
|
1403
1585
|
# @return [Element, CATimedelta]
|
|
1404
1586
|
def mean(*args, **opts)
|
|
1405
|
-
round_and_relift(parent.mean(*args, **
|
|
1587
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.mean(*args, **o)) }
|
|
1406
1588
|
end
|
|
1407
1589
|
|
|
1408
1590
|
# median / percentile / quantile / stddev / stddevp report on this array's
|
|
@@ -1414,7 +1596,7 @@ class CATimedelta
|
|
|
1414
1596
|
# Returns the median duration on `self`'s unit.
|
|
1415
1597
|
# @return [Element, CATimedelta]
|
|
1416
1598
|
def median(*args, **opts)
|
|
1417
|
-
round_and_relift(parent.median(*args, **
|
|
1599
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.median(*args, **o)) }
|
|
1418
1600
|
end
|
|
1419
1601
|
|
|
1420
1602
|
# @overload percentile(*p, axis: nil, **opts)
|
|
@@ -1423,7 +1605,7 @@ class CATimedelta
|
|
|
1423
1605
|
# two or more give an Array).
|
|
1424
1606
|
# @return [Element, CATimedelta, Array<Element>, Array<CATimedelta>]
|
|
1425
1607
|
def percentile(*args, **opts)
|
|
1426
|
-
round_and_relift(parent.percentile(*args, **
|
|
1608
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.percentile(*args, **o)) }
|
|
1427
1609
|
end
|
|
1428
1610
|
|
|
1429
1611
|
# @overload quantile(axis: nil, **opts)
|
|
@@ -1431,7 +1613,7 @@ class CATimedelta
|
|
|
1431
1613
|
# `self`'s unit.
|
|
1432
1614
|
# @return [Array<Element>, Array<CATimedelta>]
|
|
1433
1615
|
def quantile(*args, **opts)
|
|
1434
|
-
round_and_relift(parent.quantile(*args, **
|
|
1616
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.quantile(*args, **o)) }
|
|
1435
1617
|
end
|
|
1436
1618
|
|
|
1437
1619
|
# @overload stddev(axis: nil, **opts)
|
|
@@ -1440,7 +1622,7 @@ class CATimedelta
|
|
|
1440
1622
|
# the precision matters.
|
|
1441
1623
|
# @return [Element, CATimedelta]
|
|
1442
1624
|
def stddev(*args, **opts)
|
|
1443
|
-
round_and_relift(parent.stddev(*args, **
|
|
1625
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.stddev(*args, **o)) }
|
|
1444
1626
|
end
|
|
1445
1627
|
|
|
1446
1628
|
# @overload stddevp(axis: nil, **opts)
|
|
@@ -1448,7 +1630,7 @@ class CATimedelta
|
|
|
1448
1630
|
# {#stddev}.
|
|
1449
1631
|
# @return [Element, CATimedelta]
|
|
1450
1632
|
def stddevp(*args, **opts)
|
|
1451
|
-
round_and_relift(parent.stddevp(*args, **
|
|
1633
|
+
with_surface_fill(opts) { |o| round_and_relift(parent.stddevp(*args, **o)) }
|
|
1452
1634
|
end
|
|
1453
1635
|
|
|
1454
1636
|
def round_and_relift(r)
|
|
@@ -1465,39 +1647,21 @@ class CATimedelta
|
|
|
1465
1647
|
# Not supported: the variance of durations has squared-time units, which
|
|
1466
1648
|
# no type represents -- the same reason {CATime#variance} refuses. Use
|
|
1467
1649
|
# {#stddev} for the spread as a duration.
|
|
1468
|
-
# @raise [
|
|
1650
|
+
# @raise [CArray::DataTypeError] always.
|
|
1469
1651
|
def variance(*)
|
|
1470
|
-
raise
|
|
1652
|
+
raise CArray::DataTypeError, "CATimedelta#variance is ill-defined (squared-time units); use stddev"
|
|
1471
1653
|
end
|
|
1472
1654
|
|
|
1473
1655
|
# @overload variancep(*)
|
|
1474
1656
|
# Not supported, for the same reason as {#variance}. Use {#stddevp}.
|
|
1475
|
-
# @raise [
|
|
1657
|
+
# @raise [CArray::DataTypeError] always.
|
|
1476
1658
|
def variancep(*)
|
|
1477
|
-
raise
|
|
1478
|
-
end
|
|
1479
|
-
|
|
1480
|
-
# min / max ride the core reduce Face gate (ORDERABLE storage descent +
|
|
1481
|
-
# output re-lift, see ext/mkkernel.rb face_gate: :relift); the inherited
|
|
1482
|
-
# CArray#min / #max return the Element / CATimedelta shapes directly.
|
|
1483
|
-
# minmax has no core gate, so re-lift both extremes here.
|
|
1484
|
-
|
|
1485
|
-
# @overload minmax(*axes, **opts)
|
|
1486
|
-
# Returns the shortest and longest duration as a `[min, max]` pair.
|
|
1487
|
-
# @return [Array(Element, Element), Array(CATimedelta, CATimedelta)]
|
|
1488
|
-
def minmax(*args, **opts)
|
|
1489
|
-
lo, hi = parent.minmax(*args, **opts)
|
|
1490
|
-
[relift_extremum(lo), relift_extremum(hi)]
|
|
1659
|
+
raise CArray::DataTypeError, "CATimedelta#variancep is ill-defined (squared-time units); use stddevp"
|
|
1491
1660
|
end
|
|
1492
1661
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
when CArray then r.timedelta(unit: unit)
|
|
1497
|
-
else r
|
|
1498
|
-
end
|
|
1499
|
-
end
|
|
1500
|
-
private :relift_extremum
|
|
1662
|
+
# min / max / minmax ride the core reduce Face gate (ORDERABLE storage
|
|
1663
|
+
# descent + output re-lift); the inherited methods return the Element /
|
|
1664
|
+
# CATimedelta shapes directly.
|
|
1501
1665
|
# @!endgroup
|
|
1502
1666
|
|
|
1503
1667
|
# sort / partition ride the core sort Face gate (ORDERABLE storage
|
|
@@ -1518,6 +1682,23 @@ class CATimedelta
|
|
|
1518
1682
|
# @param operand [CATimedelta, CATimedelta::Element]
|
|
1519
1683
|
# @return [CATimedelta] in self's unit.
|
|
1520
1684
|
# @raise [TypeError, ArgumentError] on an unreconcilable operand / unit.
|
|
1685
|
+
# Returns `[self, other]` in one unit, the finer grid of the two, which
|
|
1686
|
+
# holds every value of both exactly. A lookup between two duration
|
|
1687
|
+
# arrays of different units meets there, so it does not depend on which
|
|
1688
|
+
# array is the receiver. A calendar and a fixed-length duration have no
|
|
1689
|
+
# common unit and raise.
|
|
1690
|
+
# @api private
|
|
1691
|
+
def in_common_unit_with (other)
|
|
1692
|
+
return [self, other] if other.unit == unit
|
|
1693
|
+
unless CATimeUnitAlgebra.same_group?(unit, other.unit)
|
|
1694
|
+
raise ArgumentError,
|
|
1695
|
+
"cannot compare a #{unit} duration with a #{other.unit} duration: " \
|
|
1696
|
+
"a calendar length (months, years) has no fixed length in days"
|
|
1697
|
+
end
|
|
1698
|
+
u = CATimeUnitAlgebra.finer(unit, other.unit)
|
|
1699
|
+
[to_unit(u), other.to_unit(u)]
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1521
1702
|
def to_comparable (operand)
|
|
1522
1703
|
case operand
|
|
1523
1704
|
when CATimedelta
|
|
@@ -1534,8 +1715,8 @@ class CATimedelta
|
|
|
1534
1715
|
end
|
|
1535
1716
|
end
|
|
1536
1717
|
|
|
1537
|
-
# @overload
|
|
1538
|
-
# Write-direction counterpart of
|
|
1718
|
+
# @overload element_to_storage(surface)
|
|
1719
|
+
# Write-direction counterpart of storage_to_element: brings a surface
|
|
1539
1720
|
# value object into this Face's int64 storage (count in self's unit) so a
|
|
1540
1721
|
# scalar store round-trips with a fetch. A {Element} is reconciled to
|
|
1541
1722
|
# self's unit via {#to_comparable} (lossless discipline; a `Time` /
|
|
@@ -1546,7 +1727,7 @@ class CATimedelta
|
|
|
1546
1727
|
# @return [Integer, Object] the storage-domain value, or `surface`
|
|
1547
1728
|
# unchanged for a pass-through type.
|
|
1548
1729
|
# @raise [TypeError, ArgumentError] on an unreconcilable surface / unit.
|
|
1549
|
-
def
|
|
1730
|
+
def element_to_storage (surface)
|
|
1550
1731
|
case surface
|
|
1551
1732
|
when Integer, String
|
|
1552
1733
|
surface
|
|
@@ -1592,6 +1773,17 @@ class CATimedelta
|
|
|
1592
1773
|
end
|
|
1593
1774
|
CATimeUnitAlgebra.finer(unit, ou)
|
|
1594
1775
|
end
|
|
1776
|
+
|
|
1777
|
+
# inspect's name for the array: the class and the tick, which the storage
|
|
1778
|
+
# type name (fixlen[8]) does not tell.
|
|
1779
|
+
private def inspect_label
|
|
1780
|
+
"#{self.class}[#{unit}]"
|
|
1781
|
+
end
|
|
1782
|
+
|
|
1783
|
+
# inspect's cell: the duration in the base unit of the tick.
|
|
1784
|
+
private def inspect_cell_formatter
|
|
1785
|
+
lambda { |e| "#{e.value * e.unit.count}#{e.unit.base}" }
|
|
1786
|
+
end
|
|
1595
1787
|
end
|
|
1596
1788
|
|
|
1597
1789
|
# Reads a time literal (Time / DateTime / Integer unix-seconds / String) into
|
|
@@ -1604,8 +1796,12 @@ module CATimeLiteral
|
|
|
1604
1796
|
|
|
1605
1797
|
# Date fields of a String literal, UTC. Ruby's Date._parse does not read
|
|
1606
1798
|
# the calendar-grid forms CATime#to_s prints -- "2019-09" comes back as a
|
|
1607
|
-
# month of 20 with a zone, and a bare "2019" as a month and a day --
|
|
1608
|
-
#
|
|
1799
|
+
# month of 20 with a zone, and a bare "2019" as a month and a day -- nor an
|
|
1800
|
+
# hour alone after the date ("2024-01-01T05", ISO 8601 reduced to the
|
|
1801
|
+
# hour), so those are read here and everything else is left to Date._parse.
|
|
1802
|
+
# Date._parse passes over what it cannot read, so text with a time after
|
|
1803
|
+
# the date that comes back without an hour is refused rather than read as
|
|
1804
|
+
# midnight.
|
|
1609
1805
|
#
|
|
1610
1806
|
# What Date._parse returns is then checked, because an out-of-range field
|
|
1611
1807
|
# used to flow into the civil kernel and normalise into a different date:
|
|
@@ -1617,23 +1813,71 @@ module CATimeLiteral
|
|
|
1617
1813
|
if format then Date._strptime(spec, format)
|
|
1618
1814
|
elsif spec =~ /\A(\d{4})-(\d{1,2})\z/ then { year: $1.to_i, mon: $2.to_i }
|
|
1619
1815
|
elsif spec =~ /\A(\d{4})\z/ then { year: $1.to_i }
|
|
1816
|
+
elsif (hour = hour_only_fields(spec)) then hour
|
|
1620
1817
|
else Date._parse(spec)
|
|
1621
1818
|
end
|
|
1622
1819
|
unless h && h[:year]
|
|
1623
1820
|
raise ArgumentError, "cannot parse time #{spec.inspect}"
|
|
1624
1821
|
end
|
|
1822
|
+
if !format && !h[:hour] &&
|
|
1823
|
+
(spec =~ /\d\s*T\s*\d/ || spec =~ /\A\s*[+-]?\d{4,}[-\/]\d{1,2}[-\/]\d{1,2}\s+\d/)
|
|
1824
|
+
raise ArgumentError, "cannot parse time #{spec.inspect}: the time after the date is not read"
|
|
1825
|
+
end
|
|
1826
|
+
# Text left over after the format is not part of a time in that format.
|
|
1827
|
+
if h[:leftover] && !h[:leftover].strip.empty?
|
|
1828
|
+
raise ArgumentError,
|
|
1829
|
+
"cannot parse time #{spec.inspect}: #{h[:leftover].inspect} is " \
|
|
1830
|
+
"left over after #{format.inspect}"
|
|
1831
|
+
end
|
|
1625
1832
|
y, m, d = h[:year], h[:mon] || 1, h[:mday] || 1
|
|
1626
1833
|
unless (1..12).cover?(m) && Date.valid_date?(y, m, d, Date::GREGORIAN)
|
|
1627
1834
|
raise ArgumentError,
|
|
1628
1835
|
"cannot parse time #{spec.inspect}: it reads as year #{y}, " \
|
|
1629
1836
|
"month #{m}, day #{d}, which is not a date"
|
|
1630
1837
|
end
|
|
1838
|
+
# Hour 24 is the end of the day, 24:00:00, and nothing past it.
|
|
1839
|
+
if h[:hour] == 24 && [h[:min], h[:sec], h[:sec_fraction]].any? { |v| v && v != 0 }
|
|
1840
|
+
raise ArgumentError,
|
|
1841
|
+
"cannot parse time #{spec.inspect}: hour 24 is only 24:00:00"
|
|
1842
|
+
end
|
|
1631
1843
|
h
|
|
1632
1844
|
end
|
|
1633
1845
|
private_class_method :parse_date_fields
|
|
1634
1846
|
|
|
1847
|
+
# "YYYY-MM-DDThh" with an optional zone ("Z", "+hh", "+hhmm", "+hh:mm").
|
|
1848
|
+
def hour_only_fields(spec)
|
|
1849
|
+
m = /\A(\d{4})-(\d{2})-(\d{2})T(\d{2})(?:(Z)|([+-])(\d{2})(?::?(\d{2}))?)?\z/.match(spec)
|
|
1850
|
+
return nil unless m
|
|
1851
|
+
h = { year: m[1].to_i, mon: m[2].to_i, mday: m[3].to_i, hour: m[4].to_i }
|
|
1852
|
+
raise ArgumentError, "cannot parse time #{spec.inspect}: hour #{h[:hour]}" if h[:hour] > 23
|
|
1853
|
+
if m[6]
|
|
1854
|
+
oh, om = m[7].to_i, m[8].to_i
|
|
1855
|
+
raise ArgumentError, "cannot parse time #{spec.inspect}: offset" if oh > 23 || om > 59
|
|
1856
|
+
h[:offset] = (oh * 3600 + om * 60) * (m[6] == "-" ? -1 : 1)
|
|
1857
|
+
elsif m[5]
|
|
1858
|
+
h[:offset] = 0
|
|
1859
|
+
end
|
|
1860
|
+
h
|
|
1861
|
+
end
|
|
1862
|
+
private_class_method :hour_only_fields
|
|
1863
|
+
|
|
1864
|
+
# An instant written outside CArray (Time / Date / DateTime / String) as a
|
|
1865
|
+
# length-1 CATime in the coarsest of :s / :ms / :us / :ns that holds it
|
|
1866
|
+
# exactly, so a comparison sees the instant it names and not that instant
|
|
1867
|
+
# floored onto some other grid. Finer than a nanosecond raises.
|
|
1868
|
+
def exact_instant(spec)
|
|
1869
|
+
sec = epoch_seconds(spec)
|
|
1870
|
+
[:s, :ms, :us, :ns].each_with_index do |u, k|
|
|
1871
|
+
ticks = sec * (10 ** (3 * k))
|
|
1872
|
+
if ticks.denominator == 1
|
|
1873
|
+
return CATime.wrap(CA_INT64([ticks.to_i]), unit: u)
|
|
1874
|
+
end
|
|
1875
|
+
end
|
|
1876
|
+
raise ArgumentError, "#{spec.inspect} is finer than a nanosecond"
|
|
1877
|
+
end
|
|
1878
|
+
|
|
1635
1879
|
# Exact Rational seconds since the Unix epoch for a start literal (Time /
|
|
1636
|
-
# DateTime / Integer unix-seconds / String). UTC default.
|
|
1880
|
+
# Date / DateTime / Integer unix-seconds / String). UTC default.
|
|
1637
1881
|
def epoch_seconds(spec, format = nil)
|
|
1638
1882
|
require 'date'
|
|
1639
1883
|
require 'time'
|
|
@@ -1654,8 +1898,7 @@ module CATimeLiteral
|
|
|
1654
1898
|
end
|
|
1655
1899
|
when String
|
|
1656
1900
|
h = parse_date_fields(spec, format)
|
|
1657
|
-
days = CATimeCivil.
|
|
1658
|
-
CA_INT64([h[:mday] || 1]))[0]
|
|
1901
|
+
days = CATimeCivil.days_from_civil_1(h[:year], h[:mon] || 1, h[:mday] || 1)
|
|
1659
1902
|
sec = Rational(days * 86400 + (h[:hour] || 0) * 3600 +
|
|
1660
1903
|
(h[:min] || 0) * 60 + (h[:sec] || 0))
|
|
1661
1904
|
sec += h[:sec_fraction] if h[:sec_fraction]
|
|
@@ -1664,6 +1907,8 @@ module CATimeLiteral
|
|
|
1664
1907
|
else
|
|
1665
1908
|
if defined?(DateTime) && spec.is_a?(DateTime)
|
|
1666
1909
|
spec.to_time.to_r
|
|
1910
|
+
elsif defined?(Date) && spec.is_a?(Date)
|
|
1911
|
+
Rational(CATimeCivil.days_from_civil_1(spec.year, spec.month, spec.day) * 86400)
|
|
1667
1912
|
else
|
|
1668
1913
|
raise ArgumentError, "cannot parse time #{spec.class}"
|
|
1669
1914
|
end
|
|
@@ -1711,6 +1956,189 @@ module CATimeLiteral
|
|
|
1711
1956
|
end
|
|
1712
1957
|
end
|
|
1713
1958
|
|
|
1959
|
+
# A format :infer can choose: a strptime format, read strictly (see
|
|
1960
|
+
# __strptime_fields__), and what the text carries (:date, :clock, or
|
|
1961
|
+
# :fraction of a second).
|
|
1962
|
+
TimeFormat = Struct.new(:format, :kind)
|
|
1963
|
+
|
|
1964
|
+
INFER_DATES = %w[%d/%m/%Y %m/%d/%Y %d-%m-%Y %m-%d-%Y %d.%m.%Y %d/%m/%y %m/%d/%y] +
|
|
1965
|
+
["%d %b %Y", "%d-%b-%Y", "%d-%b-%y", "%b %d, %Y", "%b %d %Y", "%Y%m%d"]
|
|
1966
|
+
|
|
1967
|
+
# A space in a format matches any run of spaces, none included.
|
|
1968
|
+
INFER_CLOCKS = [
|
|
1969
|
+
["%H:%M", :clock], ["%H:%M:%S", :clock], ["%H:%M:%S.%N", :fraction],
|
|
1970
|
+
["%I:%M %p", :clock], ["%I:%M:%S %p", :clock],
|
|
1971
|
+
].freeze
|
|
1972
|
+
|
|
1973
|
+
# A time after a date: none, or a clock with or without a zone. %z takes
|
|
1974
|
+
# an offset, Z, UTC or GMT, not a zone named for a place (some of those
|
|
1975
|
+
# names mean different offsets in different places).
|
|
1976
|
+
INFER_TIMES = ([["", :date]] +
|
|
1977
|
+
INFER_CLOCKS.flat_map { |clock, kind| [[" #{clock}", kind], [" #{clock} %z", kind]] }
|
|
1978
|
+
).freeze
|
|
1979
|
+
|
|
1980
|
+
# A date written in Japanese, year first ("2024<year>1<month>2<day>"),
|
|
1981
|
+
# takes a time in the same markers ("3<hour>4<minute>", "...5<second>")
|
|
1982
|
+
# or with colons.
|
|
1983
|
+
JA_YEAR, JA_MONTH, JA_DAY = "年", "月", "日"
|
|
1984
|
+
JA_HOUR, JA_MINUTE, JA_SECOND = "時", "分", "秒"
|
|
1985
|
+
INFER_JA_TIMES = ([["", :date]] +
|
|
1986
|
+
([["%H#{JA_HOUR}%M#{JA_MINUTE}", :clock],
|
|
1987
|
+
["%H#{JA_HOUR}%M#{JA_MINUTE}%S#{JA_SECOND}", :clock]] + INFER_CLOCKS)
|
|
1988
|
+
.map { |clock, kind| [" #{clock}", kind] }).freeze
|
|
1989
|
+
|
|
1990
|
+
INFER_FORMATS = (
|
|
1991
|
+
INFER_DATES.product(INFER_TIMES).map { |date, (time, kind)|
|
|
1992
|
+
TimeFormat.new(date + time, kind)
|
|
1993
|
+
} +
|
|
1994
|
+
INFER_JA_TIMES.map { |time, kind|
|
|
1995
|
+
TimeFormat.new("%Y#{JA_YEAR}%m#{JA_MONTH}%d#{JA_DAY}#{time}", kind)
|
|
1996
|
+
}
|
|
1997
|
+
).freeze
|
|
1998
|
+
INFER_FORMAT_STRINGS = INFER_FORMATS.map(&:format).freeze
|
|
1999
|
+
|
|
2000
|
+
# The one format of INFER_FORMATS that the text of object array `x` is
|
|
2001
|
+
# written in, `first` being its first present cell, as
|
|
2002
|
+
# [format, fields, address]: the fields __strptime_fields__ read in it,
|
|
2003
|
+
# and the address of the first cell not in it, or nil.
|
|
2004
|
+
#
|
|
2005
|
+
# The first cell gives the candidates and each later cell drops those it
|
|
2006
|
+
# does not fit, until one is left. Each candidate is read over the whole
|
|
2007
|
+
# array once, and the candidate a later cell leaves last is the one left:
|
|
2008
|
+
# no candidate for the first cell or none left (the last ones dropped by
|
|
2009
|
+
# one cell) raises CAFrame::UnreadableColumn, and more than one left at the
|
|
2010
|
+
# end raises CAFrame::AmbiguousTimeFormat (CAFrame is its only caller). A cell
|
|
2011
|
+
# after the choice that is not in the format is returned for the caller
|
|
2012
|
+
# to report.
|
|
2013
|
+
def infer_time_format(x, first)
|
|
2014
|
+
candidates = CArray.__strptime_fits__(first, INFER_FORMAT_STRINGS)
|
|
2015
|
+
if candidates.empty?
|
|
2016
|
+
raise CAFrame::UnreadableColumn,
|
|
2017
|
+
"cannot infer a time format from #{first.inspect}; " \
|
|
2018
|
+
"pass a strptime format, or :mixed to guess at each cell"
|
|
2019
|
+
end
|
|
2020
|
+
runs = candidates.map do |i|
|
|
2021
|
+
fields = x.__strptime_fields__(INFER_FORMATS[i].format, true)
|
|
2022
|
+
[INFER_FORMATS[i], fields, fields[7].first || Float::INFINITY]
|
|
2023
|
+
end
|
|
2024
|
+
last = runs.map(&:last).max
|
|
2025
|
+
left = runs.select { |run| run[2] == last }
|
|
2026
|
+
if left.size > 1
|
|
2027
|
+
if last == Float::INFINITY
|
|
2028
|
+
formats = left.map { |run| run[0].format }
|
|
2029
|
+
raise CAFrame::AmbiguousTimeFormat.new(
|
|
2030
|
+
"#{first.inspect} could be read as " \
|
|
2031
|
+
"#{formats.map(&:inspect).join(' or ')}, " \
|
|
2032
|
+
"and no cell tells which; pass the format",
|
|
2033
|
+
formats: formats)
|
|
2034
|
+
end
|
|
2035
|
+
raise CAFrame::UnreadableColumn,
|
|
2036
|
+
"#{x.flatten[last].inspect} is not in the format of #{first.inspect}; " \
|
|
2037
|
+
"pass a strptime format, or :mixed to guess at each cell"
|
|
2038
|
+
end
|
|
2039
|
+
f, fields, bad = left.first
|
|
2040
|
+
[f, fields, bad == Float::INFINITY ? nil : bad]
|
|
2041
|
+
end
|
|
2042
|
+
|
|
2043
|
+
# [kind, per] for __time_ticks_from_fields__: ticks of `per` nanoseconds
|
|
2044
|
+
# (:fixed) or months (:calendar), or nil when `res` is finer than a
|
|
2045
|
+
# nanosecond.
|
|
2046
|
+
def tick_kind(res)
|
|
2047
|
+
if CATimeUnitAlgebra::FIXED.key?(res.base)
|
|
2048
|
+
per = res.tick_ratio * 1_000_000_000
|
|
2049
|
+
kind = :fixed
|
|
2050
|
+
else
|
|
2051
|
+
per = res.tick_ratio
|
|
2052
|
+
kind = :calendar
|
|
2053
|
+
end
|
|
2054
|
+
(per.denominator == 1 && per >= 1) ? [kind, per.to_i] : nil
|
|
2055
|
+
end
|
|
2056
|
+
|
|
2057
|
+
# The ticks of every cell of `x` on the `res` grid, as an int64 array of
|
|
2058
|
+
# x's shape (missing and, under on_error: :mask, unparseable cells
|
|
2059
|
+
# masked), or nil when `res` is finer than a nanosecond. A strptime
|
|
2060
|
+
# format that __strptime_fields__ reads is read for the whole array in C;
|
|
2061
|
+
# otherwise each String is parsed into fields here. Either way the fields
|
|
2062
|
+
# become ticks for the whole array at once, and a cell that is not a
|
|
2063
|
+
# String goes through tick_index.
|
|
2064
|
+
def ticks_from_cells(x, res, format, on_error)
|
|
2065
|
+
require 'date'
|
|
2066
|
+
kind, per = tick_kind(res)
|
|
2067
|
+
return nil unless kind
|
|
2068
|
+
if format.is_a?(String)
|
|
2069
|
+
text = (x.data_type == CA_OBJECT && !x.face?) ? x : x.to_type(:object)
|
|
2070
|
+
fields = text.__strptime_fields__(format, false)
|
|
2071
|
+
return ticks_from_fields(fields, x, res, format, on_error) if fields
|
|
2072
|
+
end
|
|
2073
|
+
cells = x.flatten.to_a
|
|
2074
|
+
n = cells.size
|
|
2075
|
+
year = Array.new(n, 1970)
|
|
2076
|
+
mon = Array.new(n, 1)
|
|
2077
|
+
day = Array.new(n, 1)
|
|
2078
|
+
sod = Array.new(n, 0)
|
|
2079
|
+
frac = Array.new(n, 0)
|
|
2080
|
+
off = Array.new(n, 0)
|
|
2081
|
+
present = Array.new(n, 0)
|
|
2082
|
+
direct = {}
|
|
2083
|
+
cells.each_with_index do |s, i|
|
|
2084
|
+
next if s.nil? || UNDEF.equal?(s)
|
|
2085
|
+
begin
|
|
2086
|
+
if s.is_a?(String)
|
|
2087
|
+
h = parse_date_fields(s, format)
|
|
2088
|
+
year[i] = h[:year]
|
|
2089
|
+
mon[i] = h[:mon] || 1
|
|
2090
|
+
day[i] = h[:mday] || 1
|
|
2091
|
+
sod[i] = (h[:hour] || 0) * 3600 + (h[:min] || 0) * 60 + (h[:sec] || 0)
|
|
2092
|
+
frac[i] = (h[:sec_fraction] * 1_000_000_000).floor if h[:sec_fraction]
|
|
2093
|
+
off[i] = h[:offset] if h[:offset]
|
|
2094
|
+
present[i] = 1
|
|
2095
|
+
else
|
|
2096
|
+
direct[i] = tick_index(s, res, format)
|
|
2097
|
+
end
|
|
2098
|
+
rescue ArgumentError, TypeError
|
|
2099
|
+
raise if on_error == :raise
|
|
2100
|
+
end
|
|
2101
|
+
end
|
|
2102
|
+
raw = CArray.__time_ticks_from_fields__(
|
|
2103
|
+
CA_INT64(year), CA_INT64(mon), CA_INT64(day), CA_INT64(sod),
|
|
2104
|
+
CA_INT64(frac), CA_INT64(off), CA_UINT8(present), kind, per)
|
|
2105
|
+
check_ticks_fit(raw, present, present.count(1), res) { cells }
|
|
2106
|
+
direct.each { |i, t| raw[i] = t }
|
|
2107
|
+
raw.reshape(*x.shape)
|
|
2108
|
+
end
|
|
2109
|
+
|
|
2110
|
+
# Ticks from what __strptime_fields__ read; the cells it deferred are
|
|
2111
|
+
# read one by one through tick_index.
|
|
2112
|
+
def ticks_from_fields(fields, x, res, format, on_error)
|
|
2113
|
+
kind, per = tick_kind(res)
|
|
2114
|
+
year, mon, day, sod, frac, off, present, unreadable, deferred, = fields
|
|
2115
|
+
cells = nil
|
|
2116
|
+
if on_error == :raise && !unreadable.empty?
|
|
2117
|
+
cells = x.flatten.to_a
|
|
2118
|
+
raise ArgumentError, "cannot parse time #{cells[unreadable.first].inspect}"
|
|
2119
|
+
end
|
|
2120
|
+
raw = CArray.__time_ticks_from_fields__(year, mon, day, sod, frac, off,
|
|
2121
|
+
present, kind, per)
|
|
2122
|
+
check_ticks_fit(raw, present, present.sum, res) { cells ||= x.flatten.to_a }
|
|
2123
|
+
unless deferred.empty?
|
|
2124
|
+
cells ||= x.flatten.to_a
|
|
2125
|
+
deferred.each do |i|
|
|
2126
|
+
raw[i] = tick_index(cells[i], res, format)
|
|
2127
|
+
rescue ArgumentError, TypeError
|
|
2128
|
+
raise if on_error == :raise
|
|
2129
|
+
end
|
|
2130
|
+
end
|
|
2131
|
+
raw.reshape(*x.shape)
|
|
2132
|
+
end
|
|
2133
|
+
|
|
2134
|
+
# A tick that does not fit int64 is out of range, not unparseable.
|
|
2135
|
+
def check_ticks_fit(raw, present, present_count, res)
|
|
2136
|
+
return if raw.count_not_masked >= present_count
|
|
2137
|
+
cells = yield
|
|
2138
|
+
i = (0...raw.elements).find { |k| present[k] == 1 && UNDEF.equal?(raw[k]) }
|
|
2139
|
+
raise RangeError, "time #{cells[i].inspect} does not fit int64 ticks of #{res}"
|
|
2140
|
+
end
|
|
2141
|
+
|
|
1714
2142
|
# Single-literal build for {.time}: a 1-element CATime, honouring
|
|
1715
2143
|
# the on_error policy (raise, or a masked cell).
|
|
1716
2144
|
def to_time_array(literal, res, format, on_error)
|
|
@@ -1820,6 +2248,8 @@ class CArray
|
|
|
1820
2248
|
unless x.is_a?(CArray)
|
|
1821
2249
|
return CATimeLiteral.to_time_array(x, res, format, on_error)
|
|
1822
2250
|
end
|
|
2251
|
+
raw = CATimeLiteral.ticks_from_cells(x, res, format, on_error)
|
|
2252
|
+
return raw.time(unit: res) if raw
|
|
1823
2253
|
raw = CArray.int64(*x.shape)
|
|
1824
2254
|
x.each_index do |*idx|
|
|
1825
2255
|
s = x[*idx]
|