Class: CATime

Inherits:
Object
  • Object
show all
Defined in:
lib/carray/time.rb,
lib/carray/time.rb,
lib/carray/time.rb

Overview

============================================================================

Ruby surface operators (CATime)

Defined Under Namespace

Classes: Element, Grid, Resolution

Constant Summary collapse

SNAP_DIRECTIONS =

Rounding rules #snap accepts, the same three CArray#snap takes.

%i[round floor ceil].freeze

Reductions collapse

Field accessors collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.from_timesteps(k, unit:, origin: nil) ⇒ Element, CATime

Inverse of #timesteps: returns the bucket-head time for timestep k. Use to relabel a group_by(timesteps) result, generate a regular grid, or as a timesteps round-trip oracle. A scalar k returns a Element; a CArray k returns a CATime.

The result is stored on the unit grid, except a week bucket, which is stored on :D. A week grid counts from the epoch (a Thursday) and so cannot hold its own bucket head: the head is the ISO Monday, four days off every week tick. Days hold it exactly, so a :W bucket answers on the day grid and the round trip against a day-or-finer series is exact. unit: names the bucket here, the way it does for #floor / #ceil -- not the storage the answer lands on.

Parameters:

  • k (Integer, CArray) —

    timestep / timesteps.

  • unit (String, Symbol, Resolution) —

    bucket resolution.

  • origin (Time, String, CATime::Element, DateTime, nil) (defaults to: nil) —

    head of bucket 0 (default: the Unix epoch, or ISO Monday for a week bucket). It has to be a bucket head itself: on a calendar grid, the 1st at 00:00.

Returns:



2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
# File 'lib/carray/time.rb', line 2534

def self.from_timesteps(k, grid = nil, unit: nil, origin: nil)
  grid = unit if unit.is_a?(Grid)
  # A Grid answers on its own storage, so an origin off the unit grid
  # ("12 hours since ... 09:00") survives; the keyword form keeps the
  # narrower rule it always had, where the origin sits on the unit itself.
  return grid.at(k) if grid.is_a?(Grid)
  unit, origin = Grid.resolve(grid, unit, origin)
  res = Resolution.parse(unit)
  out = res.base == :W ? Resolution.new(1, :D) : res
  kk  = k.is_a?(CArray) ? k.int64 : CArray.int64(1) { Integer(k) }
  _mul, step_ticks, o = CATimeGrid.resolve_integer_grid(res, out, origin)
  raw = o + kk * step_ticks
  k.is_a?(CArray) ? raw.time(unit: out) : Element.new(raw[0], out)
end

.new(*shape, unit: :ns) ⇒ CATime

Allocates a new int64 storage CArray of the given shape and wraps it as a CATime Face with the given unit. The reference epoch is the Unix epoch (1970-01-01 UTC).

Parameters:

  • shape (Array<Integer>) —

    shape of the new CATime.

  • unit (Symbol) (defaults to: :ns) —

    resolution unit (:Y, :M, :W, :D, :h, :m, :s, :ms, :us, :ns, ...).

Returns:



397
398
399
400
# File 'lib/carray/time.rb', line 397

def self.new(*shape, unit: :ns)
  raw = CArray.int64(*shape)
  wrap(raw, unit: unit)
end

.wrap(raw, unit: :ns) ⇒ CATime

Zero-copy Face wrap of an existing int64 CArray. unit is a Resolution (or a Symbol / String it parses from).

Parameters:

  • raw (CArray) —

    int64 storage.

  • unit (Resolution, Symbol, String) (defaults to: :ns) —

    tick resolution.

Returns:



408
409
410
# File 'lib/carray/time.rb', line 408

def self.wrap(raw, unit: :ns)
  __wrap__(raw, unit)
end

Instance Method Details

#+(other) ⇒ CATime

Returns self + other for a CATimedelta: the time is the anchor, so the result keeps self's unit and the duration is converted into it (a duration finer than self's unit is truncated to it; a cross-group calendar duration raises). Adding two datetimes is ill-defined.

Parameters:

Returns:

Raises:

  • (TypeError) —

    on a non-timedelta operand.



821
822
823
824
825
826
827
828
829
830
# File 'lib/carray/time.rb', line 821

