Module: CATimeGrid
- Defined in:
- lib/carray/time.rb
Overview
Places a bucket grid on a storage resolution: resolves a timestep into whole storage ticks, resolves an origin into a tick count (or a month ordinal on the calendar path), and guards both against int64 overflow. Internal to the time surface: nothing here is part of the public API.
Constant Summary collapse
- SU_LE_DAY =
Base units that are day-or-finer (a period head is representable exactly; the civil path targets these for a calendar bucket).
%i[D h m s ms us ns ps fs as].freeze
- INT64_MIN =
Smallest value an int64 storage tick can hold.
-(2**63) # Largest value an int64 storage tick can hold.
- INT64_MAX =
Largest value an int64 storage tick can hold.
2**63 - 1
Class Method Summary collapse
-
.calendar_bucket_head_ticks(k, st, storage_res, origin) ⇒ Object
Storage ticks of the bucket head at timestep
k(int64 CArray) for a calendar bucket: ym0 + k*count months -> day 1 of that month -> ticks. -
.check_calendar_origin(origin, storage_res) ⇒ Object
Raise unless
origincan be the head of bucket 0 on a calendar grid: it has to be a month head, and a :Y tick starts in January. -
.check_int64_range(v, what) ⇒ Object
Raise (rather than let an int64 CArray operand silently wrap) if a Ruby-domain quantity is out of int64 range.
-
.origin_month_ordinal(origin) ⇒ Object
Month ordinal (year*12 + month-1) of origin; day / time ignored.
-
.origin_on_month_head?(origin) ⇒ Boolean
Whether
originsits exactly on the head of its month (the 1st at 00:00 UTC). -
.origin_seconds_exact(origin) ⇒ Object
Exact Rational seconds since the Unix epoch for a fixed-storage origin.
-
.origin_year_month(origin) ⇒ Object
[year, month] of a calendar-storage origin (finer fields ignored).
-
.resolve_integer_grid(step_res, storage_res, origin) ⇒ Object
Return [mul, step_ticks, origin_ticks] (all Integer) for the integer path -- step_ticks and origin_ticks in numerator ticks, the finer of the storage and step grids (see #resolve_timestep_grid).
-
.resolve_origin_ticks(origin, step_res, storage_res) ⇒ Object
origin -> integer tick count in storage ticks.
-
.resolve_step_scale(step_res, storage_res) ⇒ Object
[mul, step_ticks] for the integer path, or :civil (calendar bucket on day-or-finer storage), or raise.
-
.ticks_per_day(storage_res) ⇒ Object
Storage ticks per day of
storage_res(must be a whole number, else the storage grid does not tile a day -- a calendar bucket is unrepresentable).
Class Method Details
.calendar_bucket_head_ticks(k, st, storage_res, origin) ⇒ Object
Storage ticks of the bucket head at timestep k (int64 CArray) for a
calendar bucket: ym0 + k*count months -> day 1 of that month -> ticks.
1960 1961 1962 1963 1964 1965 1966 1967 1968 |
# File 'lib/carray/time.rb', line 1960 def calendar_bucket_head_ticks(k, st, storage_res, origin) tpd = ticks_per_day(storage_res) count = st.count * (st.base == :Y ? 12 : 1) ym0 = origin_month_ordinal(origin) ymk = ym0 + k * count yy = ymk / 12 mm = ymk - yy * 12 + 1 CATimeCivil.days_from_civil(yy, mm, CArray.int64(*k.shape) { 1 }) * tpd end |
.check_calendar_origin(origin, storage_res) ⇒ Object
Raise unless origin can be the head of bucket 0 on a calendar grid:
it has to be a month head, and a :Y tick starts in January. The day
and time would otherwise be dropped silently -- the same loss a fixed
unit already refuses (see resolve_origin_ticks).
2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 |
# File 'lib/carray/time.rb', line 2070 def check_calendar_origin(origin, storage_res) return if origin.nil? y, m = origin_year_month(origin) unless origin_on_month_head?(origin) raise ArgumentError, "origin #{origin.inspect} is not a month head: a calendar grid " \ "is addressed by month, so its origin must be the 1st at 00:00" end if storage_res && storage_res.base == :Y && m != 1 raise ArgumentError, "origin #{origin.inspect} is not on the #{storage_res} grid " \ "(a #{storage_res} tick starts in January)" end [y, m] end |
.check_int64_range(v, what) ⇒ Object
Raise (rather than let an int64 CArray operand silently wrap) if a Ruby-domain quantity is out of int64 range. Returns the value on success so it composes inline.
1926 1927 1928 1929 1930 1931 1932 1933 1934 |
# File 'lib/carray/time.rb', line 1926 def check_int64_range(v, what) if v < INT64_MIN || v > INT64_MAX raise RangeError, "time step: #{what} = #{v} overflows int64 " \ "(the storage unit is too fine for this step / origin / span; " \ "use a coarser unit)" end v end |
.origin_month_ordinal(origin) ⇒ Object
Month ordinal (year*12 + month-1) of origin; day / time ignored. Default (nil) is the epoch month 1970-01.
1972 1973 1974 1975 1976 |
# File 'lib/carray/time.rb', line 1972 def origin_month_ordinal(origin) return 1970 * 12 if origin.nil? y, m = check_calendar_origin(origin, nil) y * 12 + (m - 1) end |
.origin_on_month_head?(origin) ⇒ Boolean
Whether origin sits exactly on the head of its month (the 1st at
00:00 UTC). A calendar grid is addressed by month ordinal, so its
bucket heads are month heads and nothing else; an origin anywhere in
between names a bucket that does not exist.
2056 2057 2058 2059 2060 2061 2062 2063 2064 |
# File 'lib/carray/time.rb', line 2056 def origin_on_month_head?(origin) # A calendar element is a month head by construction, and the shared # entry point says so by handing back that midnight -- no branch here # has to know it. secs = CATimeLiteral.epoch_seconds(origin) y, m = origin_year_month(origin) head = CATimeCivil.days_from_civil(CA_INT64([y]), CA_INT64([m]), CA_INT64([1]))[0] secs == head * 86400 end |
.origin_seconds_exact(origin) ⇒ Object
Exact Rational seconds since the Unix epoch for a fixed-storage origin. Everything but a bare Integer goes through the shared entry point, so an origin reads the same here as it does for a start literal.
2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 |
# File 'lib/carray/time.rb', line 2041 def origin_seconds_exact(origin) case origin when Integer raise ArgumentError, "origin: a bare Integer is ambiguous (epoch-dependent); pass a " \ "Time / String / CATime scalar" else CATimeLiteral.epoch_seconds(origin) # Time / String / DateTime / Element end end |
.origin_year_month(origin) ⇒ Object
[year, month] of a calendar-storage origin (finer fields ignored).
2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 |
# File 'lib/carray/time.rb', line 2087 def origin_year_month(origin) case origin when Integer raise ArgumentError, "origin: a bare Integer is ambiguous (epoch-dependent); pass a " \ "Time / String / CATime scalar" else CATimeLiteral.year_month(origin) # Time / String / DateTime / Element end end |
.resolve_integer_grid(step_res, storage_res, origin) ⇒ Object
Return [mul, step_ticks, origin_ticks] (all Integer) for the integer path -- step_ticks and origin_ticks in numerator ticks, the finer of the storage and step grids (see #resolve_timestep_grid). The calendar path is handled by the caller.
1940 1941 1942 1943 1944 |
# File 'lib/carray/time.rb', line 1940 def resolve_integer_grid(step_res, storage_res, origin) mul, step_ticks = resolve_step_scale(step_res, storage_res) [mul, step_ticks, resolve_origin_ticks(origin, step_res, mul == 1 ? storage_res : step_res)] end |
.resolve_origin_ticks(origin, step_res, storage_res) ⇒ Object
origin -> integer tick count in storage ticks. Default (nil) is the epoch, except a week bucket defaults to ISO Monday (1970-01-05). A lossy conversion (origin not landing exactly on the storage grid) raises; a bare Integer is rejected (epoch-dependent, ambiguous).
2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 |
# File 'lib/carray/time.rb', line 2007 def resolve_origin_ticks(origin, step_res, storage_res) if origin.nil? if step_res.base == :W && SU_LE_DAY.include?(storage_res.base) return check_int64_range(ticks_per_day(storage_res) * 4, "ISO-Monday week origin in ticks of #{storage_res}") end return 0 end if CATimeUnitAlgebra::FIXED.key?(storage_res.base) secs = origin_seconds_exact(origin) tick = secs / storage_res.tick_ratio # Rational unless tick.denominator == 1 raise ArgumentError, "origin #{origin.inspect} is not representable losslessly " \ "in storage resolution #{storage_res} (would truncate the grid phase)" end check_int64_range(tick.numerator, "origin #{origin.inspect} in ticks of #{storage_res}") else # :Y / :M storage y, m = check_calendar_origin(origin, storage_res) ord = storage_res.base == :Y ? (y - 1970) : (y * 12 + (m - 1) - 1970 * 12) if storage_res.count > 1 unless (ord % storage_res.count).zero? raise ArgumentError, "origin #{origin.inspect} is not on the #{storage_res} grid" end ord /= storage_res.count end ord end end |
.resolve_step_scale(step_res, storage_res) ⇒ Object
[mul, step_ticks] for the integer path, or :civil (calendar bucket on day-or-finer storage), or raise. Bucket arithmetic runs in the finer of the two grids, so exactly one of the pair is > 1:
- bucket at or coarser than the storage tick (a :h bucket on :s
storage) -> [1, N]: N storage ticks per bucket.
- bucket finer than the storage tick (a :h bucket on :D storage) ->
[N, 1]: one storage tick spans N buckets exactly, so a timestep is a
plain widening. This needs the storage tick to be a *whole* multiple
of the bucket tick; a partial multiple ("90 minutes" storage against
an :h bucket) has no integer timestep and raises.
1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 |
# File 'lib/carray/time.rb', line 1988 def resolve_step_scale(step_res, storage_res) r = CATimeUnitAlgebra.ratio(step_res, storage_res) # storage ticks / step tick if r what = "bucket #{step_res} in ticks of #{storage_res}" return [1, check_int64_range(r.numerator, what)] if r.denominator == 1 return [check_int64_range(r.denominator, what), 1] if r.numerator == 1 end if CATimeUnitAlgebra::CALENDAR.key?(step_res.base) && SU_LE_DAY.include?(storage_res.base) return :civil end raise ArgumentError, "cannot express bucket #{step_res} on storage resolution #{storage_res} " \ "(neither is a whole multiple of the other)" end |
.ticks_per_day(storage_res) ⇒ Object
Storage ticks per day of storage_res (must be a whole number, else the
storage grid does not tile a day -- a calendar bucket is unrepresentable).
1948 1949 1950 1951 1952 1953 1954 1955 1956 |
# File 'lib/carray/time.rb', line 1948 def ticks_per_day(storage_res) r = CATimeUnitAlgebra.ratio(CATime::Resolution.new(1, :D), storage_res) unless r.denominator == 1 raise ArgumentError, "cannot place a calendar bucket on storage resolution " \ "#{storage_res} (its tick does not tile a day)" end check_int64_range(r.numerator, "ticks per day for #{storage_res}") end |