UTC time scales and leap second support.
The world's standard time scale is Universal Coordinated Time (UTC).
UTC is directly based on International Atomic Time (TAI)
ts-taistd/time/instant/ts-tai: timescale) and uses standard SI seconds. The UTC time scale differs from
TAI in that UTC stays within 1 second of UT1; the
time scale based on the rotation of the Earth. To keep UTC close to UT1
about every 1.5 year a leap second is added to the day (appearing as
a 60th second in the last minute of the day, e.g. 23:59:60h).
In order to calculate SI second durations between time instants, we need to know when a leap second is inserted. For example, there was a leap second inserted on 2017-01-01Z, so:
time(2017,1,1,0,0,0) - time(2016,12,31,23,0,0) time(2017,1,1,0,0,0) - time(2016,12,31,23,0,0)
equals 3601 SI seconds to account for the extra leap second. We also need to
know the inserted leap seconds to convert UTC time to TAI which forms the
basis of many other time scales (see
The occurrence of leap seconds cannot be reliably predicted though and the IERS usually announces leap seconds about half a year in advance. The IETF leap second table contains a list of all currently announced leap seconds. As such, any future duration calculation in UTC is essentially non-deterministic since it may be off by a number of (as yet unannounced) leap seconds. This means in practice:
You cannot reliably tell how many SI seconds in the future a certain
UTC time occurs (since there may be leap seconds inserted in the future).
If you need to store a future date, say
store it as an ISO calendar date (
time), not as a time stamp.
Do not use Unix time stamps as those may be ambigious (see
unix-instantstd/time/utc/unix-instant: (secs : double, frac : ?double) -> instant);
already defaults to time stamps as SI seconds since
epochstd/time/instant/epoch: instant which are
monotonic and unambigious. If no interaction with the user is needed,
consider using TAI time and calendar instead.
Since UTC time calculations depend on a leap second table (
the UTC time scale can only be created from such table (
ts-utc-createstd/time/utc/ts-utc-create: (leaps : leaps-table) -> timescale).
For most precise UTC time, you can use
to automatically download and cache the latest IERS leap second information.
This can be somewhat cumbersome though as this is a function with an
iostd/core/io: E effect.
International Time (TI) (or Temps International) is a proposed time scale
that matches exactly UTC up to some date but with no further leap seconds
added after that – which makes time calculations robust and deterministic
with regard to future dates. For this reason, this library defaults to using
TI instead of UTC. The TI time scale (
ts-tistd/time/utc/ts-ti: timescale) is defined
to exactly match UTC before the compiler release date (currently 2017) but
ignores any future leap seconds.
UTC is only well defined after 1972-01-01Z where it got integral leap seconds. Before 1972, the UTC time was broadcast using a combination of fixed time steps (i.e. mini leap seconds) and an offset from the atomic clock frequency. By adjusting the frequency, UTC time was effectively running at a linearly adjusted rate relative to TAI. We call this adjustment drift. For example, between 1963-11-01Z and 1964-01-01Z the frequency offset was -130×10-10 which means UTC was running ahead of TAI by 0.0011232 seconds per day. The drift was then defined as:
(TAI-UTC) = 1.9458580 + 0.0011232×Δ(1962-01-01)
where the function Δ(date) returns the number of days since date. Therefore, the difference started as 2.6972788s (as there were 669 days between 1962-01-01 and 1963-11-01) and increased linearly up to 2.7657940s on 1964-01-01Z. For dates between 1961-01-01Z and 1972-01-01Z the library calculates the UTC time based on the historical USNO drift data (see Table 1).
Here are some interesting time steps that occurred in this time frame:
There was a negative time step of -0.1s inserted on 1968-02-01Z:
instant(1968,1,31,23,59,59,0.9).time(cal=cal-tai) == "1968-02-01T00:00:06.185682Z TAI" instant(1968,2,1).time(cal=cal-tai) == "1968-02-01T00:00:06.185682Z TAI" instant(1968,1,31,23,59,59,0.9).time(cal=cal-tai) == "1968-02-01T00:00:06.185682Z TAI" instant(1968,2,1).time(cal=cal-tai) == "1968-02-01T00:00:06.185682Z TAI"
The only other negative time step was inserted on 1961-08-01Z with a duration of -0.05s.
On the transition to modern UTC at 1972-01-01Z there is a mini leap second of 0.107758s. (because the final drift just before 1971-01-01 is 9.892242s)
instant(1972,1,1,0,0,9,0.99999999,cal=cal-tai).time.show(6) == "1971-12-31T23:59:60.107758Z" instant(1972,1,1,0,0,10,cal=cal-tai).time.show == "1972-01-01T00:00:00Z" instant(1972,1,1,0,0,9,0.99999999,cal=cal-tai).time.show(6) == "1971-12-31T23:59:60.107758Z" instant(1972,1,1,0,0,10,cal=cal-tai).time.show == "1972-01-01T00:00:00Z"
The TAI timescale was officially established on 1958-01-01Z, and at that time the difference (TAI - UT1) was approximately zero. In the time frame 1958 up to 1961 the relation between TAI and UTC is not well defined, as UTC was only officially established in 1960 (Arias and Guinot ).
We can however use the time steps and frequency adjustments as broadcast by the NIST WWV radio station (see Explanatory Supplement to the Astronomical Almanac [2, pages 86–87]). Historically there were 18 20ms time steps from 1958. The frequency offset in the year 1958 is not clear, and is described in the Supplement as “.. an offset of about -100×10-10 during 1958”. We fixed the offset at -85×10-10 in order to have a zero difference from TAI at exactly 1958-01-01Z.
In the library, this makes UTC coincide with TAI up to 1958-01-01, interpolated with ‘drift’ up to 1972-01-01, and using integral leap-seconds after that.
Table 1 shows all leap second time steps from 1958 up to 2017. This table is calculated and uses historical data for time steps before 1972.
|Date (UTC)||Time steps||(TAI–UTC)||(TAI–UTC)||Drift||Frequency|
|Just before||On the date||Offset|
|1958-01-01||+0||0||0||0.000s + 0.00073458×Δ(1958-01-01)||-85×10-10|
|1958-01-15, 19:00||+0.020||0.0108657||0.0308657||0.020s + 0.00073458×Δ( " )||-85×10-10|
|1958-02-05, 19:00||+0.020||0.0462918||0.0662918||0.040s + 0.00073458×Δ( " )||-85×10-10|
|1958-02-19, 19:00||+0.020||0.0765760||0.0965760||0.060s + 0.00073458×Δ( " )||-85×10-10|
|1958-04-09, 19:00||+0.020||0.1325704||0.1525704||0.080s + 0.00073458×Δ( " )||-85×10-10|
|1958-06-11, 19:00||+0.020||0.1988489||0.2188489||0.100s + 0.00073458×Δ( " )||-85×10-10|
|1958-07-02, 19:00||+0.020||0.2342751||0.2542751||0.120s + 0.00073458×Δ( " )||-85×10-10|
|1958-07-16, 19:00||+0.020||0.2645592||0.2845592||0.140s + 0.00073458×Δ( " )||-85×10-10|
|1958-10-22, 19:00||+0.020||0.3565481||0.3765481||0.160s + 0.00073458×Δ( " )||-85×10-10|
|1958-11-26, 19:00||+0.020||0.4022584||0.4222584||0.180s + 0.00073458×Δ( " )||-85×10-10|
|1958-12-24, 19:00||+0.020||0.4428266||0.4628266||0.200s + 0.00073458×Δ( " )||-85×10-10|
|1959-01-01||+0||0.4681217||0.4681220||0.468122s + 0.0008640×Δ(1959-01-01)||-100×10-10|
|1959-01-28, 19:00||+0.020||0.4921340||0.5121340||0.488122s + 0.0008640×Δ( " )||-100×10-10|
|1959-02-25, 19:00||+0.020||0.5363260||0.5563260||0.508122s + 0.0008640×Δ( " )||-100×10-10|
|1959-04-05, 19:00||+0.020||0.5900220||0.6100220||0.528122s + 0.0008640×Δ( " )||-100×10-10|
|1959-08-26, 19:00||+0.020||0.7335740||0.7535740||0.548122s + 0.0008640×Δ( " )||-100×10-10|
|1959-09-30, 19:00||+0.020||0.7838140||0.8038140||0.568122s + 0.0008640×Δ( " )||-100×10-10|
|1959-11-04, 19:00||+0.020||0.8340540||0.8540540||0.588122s + 0.0008640×Δ( " )||-100×10-10|
|1959-11-18, 19:00||+0.020||0.8661500||0.8861500||0.608122s + 0.0008640×Δ( " )||-100×10-10|
|1959-12-16, 19:00||+0.020||0.9103420||0.9303420||0.628122s + 0.0008640×Δ( " )||-100×10-10|
|1960-01-01||+0||0.9434820||0.9434820||0.943482s + 0.0012960×Δ(1960-01-01)||-150×10-10|
|1961-01-01||+0.005||1.4178180||1.4228180||1.422818s + 0.0012960×Δ(1961-01-01)||-150×10-10|
|1961-08-01||–0.050||1.6975700||1.6475700||1.372818s + 0.0012960×Δ( " )||-150×10-10|
|1962-01-01||+0||1.8458580||1.8458580||1.845858s + 0.0011232×Δ(1962-01-01)||-130×10-10|
|1963-11-01||+0.100||2.5972788||2.6972788||1.945858s + 0.0011232×Δ( " )||-130×10-10|
|1964-01-01||+0||2.7657940||2.7657940||3.240130s + 0.0012960×Δ(1965-01-01)||-150×10-10|
|1964-04-01||+0.100||2.8837300||2.9837300||3.340130s + 0.0012960×Δ( " )||-150×10-10|
|1964-09-01||+0.100||3.1820180||3.2820180||3.440130s + 0.0012960×Δ( " )||-150×10-10|
|1965-01-01||+0.100||3.4401300||3.5401300||3.540130s + 0.0012960×Δ( " )||-150×10-10|
|1965-03-01||+0.100||3.6165940||3.7165940||3.640130s + 0.0012960×Δ( " )||-150×10-10|
|1965-07-01||+0.100||3.8747060||3.9747060||3.740130s + 0.0012960×Δ( " )||-150×10-10|
|1965-09-01||+0.100||4.0550580||4.1550580||3.840130s + 0.0012960×Δ( " )||-150×10-10|
|1966-01-01||+0||4.3131700||4.3131700||4.313170s + 0.0025920×Δ(1966-01-01)||-300×10-10|
|1968-02-01||–0.100||6.2856820||6.1856820||4.213170s + 0.0025920×Δ( " )||-300×10-10|
– Daan Leijen, 2016.
Leap second adjustments. For an instant
TAI-offset = offset + (delta * days(i - delta-start)).
A leap second table describes when UTC leap seconds occur.
Automatically generated. Retrieves the
expirestd/time/utc/expire: (leaps-table) -> instant constructor field of the
leaps-tablestd/time/utc/leaps-table: V type.
Default TI leaps table has leap second information up to the compiler release (currently
NTP time scale is equal to the UTC time scale (
with a 1900-01-01Z epoch.
Unix time scale based on Unix seconds but with 1970-01-01 epoch.
Create a new UTC-SLS time scale from a provided leap second table
Implements a UTC time scale except without ever showing leap seconds.
UTC-SLS is equivalent to UTC except for the last 1000 seconds of a day where
a leap second occurs. On such day, the leap second time step (positive or negative)
is distributed over the last 1000 seconds of the day. On the full hour, UTC
and UTC-SLS are equal again.
This is a recommended time scale to use for time stamps or communication with other services since it avoids any potential trouble with leap seconds while still being quite precise. See also: https://www.cl.cam.ac.uk/~mgk25/time/utc-sls.
You can create a UTC-SLS time scale based on the latest IETF leap second data using
Get a list of leap second steps in a triple, UTC time, offset just before, and the new offset at that time, the base offset, the delta start date and the delta rate.
Parse a leap second table from text
leaps in the IETF leap second
file format. Returns a full leap second table extended with historical leap second adjustments from before 1972.
The TI (International Time) time scale with a 2000-01-01Z UTC epoch. This is the default time scale used in this library. It was a proposed time scale at the 2004 ITU-R meeting as a replacement of UTC without future leap seconds. In this library, we define TI to match exactly UTC up to the compiler release date (currently 2017) but ignore any possible future leap seconds after that date. This is the preferred time scale in this library as it guarantees deterministic time calculations for any future date, i.e. before 2017-01-01Z, TI == UTC, while after that, TI == TAI - 37s.
TI time scale with smoothed leap seconds.
Implements a TI time scale (
ts-tistd/time/utc/ts-ti: timescale) except without ever showing leap seconds.
TI-SLS is equivalent to TI except for the last 1000 seconds of a day where
a leap second occurs. On such day, the leap second time step (positive or negative)
is distributed over the last 1000 seconds of the day. On the full hour, TI
and TI-SLS are equal again.
Given a Unix time stamp in (fractional) seconds (
secs) and an optional separate
fraction of seconds
frac (for increased precision for nanosecond timestamps), return an
secs + frac seconds after the Unix epoch (
Unfortunately, Unix time stamps are ambigious.
secs are interpreted as:
val daysstd/time/duration/days: (n : int) -> duration = secs / 86400 and
val dsecs = secs % 86400,
daysstd/time/duration/days: (n : int) -> duration is the number of days since
dsecs is the SI seconds into
the day. This means that one cannot represent a possible extra leap second since it
will look as the first second of the next day. For example, here is how the time stamps look
around the leap second of
> instant(1972,12,31,23,59,59).unix-timestamp 94694399.0 > instant(1972,12,31,23,59,60).unix-timestamp 94694400.0 > instant(1973,1,1).unix-timestamp 94694400.0
Internally, this library uses proper
timestampstd/time/timestamp/timestamp: Vs that can keep track of leap seconds.
To indicate a time in a leap second, you can use a fraction
frac that is larger than
1.0. For example:
> unix-instant(94694399.0).time 1972-12-31T23:59:59Z > unix-instant(94694399.0,1.0).time 1972-12-31T23:59:60Z > unix-instant(94694400.0).time 1973-01-01T00:00:00Z
This works well for systems that support
Get a standard Unix timestamp in fractional seconds since the Unix epoch (1970-01-01Z).
Create a new time scale based on UTC seconds with a given
namestd/time/instant/name: (timescale : timescale) -> string and a
timestamp0std/time/timestamp/timestamp0: timestamp) which is the timestamp of the 2001-01-01 date in that timescale
e.g. for a timescale
y2k-epoch = instant-at(2000,1,1,cal=iso-calender(ts)).timestamp(ts).