def +(other)
  case other
  when CATimedelta
    (parent + CATimeUnitAlgebra.convert_scale_trunc(other.parent, other.unit, unit)).time(unit: unit)
  when CATime
    raise TypeError, "CATime + CATime is ill-defined"
  else
    raise TypeError, "CATime + #{other.class} is not allowed (use CATimedelta)"
  end
end

#-(other) ⇒ CATime, CATimedelta

Subtracting a CATimedelta yields a CATime at self's unit (the duration is converted into it, truncated when finer); subtracting another CATime yields a CATimedelta at the finer of the two units (cross-group falls to the fixed unit).

Parameters:

Returns:

Raises:

  • (TypeError) —

    on an unsupported operand.



840
841
842
843
844
845
846
847
848
849
850
851
852
# File 'lib/carray/time.rb', line 840

def -(other)
  case other
  when CATimedelta
    (parent - CATimeUnitAlgebra.convert_scale_trunc(other.parent, other.unit, unit)).time(unit: unit)
  when CATime
    u = CATimeUnitAlgebra.diff_unit(unit, other.unit)
    a = CATimeUnitAlgebra.convert_instant!(parent, unit, u)
    b = CATimeUnitAlgebra.convert_instant!(other.parent, other.unit, u)
    (a - b).timedelta(unit: u)
  else
    raise TypeError, "CATime - #{other.class} is not allowed"
  end
end

#ajd ⇒ CArray

Returns the Astronomical Julian Day (float, offset by half a day) for each element.

Returns:



1220
1221
1222
1223
# File 'lib/carray/time.rb', line 1220

def ajd
  require 'date'
  to_time.convert(:double) {|t| t.to_datetime.ajd.to_f}
end

#ceil(grid = nil, unit:, origin: nil) ⇒ CATime

#snap with direction: :ceil -- each element at the bucket head at or after it (an element already on a boundary maps to itself).

Returns:



2477
2478
2479
# File 'lib/carray/time.rb', line 2477

def ceil(grid = nil, unit: nil, origin: nil)
  snap(grid, unit: unit, origin: origin, direction: :ceil)
end

#day ⇒ CArray

Day of the month, 1..31. Every cell is 1 for :Y / :M storage, which does not resolve days.

Returns:



1173
1174
1175
1176
1177
1178
# File 'lib/carray/time.rb', line 1173

def day
  case unit.base
  when :Y, :M then parent * 0 + 1
  else CATimeCivil.civil_from_days(days_since_epoch)[2]
  end
end

#floor(grid = nil, unit:, origin: nil) ⇒ CATime

#snap with direction: :floor -- each element at its bucket head (toward the past), as a CATime in the same storage resolution.

Returns:



2469
2470
2471
# File 'lib/carray/time.rb', line 2469

def floor(grid = nil, unit: nil, origin: nil)
  snap(grid, unit: unit, origin: origin, direction: :floor)
end

#grid ⇒ Grid

The grid this array is stored on: its own tick, anchored at the epoch. t.timesteps is t.timesteps(t.grid), which is what the unit: default has always meant.

Returns:



2375
# File 'lib/carray/time.rb', line 2375

def grid = Grid.new(unit)

#hour ⇒ CArray

Hour of the day, 0..23; 0 when the storage unit is coarser than an hour.

Returns:



1182
# File 'lib/carray/time.rb', line 1182

def hour;   clock_field(:h); end

#is_leap ⇒ CArray

Returns a boolean CArray flagging elements that fall in a leap year (UTC).

Returns:



1229
1230
1231
1232
# File 'lib/carray/time.rb', line 1229

def is_leap
  y = year
  ((y % 4).eq(0) & (y % 100).ne(0)) | (y % 400).eq(0)
end

#is_righttime(unit:, origin: nil) ⇒ CArray

Returns a boolean CArray flagging elements that land exactly on a bucket head. Use as an assertion before matching to catch off-grid series (a timesteps match is "same bucket", not "same instant").

Parameters:

  • unit (String, Symbol, Resolution) —

    bucket resolution.

  • origin (Time, String, CATime::Element, DateTime, nil) (defaults to: nil) —

    head of bucket 0 (default: the Unix epoch, or ISO Monday for a week bucket on day-or-finer storage -- a week grid counts from the epoch Thursday and cannot hold a Monday, so it keeps its own ticks). It has to be a bucket head itself: on a calendar grid, the 1st at 00:00.

Returns:



2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
# File 'lib/carray/time.rb', line 2503

