Class: CATime::Grid

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

Overview

A tick resolution and where its ticks are anchored: the (unit:, origin:) pair that #timesteps, #snap and from_timesteps already take, given a name and a value.

Resolution says how wide a tick is; Grid says that and where tick 0 starts. A CATime is stored as epoch-anchored int64 either way, so this reifies a calling convention -- it does not add a second time representation, and the reference-time epoch stays out of storage.

g = CATime::Grid.parse("12 hours since 2017-11-30 09:00")
t.snap(g, direction: :floor)     # the pair, passed once
t.timesteps(g)                   # which tick each element falls in
g.at(CA_INT64([0, 1, 2]))        # and back

Proleptic Gregorian, UTC. There is no calendar seam here on purpose: a swappable calendar taxes every operation that touches a time, and the benefit does not follow.

Constant Summary collapse

ORIGIN_SHIFT =

udunits' origin-shift operators, all equivalent: the words since, after, from, ref (whitespace-separated) and the @ sign, which needs none ("seconds@1970-01-01"). Generous on input because a units attribute is written by whoever wrote the file; #to_s only ever emits since, which is what CF-conforming files use.

/\A(.+?)(?:\s+(?:since|after|from|ref)\s+|\s*@\s*)(.+)\z/i
WORD =

Plural unit words, the spelling Resolution.parse reads back. (Bare Resolution#to_s does not round-trip: it prints "h" / "12 h", and Resolution.parse takes neither.)

{ Y: "years", M: "months", W: "weeks", D: "days", h: "hours",
m: "minutes", s: "seconds", ms: "milliseconds",
us: "microseconds", ns: "nanoseconds", ps: "picoseconds",
fs: "femtoseconds", as: "attoseconds" }.freeze
FALLBACK_BASES =

Storage resolutions to fall back on when the unit's own tick cannot hold the origin exactly (a day grid anchored at 12:00 needs seconds).

%i[s ms us ns].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(unit, origin = nil) ⇒ Grid

Returns a new instance of Grid.



2251
2252
2253
2254
2255
2256
2257
# File 'lib/carray/time.rb', line 2251

def initialize(unit, origin = nil)
  @unit    = Resolution.parse(unit)
  @origin  = origin
  @storage = resolve_storage
  @origin  = CArray.time(origin, unit: @storage)[0] if origin
  freeze
end

Instance Attribute Details

#originElement? (readonly)

Returns the instant tick 0 starts at. nil is the epoch, which is what every origin: keyword already defaults to.

Returns:

  • (Element, nil)

    the instant tick 0 starts at. nil is the epoch, which is what every origin: keyword already defaults to.



2264
2265
2266
# File 'lib/carray/time.rb', line 2264

def origin
  @origin
end

#storageResolution (readonly)

Returns the resolution an array on this grid is stored on: the unit itself, or finer when the origin's phase needs it. Derived from (unit, origin) and memoized here, so a grid's identity stays the (count, unit, origin) triple.

Returns:

  • (Resolution)

    the resolution an array on this grid is stored on: the unit itself, or finer when the origin's phase needs it. Derived from (unit, origin) and memoized here, so a grid's identity stays the (count, unit, origin) triple.



2270
2271
2272
# File 'lib/carray/time.rb', line 2270

def storage
  @storage
end

#unitResolution (readonly)

Returns the tick this grid counts in.

Returns:



2260
2261
2262
# File 'lib/carray/time.rb', line 2260

def unit
  @unit
end

Class Method Details

.parse(spec, origin: nil) ⇒ Grid

Parameters:

  • spec ("<unit> since <instant>", unit spec, Grid)
  • origin (Time, String, DateTime, Element, nil) (defaults to: nil)

    when spec carries no origin-shift clause.

Returns:



2228
2229
2230
2231
2232
2233
2234
2235
# File 'lib/carray/time.rb', line 2228

def self.parse(spec, origin: nil)
  return spec if spec.is_a?(Grid)
  if spec.is_a?(String) and (fields = ORIGIN_SHIFT.match(spec))
    new(fields[1], fields[2])
  else
    new(spec, origin)
  end
end

.resolve(grid, unit, origin) ⇒ Array(Object, Object)

Normalizes the three ways a grid reaches a method -- positionally, as unit:, or as the loose (unit:, origin:) pair -- into that pair. One call at the head of a method is the whole Grid arm.

Returns:

  • (Array(Object, Object))

    (unit, origin).



2241
2242
2243
2244
2245
2246
2247
2248
2249
# File 'lib/carray/time.rb', line 2241

def self.resolve(grid, unit, origin)
  return [grid.unit, grid.origin] if grid.is_a?(Grid)
  return [unit.unit, unit.origin] if unit.is_a?(Grid)
  unless grid.nil?
    raise ArgumentError,
          "the positional argument must be a CATime::Grid (got #{grid.class})"
  end
  [unit, origin]
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Two grids are equal when they place ticks in the same places: same timestep, same origin.

Returns:

  • (Boolean)


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

def ==(other) = other.is_a?(Grid) && other.unit == @unit && other.to_s == to_s

#at(k) ⇒ CATime, Element

Tick indices -> instants: the CATime.from_timesteps direction, but answering on #storage so a phased origin survives.

Parameters:

Returns:



2288
2289
2290
2291
2292
# File 'lib/carray/time.rb', line 2288

def at(k)
  return at(CArray.int64(1) { Integer(k) })[0] unless k.is_a?(CArray)
  return CATime.from_timesteps(k.int64, unit: @unit, origin: @origin) if calendar?
  (origin_ticks + k.int64 * step_ticks).time(unit: @storage)
end

#calendar?Boolean

Returns whether the unit is a calendar one (:Y / :M), whose ticks are month ordinals rather than a fixed number of seconds.

Returns:

  • (Boolean)

    whether the unit is a calendar one (:Y / :M), whose ticks are month ordinals rather than a fixed number of seconds.



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

def calendar? = %i[Y M].include?(@unit.base)

#hashInteger

Hashes with #==, so a grid works as a Hash key.

Returns:

  • (Integer)


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

def hash = [@unit, to_s].hash

#index(time) ⇒ CArray

Instants -> tick indices: the CATime#timesteps direction. Off-grid elements floor toward the past; ask #on when that would be a lie.

Returns:



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

def index(time) = time.timesteps(self)

#inspectString

Returns the #to_s spec, wrapped for the console.

Returns:

  • (String)

    the #to_s spec, wrapped for the console.



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

def inspect = "#<CATime::Grid #{self}>"

#on(time) ⇒ CArray

Returns boolean, flagging elements that land on a tick.

Returns:

  • (CArray)

    boolean, flagging elements that land on a tick.



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

def on(time) = time.is_righttime(self)

#on?(time) ⇒ Boolean

Returns whether every element lands on a tick.

Returns:

  • (Boolean)

    whether every element lands on a tick.



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

def on?(time) = on(time).all

#origin_ticksInteger

Returns storage ticks from the epoch to the origin.

Returns:

  • (Integer)

    storage ticks from the epoch to the origin.



2280
2281
2282
# File 'lib/carray/time.rb', line 2280

def origin_ticks
  @origin ? Integer(CATimeLiteral.epoch_seconds(@origin) / @storage.tick_ratio) : 0
end

#range(start, last) ⇒ Object

A regular series on this grid.



2306
2307
2308
# File 'lib/carray/time.rb', line 2306

def range(start, last) = CArray.time_range(start, last, unit: @storage, step: @unit)
# A regular series on this grid: +count+ elements from +start+, one per
# tick.

#series(start, count:) ⇒ Object

A regular series on this grid: count elements from start, one per tick.



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

def series(start, count:) = CArray.time_series(start, count: count, unit: @storage, step: @unit)

#step_ticksInteger

Returns storage ticks per unit tick.

Returns:

  • (Integer)

    storage ticks per unit tick.



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

def step_ticks = Integer(@unit.tick_ratio / @storage.tick_ratio)

#to_sString

Returns "12 hours since 2017-11-30 09:00:00", which parse reads back into an equal grid.

Returns:

  • (String)

    "12 hours since 2017-11-30 09:00:00", which parse reads back into an equal grid.



2313
2314
2315
2316
2317
# File 'lib/carray/time.rb', line 2313

def to_s
  word = WORD.fetch(@unit.base)
  spec = @unit.count == 1 ? word : "#{@unit.count} #{word}"
  @origin ? "#{spec} since #{@origin.to_time.strftime('%Y-%m-%d %H:%M:%S')}" : spec
end