Class: Horologium::Scales::UTC
- Defined in:
- lib/horologium/scales/utc.rb,
sig/horologium/scales/utc.rbs
Overview
Coordinated Universal Time, the civil scale of clocks and calendars. It keeps close to the Earth's rotation by holding a leap second now and then, so a UTC day is usually 86,400 SI seconds but 86,401 on a day that gains one. TAI runs without them, so the gap between the two, TAI - UTC, steps up by a second at each.
A UTC Julian Date is not uniform time: a day gaining a leap second still
spans one unit of it, so its fraction is stretched over 86,401 seconds.
This is the convention ERFA uses, and it is why Representations::Civil
asks the scale how long the day is. The conversion to and from TAI reads
the leap seconds from Data::LeapSeconds, and does the arithmetic at the
instant's precision, so a UTC to TAI round trip is exact at :exact and
within a nanosecond at :standard.
UTC runs from 1961-01-01, the start of the published TAI - UTC series. From 1972 it steps by whole leap seconds. From 1961 to 1972 it was steered by rate adjustments instead: TAI - UTC drifts by a fraction of a second a day, so a UTC second there is fractionally longer than an SI second and the civil clock still counts 86,400 of them a day, with no second 60. Data::LeapSeconds reads both regimes. A reading before 1961 raises OutOfRangeError and names the continuous scales, which reach any date.
Constant Summary collapse
- FIRST_DAY =
The Julian Day Number of 1961-01-01, the first day the published TAI - UTC series covers. A reading on an earlier day is refused.
2_437_301- HALF =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Half a day, the gap between a Julian Date, which starts at noon, and the midnight a day starts at.
Rational(1, 2)
- MAX_STEPS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
The refinement takes at most one step, since a guess from the TAI date is at most a day out. Two more are room to spare before a moment is taken as out of range.
3
Class Method Summary collapse
-
.from_reference(value, precision) ⇒ Horologium::Numeric::TwoPartFloat, Horologium::Numeric::Exact
A TAI Julian Date, read in UTC.
-
.provenance(value) ⇒ Symbol
How well founded a UTC reading is.
-
.seconds_in_day(day_number) ⇒ Integer
The seconds the civil clock counts in a UTC day: 86,400, and 86,401 on a day that holds a whole leap second, where the last minute reaches second 60.
-
.si_seconds_in_day(day_number) ⇒ Integer, Rational
The SI seconds a UTC day spans: 86,400, one more on a day that holds a leap second, and a fraction more through the pre-1972 drift.
-
.to_reference(value, precision) ⇒ Horologium::Numeric::TwoPartFloat, Horologium::Numeric::Exact
A UTC Julian Date, read back in TAI.
-
.zone_designator ⇒ String
UTC writes
Z, where a zero offset is a real thing.
Class Method Details
.from_reference(value, precision) ⇒ Horologium::Numeric::TwoPartFloat, Horologium::Numeric::Exact
A TAI Julian Date, read in UTC. It finds the UTC day the instant
falls in, then takes the fraction through that day back off the leap
second spread and the drift, so it reads as a plain time of day. This
inverts to_reference in closed form, exactly at :exact.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/horologium/scales/utc.rb', line 66 def from_reference(value, precision) day = (value.to_r + HALF).floor MAX_STEPS.times do break if day < FIRST_DAY if before_midnight?(value, day, precision) day -= 1 elsif !before_midnight?(value, day + 1, precision) day += 1 else enforce_horizon(day) return Numeric::Precision.add( Numeric::Precision.build(day - HALF, precision), unstretch(value, day, precision) ) end end refuse end |
.provenance(value) ⇒ Symbol
How well founded a UTC reading is. :measured up to the date the
leap second data vouches for, :extrapolated past it, where the
offset is the last known one and a new leap second could overturn
it. A source that states no expiry is taken as :measured
throughout, since there is no horizon to be past.
168 169 170 |
# File 'lib/horologium/scales/utc.rb', line 168 def provenance(value) past_horizon?((value.to_r + HALF).floor) ? :extrapolated : :measured end |
.seconds_in_day(day_number) ⇒ Integer
The seconds the civil clock counts in a UTC day: 86,400, and 86,401 on a day that holds a whole leap second, where the last minute reaches second 60. Before 1972 the day is always 86,400 civil seconds; the drift in TAI - UTC there is spread across the day as slightly longer seconds, not shown as an extra one. The conversion to and from TAI uses the SI length instead, which the drift stretches.
129 130 131 132 133 |
# File 'lib/horologium/scales/utc.rb', line 129 def seconds_in_day(day_number) refuse unless day_number >= FIRST_DAY Duration::SECONDS_PER_DAY + whole_leap_seconds(day_number) end |
.si_seconds_in_day(day_number) ⇒ Integer, Rational
The SI seconds a UTC day spans: 86,400, one more on a day that holds a leap second, and a fraction more through the pre-1972 drift. It is the length the conversion to TAI stretches the day over, where seconds_in_day is the whole count the civil clock shows. A numeric ISO 8601 offset counts against this, so it shifts by SI seconds even on a drift day.
145 146 147 148 149 150 |
# File 'lib/horologium/scales/utc.rb', line 145 def si_seconds_in_day(day_number) refuse unless day_number >= FIRST_DAY day_scale(day_number, tai_utc_at(day_number)) * Duration::SECONDS_PER_DAY end |
.to_reference(value, precision) ⇒ Horologium::Numeric::TwoPartFloat, Horologium::Numeric::Exact
A UTC Julian Date, read back in TAI. It takes the fraction through
the UTC day, spreads it over the day's real length with the leap
second and the drift included, and adds it to the TAI of that day's
0h. This is the conversion ERFA performs in eraUtctai.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/horologium/scales/utc.rb', line 101 def to_reference(value, precision) day = (value.to_r + HALF).floor refuse unless day >= FIRST_DAY enforce_horizon(day) dat0 = tai_utc_at(day) scaled = day_fraction(value, day, precision) * day_scale(day, dat0) Numeric::Precision.add( Numeric::Precision.build(day - HALF, precision), Numeric::Precision.add( scaled, Numeric::Precision.build(dat0, precision) / Duration::SECONDS_PER_DAY ) ) end |
.zone_designator ⇒ String
UTC writes Z, where a zero offset is a real thing.
155 156 157 |
# File 'lib/horologium/scales/utc.rb', line 155 def zone_designator "Z" end |