def is_righttime(grid = nil, unit: nil, origin: nil)
  unit, origin = Grid.resolve(grid, unit, origin)
  g = resolve_timestep_grid(unit, origin)
  return calendar_bucket(:on, g[1], origin) if g[0] == :civil
  su, mul, step_ticks, o = g
  raise_if_int64_overflow(su, mul, o)
  d = storage_ticks_in_numerator_grid(mul) - o
  q = d / step_ticks
  (d - q * step_ticks).eq(0)
end

#jd ⇒ CArray

Returns the Julian Day Number for each element.

Returns:



1211
1212
1213
1214
# File 'lib/carray/time.rb', line 1211

def jd
  require 'date'
  to_time.convert(:int) {|t| t.to_date.jd}
end

#linear_fetch(addr, axis: nil) ⇒ Element, CATime

Returns the time at the fractional position addr on this array's grid, interpolating between the two bracketing instants. The inverse of linear_section, and the reason for the override: linear_fetch returns a value, so the result is a CATime again, whereas linear_section returns a position and stays a plain index.

The result keeps self's unit -- the array's grid is the output grid, so an instant that lands between two ticks is rounded to the nearest one. Widen the grid first when the interpolation needs finer resolution: t.to_unit(:ms).linear_fetch(addr) interpolates on the millisecond grid. (Unlike #mean / #median, which collapse the axis and therefore have no output grid to preserve, so they refine the resolution instead.)

An out-of-range addr yields UNDEF rather than the NaN that a plain float axis returns -- int64 storage has no NaN to carry a sentinel. A masked addr cell stays UNDEF (the kernel's own rule: an undetermined query gets an undetermined answer).

Parameters:

  • addr (Float, CArray) —

    fractional position(s) into self.

  • axis (Integer, nil) (defaults to: nil)

Returns:



1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
# File 'lib/carray/time.rb', line 1082

def linear_fetch (addr, **opts)
  r = parent.float64.linear_fetch(addr, **opts)
  case r
  when CArray
    # mask_invalid before the int64 cast: the cast turns a NaN into 0, which
    # would read as the epoch instead of "no answer".
    r.mask_invalid.round.int64.time(unit: unit)
  when Numeric
    # Scalar query.  Out of range -> nil, joining the nil the kernel already
    # returns for a masked scalar query: one "no answer" on this path.
    r.to_f.nan? ? nil : Element.new(r.round, unit)
  else
    r                                   # nil (masked scalar query)
  end
end

#mean(axis: nil, **opts) ⇒ Element, CATime

Returns the centroid time on self's unit (§8), rounded to the nearest tick.

Returns:



905
906
907
# File 'lib/carray/time.rb', line 905

def mean(*args, **opts)
  reduce_on_own_grid(:mean, :time, args, opts)
end

#median(axis: nil, **opts) ⇒ Element, CATime

Returns the median time on self's unit (§8). An odd-count full reduction is an actual element (exact); the even-count / per-axis cases interpolate and round to the nearest tick.

Returns:



914
915
916
# File 'lib/carray/time.rb', line 914

def median(*args, **opts)
  reduce_on_own_grid(:median, :time, args, opts)
end

#minmax(*axes, **opts) ⇒ Array(Element, Element), Array(CATime, CATime)

Returns the earliest and latest time as a [min, max] pair (§8).



880
881
882
883
# File 'lib/carray/time.rb', line 880

def minmax(*args, **opts)
  lo, hi = parent.minmax(*args, **opts)
  [relift_extremum(lo), relift_extremum(hi)]
end

#minute ⇒ CArray

Minute of the hour, 0..59; 0 when the storage unit is coarser than a minute.

Returns:



1185
# File 'lib/carray/time.rb', line 1185

def minute; clock_field(:m); end

#month ⇒ CArray

Calendar month, 1..12. Every cell is 1 for :Y storage, which does not resolve months.

Returns:



1160
1161
1162
1163
1164
1165
1166
1167
1168
# File 'lib/carray/time.rb', line 1160

def month
  case unit.base
  when :Y then parent * 0 + 1
  when :M
    mo = parent * unit.count + 1970 * 12
    mo % 12 + 1
  else CATimeCivil.civil_from_days(days_since_epoch)[1]
  end
end

#percentile(*p, axis: nil, **opts) ⇒ Element, ...

Returns the percentile instants on self's unit, in the shapes the plain CArray#percentile uses: one p reduces to a single value (an Element, or a CATime with axis:), two or more p give an Array of those.



924
925
926
# File 'lib/carray/time.rb', line 924

def percentile(*args, **opts)
  reduce_on_own_grid(:percentile, :time, args, opts)
end

#quantile(axis: nil, **opts) ⇒ Array<Element>, Array<CATime>

Returns the five quartile instants [p0, p25, p50, p75, p100] on self's unit (shorthand for percentile(0, 25, 50, 75, 100)).

Returns:



932
933
934
# File 'lib/carray/time.rb', line 932

def quantile(*args, **opts)
  reduce_on_own_grid(:quantile, :time, args, opts)
end

#round(grid = nil, unit:, origin: nil) ⇒ CATime

#snap with direction: :round -- each element at its nearest bucket head, ties toward the future. Exact for odd step_ticks (no half-tick loss). For a calendar bucket the nearest head is by absolute tick distance (month lengths vary), ties toward the future.

Returns:



2487
2488
2489
# File 'lib/carray/time.rb', line 2487

def round(grid = nil, unit: nil, origin: nil)
  snap(grid, unit: unit, origin: origin, direction: :round)
end

#scalar_to_storage(surface) ⇒ Integer, Object

Write-direction counterpart of storage_to_scalar (the store hook fired from rb_ca_obj2ptr): brings a surface value object into this Face's int64 storage (count in self's unit since the Unix epoch) so a scalar store round-trips with a fetch. A Element / Time / DateTime is reconciled to self's unit via #to_comparable (same lossless discipline: a cross-group unit or a non-exact finer->coarser cast raises). A bare Integer (the documented .parent raw-storage escape) and a String (parsing is a separate, opposite-direction mechanism) pass through unchanged to the storage cast.

Parameters:

  • surface (Element, Time, DateTime, Integer, String)

Returns:

  • (Integer, Object) —

    the storage-domain value, or surface unchanged for a pass-through type.

Raises:

  • (TypeError, ArgumentError) —

    on an unreconcilable surface / unit.



1042
1043
1044
1045
1046
1047
1048
1049
# File 'lib/carray/time.rb', line 1042

def scalar_to_storage (surface)
  case surface
  when Integer, String
    surface
  else
    to_comparable(surface).parent[0]
  end
end

#second ⇒ CArray

Second of the minute, 0..59; 0 when the storage unit is coarser than a second.

Returns:



1188
# File 'lib/carray/time.rb', line 1188

def second; clock_field(:s); end

#floor(unit:, origin: nil) ⇒ CATime #snap(grid = nil, unit:, origin: nil, direction: :round) ⇒ CATime

Overloads:

  • #floor(unit:, origin: nil) ⇒ CATime

    Returns each element floored to its bucket head (toward the past), as a CATime in the same storage resolution.

    Parameters:

    • unit (String, Symbol, Resolution) —

      bucket resolution.

    • origin (Time, String, CATime::Element, DateTime, nil) (defaults to: nil) —

      head of bucket 0 (default: the Unix epoch, or ISO Monday for a week bucket on day-or-finer storage -- a week grid counts from the epoch Thursday and cannot hold a Monday, so it keeps its own ticks). It has to be a bucket head itself: on a calendar grid, the 1st at 00:00.

    Returns:

  • #snap(grid = nil, unit:, origin: nil, direction: :round) ⇒ CATime

    Returns each element snapped to a point on the bucket grid, as a CATime in the same storage resolution. This is the time-domain twin of CArray#snap: there, (step, offset:, direction:); here a Grid (= tick + origin), or the same pair spelled as unit: / origin:, and the same three directions.

    :round picks the nearest bucket head, ties toward the future; :floor the head at or before; :ceil the head at or after. #floor, #ceil and #round are this method with direction: fixed.

    Parameters:

    • grid (Grid, nil) (defaults to: nil) —

      the bucket grid, passed as one value.

    • unit (String, Symbol, Resolution, Grid) —

      bucket resolution.

    • origin (Time, String, CATime::Element, DateTime, nil) (defaults to: nil) —

      head of bucket 0 (default: the Unix epoch, or ISO Monday for a week bucket on day-or-finer storage -- a week grid counts from the epoch Thursday and cannot hold a Monday, so it keeps its own ticks). It has to be a bucket head itself: on a calendar grid, the 1st at 00:00.

    • direction (:round, :floor, :ceil) (defaults to: :round) —

      rounding rule.

    Returns:



2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
# File 'lib/carray/time.rb', line 2439

def snap(grid = nil, unit: nil, origin: nil, direction: :round)
  unless SNAP_DIRECTIONS.include?(direction)
    raise ArgumentError,
          "snap: direction must be :round / :floor / :ceil " \
          "(got #{direction.inspect})"
  end
  unit, origin = Grid.resolve(grid, unit, origin)
  g = resolve_timestep_grid(unit, origin)
  return calendar_bucket(direction, g[1], origin) if g[0] == :civil
  su, mul, step_ticks, o = g
  raise_if_int64_overflow(su, mul, o, step_ticks, direction)
  n = storage_ticks_in_numerator_grid(mul)
  d = n - o
  q = d / step_ticks
  q = q - (d - q * step_ticks).lt(0)            # floor bucket
  case direction
  when :ceil
    fl = o + q * step_ticks
    return bucket_head_as_time(fl + step_ticks * n.ne(fl), mul, su)
  when :round
    r = d - q * step_ticks                      # 0 <= r < step_ticks
    q = q + r.ge((step_ticks + 1) / 2)          # ties -> future; no 2*d / 2*r
  end
  bucket_head_as_time(o + q * step_ticks, mul, su)
end

#stddev(axis: nil, **opts) ⇒ CATimedelta::Element, CATimedelta

Returns the spread of the instants as a CATimedelta (a duration) on self's unit. A spread is not a lattice point, so the rounding costs more here than for a centroid: on a coarse unit use t.to_unit(:h).stddev when the precision matters (§8).



942
943
944
# File 'lib/carray/time.rb', line 942

def stddev(*args, **opts)
  reduce_on_own_grid(:stddev, :timedelta, args, opts)
end

#stddevp(axis: nil, **opts) ⇒ CATimedelta::Element, CATimedelta

Returns the population spread as a CATimedelta, in the same shapes as #stddev.



950
951
952
# File 'lib/carray/time.rb', line 950

def stddevp(*args, **opts)
  reduce_on_own_grid(:stddevp, :timedelta, args, opts)
end

#strftime(fmt) ⇒ CAString

Returns a CAString whose elements are the per-cell Time#strftime(fmt) result (UTC). The input mask propagates.

Parameters:

  • fmt (String) —

    strftime format string.

Returns:



1239
1240
1241
# File 'lib/carray/time.rb', line 1239

def strftime(fmt)
  CAString.wrap(to_time.convert(:object) {|t| t.strftime(fmt)})
end

#sum(*) ⇒ Object

Not supported; use #mean for a centroid.

Raises:

  • (TypeError) —

    always.

Raises:

  • (TypeError)


957
958
959
# File 'lib/carray/time.rb', line 957

def sum(*)
  raise TypeError, "CATime#sum is ill-defined; use mean for centroid"
end

#ticks ⇒ CArray

Sanctioned external accessor that pairs with the existing unit reader. An interop bridge (carray-xarray, carray-pycall consumers, etc.) can read out the int64 count + unit without reaching into parent (= an internal-contract accessor). The reference epoch is the implicit Unix epoch (1970-01-01 UTC) baked into the convention at the top of this file and into the C layer's Time.at-based decoding; it is not a per-instance state and therefore is not exposed as an accessor.

ca.ticks               # => CArray (int64), tick counts since 1970-01-01 UTC
ca.unit                # => Symbol (:Y :M :W :D :h :m :s :ms :us :ns ...)

Returns the underlying int64 CArray of tick counts since the Unix epoch (1970-01-01 UTC) — the k-th tick of this array's resolution (see §4 of docs/CATime.md).

Returns:



778
779
780
# File 'lib/carray/time.rb', line 778

def ticks
  parent
end

#timesteps(unit: self.unit, origin: nil) ⇒ CArray

Returns the integer timestep of every element: the k-th unit-wide bucket counted from origin (floor toward the past, so pre-origin elements get a negative index -- a normal value, not masked). The result is an int64 CArray; the input mask propagates. With no unit the bucket is the storage resolution itself, so the result is a copy of the raw tick indices since the epoch (the same values as #ticks, but a fresh array rather than the live storage).

Parameters:

  • unit (String, Symbol, Resolution) (defaults to: self.unit) —

    bucket resolution (default: this array's own storage resolution).

  • origin (Time, String, CATime::Element, DateTime, nil) (defaults to: nil) —

    head of bucket 0 (default: the Unix epoch, or ISO Monday for a week bucket on day-or-finer storage -- a week grid counts from the epoch Thursday and cannot hold a Monday, so it keeps its own ticks). It has to be a bucket head itself: on a calendar grid, the 1st at 00:00.

Returns:

  • (CArray) —

    int64 timesteps.

Raises:

  • (ArgumentError) —

    on a sub-resolution / unrepresentable (unit, storage-resolution) pair or a lossy origin.



2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
# File 'lib/carray/time.rb', line 2396

def timesteps(grid = nil, unit: nil, origin: nil)
  unit, origin = Grid.resolve(grid, unit, origin)
  unit = self.unit if unit.nil?
  g = resolve_timestep_grid(unit, origin)
  return calendar_bucket(:index, g[1], origin) if g[0] == :civil
  su, mul, step_ticks, o = g
  raise_if_int64_overflow(su, mul, o)
  d = storage_ticks_in_numerator_grid(mul) - o
  q = d / step_ticks
  q - (d - q * step_ticks).lt(0)      # floor-div correction (%-independent)
end

#to_comparable(operand) ⇒ CATime

Brings operand into self's unit space for a direct storage comparison (comparison operators / search family / linear_section). self is the reference Face -- always one of our classes -- so it class-dispatches the operand rather than requiring every operand type to know every Face (which a core class like Time could not). CATime is ORDERABLE but not COMPARABLE (an operand may carry a different unit or shape), so the gate routes the operand through here.

Accepted operands: another CATime (unit-rescaled to self), a Element (lifted to a length-1 CATime), a Ruby Time, and a Ruby DateTime (both absolute instants converted to self's unit, Unix epoch, UTC). A String is out of scope (parsing is a separate, opposite-direction mechanism). A bare Integer / other type raises; descend to ca.parent to compare the hidden storage directly.

The rescale is an INSTANT conversion (convert_instant!), lossless: a coarser->finer unit always converts; a finer->coarser unit converts only when every value lands exactly on the coarser grid, else raises. Unlike a duration, a cross-group time cast IS possible via civil-date algebra (a :M value has a well-defined instant): :M/:Y widen exactly to :D and finer, and a fixed operand coarsens to :M/:Y only when it sits on the calendar boundary (:W is the one exception -- month/year starts are not week-aligned, so :Y/:M <-> :W raises).

Parameters:

Returns:

  • (CATime) —

    in self's unit.

Raises:

  • (TypeError, ArgumentError) —

    on an unreconcilable operand / unit.



1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
# File 'lib/carray/time.rb', line 1006

def to_comparable (operand)
  case operand
  when CATime
    return operand if operand.unit == unit
    CATimeUnitAlgebra.convert_instant!(operand.parent, operand.unit, unit)
                         .time(unit: unit)
  when CATime::Element
    lifted = CATime.wrap(CA_INT64([operand.value]), unit: operand.unit)
    to_comparable(lifted)
  when Time
    # Reuse the single-literal builder; it yields a length-1 CATime
    # in the requested (= self's) resolution.
    CArray.time(operand, unit: unit)
  when defined?(DateTime) && DateTime
    to_comparable(operand.to_time.utc)
  else
    raise TypeError,
          "CATime cannot reconcile #{operand.class} " \
          "(use ca.parent to compare the raw int64 storage directly)"
  end
end

#to_date ⇒ CArray

Converts every element to a Ruby Date (UTC), returned as an object CArray. A sub-day unit floors to its day. Array-level counterpart to CATime::Element#to_date.

Returns:



1126
1127
1128
1129
1130
# File 'lib/carray/time.rb', line 1126

def to_date
  require 'date'
  # 2440588 = JD of 1970-01-01; proleptic Gregorian to match to_time and the field accessors.
  days_since_epoch.convert(:object) {|d| Date.jd(2440588 + d, Date::GREGORIAN)}
end

#to_datetime ⇒ CArray

Converts every element to a Ruby DateTime (UTC offset 0), returned as an object CArray.

Returns:



1136
1137
1138
1139
# File 'lib/carray/time.rb', line 1136

def to_datetime
  require 'date'
  to_time.convert(:object) {|t| t.to_datetime}
end

#to_time ⇒ CArray

Converts every element to a Ruby Time (UTC), returned as an object CArray. Array-level counterpart to CATime::Element#to_time.

Returns:



1111
1112
1113
1114
1115
1116
1117
1118
1119
# File 'lib/carray/time.rb', line 1111

def to_time
  require 'time'
  if CATimeUnitAlgebra::FIXED.key?(unit.base)
    f = unit.tick_ratio                        # exact seconds / tick (Rational)
    parent.convert(:object) {|v| Time.at(v * f, in: 'UTC')}
  else                                         # calendar: exact granule midnight
    (days_since_epoch * 86400).convert(:object) {|v| Time.at(v, in: 'UTC')}
  end
end

#to_unit(unit) ⇒ CATime

Returns the same instants re-expressed on the unit grid: a new CATime whose storage resolution is unit. Changing the resolution is a cast, not an assertion -- the same relation CArray.time has to its input:

  • Finer target (:D -> :h, :Y -> :M, :M -> :s): exact. Every instant lands on the new grid unchanged.
  • Coarser target (:h -> :D, :D -> :M): each instant floors to the head of the unit tick it falls in -- toward the past, so a pre-epoch instant floors the same way a post-epoch one does. Use #ceil / #round first to land on a different boundary.

The calendar / fixed-length boundary is crossed by civil-date algebra, not by a ratio: a :M value is a real instant (the month's first midnight), so :M -> :D widens exactly and :D -> :M floors to the containing month. :Y / :M -> :W is the one refusal, since a month head is not week-aligned and widening would move the instant.

A duration has no such conversion (CATimedelta#to_unit truncates toward zero and refuses to cross the boundary at all).

Parameters:

  • unit (Resolution, Symbol, String) —

    target resolution.

Returns:

Raises:

  • (ArgumentError) —

    on :Y / :M -> :W.

  • (RangeError) —

    when the widened ticks overflow int64.



807
808
809
810
# File 'lib/carray/time.rb', line 807

def to_unit(unit)
  to = CATime::Resolution.parse(unit)
  CATimeUnitAlgebra.convert_instant_floor(parent, self.unit, to).time(unit: to)
end

#variance(*) ⇒ Object

Not supported: the variance of instants has squared-time units, which no type represents (ill-defined, like #sum). Use #stddev for the spread as a duration.

Raises:

  • (TypeError) —

    always.

Raises:

  • (TypeError)


966
# File 'lib/carray/time.rb', line 966

def variance(*); raise TypeError, "CATime#variance is ill-defined (squared-time units); use stddev"; end

#variancep(*) ⇒ Object

Not supported, for the same reason as #variance. Use #stddevp.

Raises:

  • (TypeError) —

    always.

Raises:

  • (TypeError)


971
# File 'lib/carray/time.rb', line 971

def variancep(*); raise TypeError, "CATime#variancep is ill-defined (squared-time units); use stddevp"; end

#weekday ⇒ CArray

Day of the week, Sunday = 0 .. Saturday = 6.

Returns:



1192
1193
1194
1195
1196
# File 'lib/carray/time.rb', line 1192

def weekday
  # 1970-01-01 is a Thursday (wday 4); Sun=0..Sat=6.
  n = days_since_epoch + 4
  n % 7
end

#yday ⇒ CArray

Day of the year, 1..366.

Returns:



1200
1201
1202
1203
1204
1205
# File 'lib/carray/time.rb', line 1200

def yday
  d    = days_since_epoch
  y    = CATimeCivil.civil_from_days(d)[0]
  ones = CArray.int64(*shape) { 1 }
  d - CATimeCivil.days_from_civil(y, ones, ones) + 1
end

#year ⇒ CArray

Each accessor returns an integer CArray with the requested calendar / clock field extracted from every element (UTC), computed by vectorized civil-date algebra directly on the int64 storage -- no per-cell Time. Exact for every unit, including :M / :Y (where the old Time.at path drifted by using 30.5-day / 365.25-day approximations). Fields finer than the storage unit collapse to their zero point, and the input mask propagates.

Returns:



1149
1150
1151
1152
1153
1154
1155
# File 'lib/carray/time.rb', line 1149

def year
  case unit.base
  when :Y then parent * unit.count + 1970
  when :M then (parent * unit.count + 1970 * 12) / 12
  else CATimeCivil.civil_from_days(days_since_epoch)[0]
  end
end