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:



2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
# File 'lib/carray/time.rb', line 2518

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:



380
381
382
383
# File 'lib/carray/time.rb', line 380

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:



391
392
393
# File 'lib/carray/time.rb', line 391

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.



804
805
806
807
808
809
810
811
812
813
# File 'lib/carray/time.rb', line 804

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.



823
824
825
826
827
828
829
830
831
832
833
834
835
# File 'lib/carray/time.rb', line 823

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

#ajdCArray

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

Returns:



1203
1204
1205
1206
# File 'lib/carray/time.rb', line 1203

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:



2461
2462
2463
# File 'lib/carray/time.rb', line 2461

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

#dayCArray

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

Returns:



1156
1157
1158
1159
1160
1161
# File 'lib/carray/time.rb', line 1156

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:



2453
2454
2455
# File 'lib/carray/time.rb', line 2453

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

#gridGrid

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:



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

def grid = Grid.new(unit)

#hourCArray

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

Returns:



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

def hour;   clock_field(:h); end

#is_leapCArray

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

Returns:



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

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:



2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
# File 'lib/carray/time.rb', line 2487

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

#jdCArray

Returns the Julian Day Number for each element.

Returns:



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

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:



1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
# File 'lib/carray/time.rb', line 1065

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:



888
889
890
# File 'lib/carray/time.rb', line 888

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:



897
898
899
# File 'lib/carray/time.rb', line 897

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).



863
864
865
866
# File 'lib/carray/time.rb', line 863

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

#minuteCArray

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

Returns:



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

def minute; clock_field(:m); end

#monthCArray

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

Returns:



1143
1144
1145
1146
1147
1148
1149
1150
1151
# File 'lib/carray/time.rb', line 1143

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.



907
908
909
# File 'lib/carray/time.rb', line 907

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:



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

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:



2471
2472
2473
# File 'lib/carray/time.rb', line 2471

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.



1025
1026
1027
1028
1029
1030
1031
1032
# File 'lib/carray/time.rb', line 1025

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

#secondCArray

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

Returns:



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

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:



2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
# File 'lib/carray/time.rb', line 2423

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).



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

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.



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

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:



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

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)


940
941
942
# File 'lib/carray/time.rb', line 940

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

#ticksCArray

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:



761
762
763
# File 'lib/carray/time.rb', line 761

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.



2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
# File 'lib/carray/time.rb', line 2380

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.



989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
# File 'lib/carray/time.rb', line 989

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_dateCArray

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:



1109
1110
1111
1112
1113
# File 'lib/carray/time.rb', line 1109

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_datetimeCArray

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

Returns:



1119
1120
1121
1122
# File 'lib/carray/time.rb', line 1119

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

#to_timeCArray

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

Returns:



1094
1095
1096
1097
1098
1099
1100
1101
1102
# File 'lib/carray/time.rb', line 1094

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.



790
791
792
793
# File 'lib/carray/time.rb', line 790

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)


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

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)


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

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

#weekdayCArray

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

Returns:



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

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

#ydayCArray

Day of the year, 1..366.

Returns:



1183
1184
1185
1186
1187
1188
# File 'lib/carray/time.rb', line 1183

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

#yearCArray

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:



1132
1133
1134
1135
1136
1137
1138
# File 'lib/carray/time.rb', line 1132

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