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.



2267
2268
2269
2270
2271
2272
2273
# File 'lib/carray/time.rb', line 2267

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

#origin ⇒ Element? (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.



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

def origin
  @origin
end

#storage ⇒ Resolution (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.



2286
2287
2288
# File 'lib/carray/time.rb', line 2286

def storage
  @storage
end

#unit ⇒ Resolution (readonly)

Returns the tick this grid counts in.

Returns:



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

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:



2244
2245
2246
2247
2248
2249
2250
2251
# File 'lib/carray/time.rb', line 2244

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



2257
2258
2259
2260
2261
2262
2263
2264
2265
# File 'lib/carray/time.rb', line 2257

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)


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

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:



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

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.



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

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

#hash ⇒ Integer

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

Returns:

  • (Integer)


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

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:



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

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

#inspect ⇒ String

Returns the #to_s spec, wrapped for the console.

Returns:

  • (String) —

    the #to_s spec, wrapped for the console.



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

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.



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

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.



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

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

#origin_ticks ⇒ Integer

Returns storage ticks from the epoch to the origin.

Returns:

  • (Integer) —

    storage ticks from the epoch to the origin.



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

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

#range(start, last) ⇒ Object

A regular series on this grid.



2322
2323
2324
# File 'lib/carray/time.rb', line 2322

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.



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

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

#step_ticks ⇒ Integer

Returns storage ticks per unit tick.

Returns:

  • (Integer) —

    storage ticks per unit tick.



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

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

#to_s ⇒ String

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.



2329
2330
2331
2332
2333
# File 'lib/carray/time.rb', line 2329

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