The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

    Time::TAI::Simple - High resolution UNIX epoch time without leapseconds

VERSION

    1.16

SYNOPSIS

    use Time::TAI::Simple;  # imports tai, tai10, and tai35

    # simple and fast procedural interface:

    $seconds_since_epoch = tai();
    $since_epoch_minus_ten = tai10();  # Probably what you want!
    $close_to_utc_time_for_now = tai35();

    # You can likely skip the rest of this synopsis.

    # object-oriented interface:

    $tai = Time::TAI::Simple->new();

    $since_epoch_minus_ten = $tai->time();

    # download a more up-to-date leapsecond list, and recalculate time base:

    $tai->download_leapseconds() or die("cannot download leapseconds file");
    $tai->load_leapseconds();
    $tai->calculate_base();
    $since_epoch_minus_ten = $tai->time();

    # .. or simply download the leapsecond list as part of instantiation.
    # There is also an option for specifying where to put/find the list:

    $tai = Time::TAI::Simple->new(
        download_leapseconds => 1,
        leapseconds_pathname => '/etc/leap-seconds.list'
        );
    $since_epoch_minus_ten = $tai->time();

    # use mode parameter for TAI-00 time or TAI-35 time:

    $tai00 = Time::TAI::Simple->new(mode => 'tai');
    $seconds_since_epoch = $tai00->time();

    $tai35 = Time::TAI::Simple->new(mode => 'tai35');
    $close_to_utc_time_for_now = $tai35->time();

    # reduce processing overhead of instantiation, at the expense of
    # some precision, by turning off fine-tuning step:

    $tai = Time::TAI::Simple->new(fine_tune => 0);
    $nowish = $tai->time();  # accurate to a few milliseconds, not microseconds.

DESCRIPTION

The Time::TAI::Simple module provides a very simple way to obtain the number of seconds elapsed since the beginning of the UNIX epoch (January 1st, 1970).

It differs from Time::HiRes in that it returns the actual number of elapsed seconds, unmodified by the leap seconds introduced by the IETF to make UTC time. These leap seconds can be problematic for automation software, as they effectively make the system clock stand still for one second every few years.

D. J. Bernstein describes other problems with leapseconds-adjusted time in this short and sweet article: http://cr.yp.to/proto/utctai.html

Time::TAI::Simple provides a monotonically increasing count of seconds, which means it will never stand still or leap forward or backward due to system clock adjustments (such as from NTP), and avoids leapseconds-related problems in general.

This module differs from Time::TAI and Time::TAI::Now in a few ways:

    * it is much simpler to use,

    * it uses the same epoch as perl's time builtin and Time::HiRes, not the IETF's 1900-based epoch,

    * it is a "best effort" implementation, accurate to a few microseconds,

    * it depends on the local POSIX monotonic clock, not an external atomic clock.

ABOUT TAI, TAI10, TAI35

This module provides three modes of TAI time:

tai is, very simply, the actual number of elapsed seconds since the epoch.

tai10 provides TAI-10 seconds, which is how TAI time has traditionally been most commonly used, because when leapseconds were introduced in 1972, UTC was TAI minus 10 seconds.

It is the type of time provided by Arthur David Olson's popular time library, and by the TAI patch currently proposed to the standard zoneinfo implementation. When most people use TAI time, it is usually TAI-10.

tai35 provides TAI-35 seconds, which makes it exactly equal to the system clock time returned by Time::HiRes::time() before July 1 2015. As the IETF introduces more leapseconds, tai35 will be one second ahead of the system clock time with each introduction.

This mode is provided for use-cases where compatability with other TAI time implementations is not required, and keeping the monotonically increasing time relatively close to the system clock time is desirable.

It was decided to provide three types of TAI time instead of allowing an arbitrary seconds offset parameter to make it easier for different systems with different users and different initialization times to pick compatible time modes.

FURTHER READING

The following reading is recommended:

http://cr.yp.to/proto/utctai.html

http://tycho.usno.navy.mil/leapsec.html

http://leapsecond.com/java/gpsclock.htm

MODULE-SCOPE VARIABLES

Time::TAI::Simple defines a few externally-accessible variables so that users may customize their values to fit their needs, or to use them in other programming projects.

@Time::TAI::Simple::LEAPSECOND_UNIX_PATHNAME_LIST

This list enumerates the pathnames where methods will look for the file listing IETF-defined leapseconds on UNIX systems. The list is traversed in order, and the first readable file will be used.

@Time::TAI::Simple::LEAPSECOND_WINDOWS_PATHNAME_LIST

This list enumerates the pathnames where methods will look for the file listing IETF-defined leapseconds on Windows systems. Like its UNIX counterpart, the list is traversed in order, and the first readable file will be used.

@Time::TAI::Simple::FALLBACK_LEAPSECONDS_LIST

If no leapseconds list file can be found, Time::TAI::Simple falls back on using this hard-coded list of IETF-defined leapseconds.

This is dangerous because if the module is too old to include recently introduced leapseconds, TAI clock objects instantiated after the new leapsecond will be one second ahead of the desired TAI time.

This problem can be avoided by downloading the most recent leapsecond list file, either by invoking the download_leapseconds method or by manually downloading it from https://www.ietf.org/timezones/data/leap-seconds.list and putting it somewhere Time::TAI::Simple will find it, such as /etc/leap-seconds.list or C:\WINDOWS\leap-seconds.list.

@Time::TAI::Simple::FALLBACK_LEAPSECONDS_LIST is a list of arrayrefs, each referenced array consisting of two elements, an IETF timestamp and a time delta.

$Time::TAI::Simple::LEAPSECOND_IETF_DELTA

The IETF represents TAI time as the number of seconds elapsed since 1900-01-01, which is 2208960000 seconds greater than the number of seconds elapsed since 1971-01-01 (the UNIX epoch). Time::TAI::Simple keeps this value in $Time::TAI::Simple::LEAPSECOND_IETF_DELTA and uses it internally to convert IETF times to UNIX epoch times.

$Time::TAI::Simple::TAI_OR

$Time::TAI::Simple::TAI10_OR

$Time::TAI::Simple::TAI35_OR

When using Time::TAI::Simple's procedural interface, the first time the tai, tai10, and tai35 functions are invoked, they instantiate Time::TAI::Simple with the appropriate mode and assign it to these module-scope variables. Subsequent invocations re-use these instants.

Before the first invocation, these variables are undef.

PROCEDURAL INTERFACE

$seconds = tai()

$seconds = tai10()

$seconds = tai35()

These functions return a floating-point number of seconds elapsed since the epoch. They are equivalent to instantiating a $tai object with the corresponding mode and invoking its time method.

EXAMPLE:

    use Time::TAI::Simple;

    my $start_time = tai();
    do_something();
    my $time_delta = tai() - $start_time;
    print "doing something took $time_delta seconds\n";

OBJECT ORIENTED INTERFACE

INSTANTIATION

$tai = Time::TAI::Simple->new(%options)

Instantiates and returns a new Time::TAI::Simple object, hereafter referred to as $tai. Returns undef on irrecoverable error.

Without options, instantiation will:

    * find and load the local copy of the leapseconds file into $tai->{ls_ar} (or load from @Time::TAI::Simple::FALLBACK_LEAPSECONDS_LIST if no local file is found),

    * instantiate a POSIX::RT::Clock object referencing the POSIX monotonic clock and store it in $tai->{tm_or},

    * calculate a value for $tai->{tm_base}, which is the number of seconds to add to the POSIX monotonic clock time to derive the TAI-10 time, and

    * perform a "fine tuning" of this tm_base, based on repeatedly sampling the system clock and estimating the time difference between loading the value of the system clock and loading the value of the monotonic clock.

This behavior can be changed by passing optional parameters:

mode => 'tai'
mode => 'tai10' (default)
mode => 'tai35'

Adjusts $tai->{tm_base} so that $tai->time() returns the TAI, TAI-10, or TAI-35 time.

download_leapseconds => 0 (default)
download_leapseconds => 1

When set, causes new to try to http-download a new leapseconds list file before loading the leapseconds file.

Time::TAI::Simple maintains an internal list of URLs from which to download this file, and it goes down this list sequentially, stopping when the file has been successfully downloaded. This list may be amended via the download_urls option.

By default, no attempt is made to download a leapseconds file. This avoids the potential for very long http timeouts and clobbering any existing administrator-provided leapseconds file.

Time::TAI::Simple-{ua_ar}> is an arrayref to a list of User-Agent strings, and one of these will be picked at random for HTTP queries, stored in Time::TAI::Simple-{ua_str}>.

User-Agent spoofing behavior is subject to the following options which can be passed to new (see the documentation for tai-download-leapseconds for a description of their use):

    agent => <STRING>,
    churn_agent => 1,
    force_edge => 1,

See tai-download-leapseconds also for the download-related options:

    retry => <NUMBER>,
    retry_delay => <SECONDS>
download_urls => [$url1, $url2, ...]

Prepends the provided list of URLs to the list of remove locations from which the leapseconds file is downloaded when the download_leapseconds option is set. Use this if your administrator maintains a leapseconds file for organizational use.

leapseconds_pathname => '/home/tai/leap-seconds.list'

Sets the pathname of the leapseconds list file. This is the pathname to which the file will be stored when downloaded via the download_leapseconds option or download_leapseconds method, and it is the pathname from which the file will be loaded by the load_leapseconds method.

By default, Time::TAI::Simple will look for this file in several locations, specified in @Time::TAI::Simple::LEAPSECOND_UNIX_PATHNAME_LIST and @Time::TAI::Simple::LEAPSECOND_WINDOWS_PATHNAME_LIST. The user may opt to replace the contents of these list variables as an alternative to using the leapseconds_pathname option (for instance, before invoking the tai, tai10, tai35 functions).

do_not_load_leapseconds => 0 (default)
do_not_load_leapseconds => 1

When set, prevents loading the timestamp list from the timestamp list file or @Time::TAI::Simple::FALLBACK_LEAPSECONDS_LIST into $tai->{ls_ar}.

This only makes sense when setting the base_time option or when populating $tai->{ls_ar} manually after instantiation and subsequently re-running the calculate_base method.

base_time => $seconds

When set, circumvents the normal process of calculating $tai->{tm_base} and uses the provided value instead. This should be the number of seconds added to the time obtained from the POSIX monotonic clock to get the TAI time returned by the time method.

fine_tune => 0
fine_tune => 1 (default)

When set (the default), adjusts tm_base, based on repeatedly sampling the system clock and estimating the time difference between loading the value of the system clock and loading the value of the monotonic clock. This can add measurable overhead to the calculate_base method -- about 35 microseconds on 2013-era hardware, accounting for about 3/4 of instantiation time.

When false, skips this fine-tuning, diminishing the precision of the time method from a few microseconds to a few milliseconds.

OBJECT ATTRIBUTES

The following attributes of a Time::TAI::Simple instance are public. Changes to some attributes will do nothing until the load_leapseconds and/or calculate_base methods are re-run.

opt_hr (hash reference)

Refers to the parameters passed to new.

tm_or (POSIX::RT::Clock object reference)

Refers to the POSIX standard monotonic clock interface used by time to calculate the current TAI time (along with tm_base).

ls_ar (array reference)

Refers to the IETF leapseconds list. Its elements are arrayrefs to [UTC epoch, seconds] tuples, and they are ordered by UTC epoch.

ls_tm (integer)

Value is the file modification time of the IETF leapseconds list file, if ls_ar was loaded from a file, or the time ls_ar was loaded from @Time::TAI::Simple::FALLBACK_LEAPSECONDS_LIST, or 0 if never loaded.

dl_tm (floating point)

Value is the system clock time the download_leapseconds method last attempted to download the IETF leapseconds list file, or 0.0 if never attempted.

tm_base (floating point)

Value is the difference, in seconds, between the POSIX monotonic clock time and the beginning of the epoch. It is used by time to calculate the current TAI time. It is initialized by the calculate_base method, and is 0.0 if never initialized.

mode (string)

Exactly one of "tai", "tai10", "tai35", indicating the mode with which the object was instantiated, and thus the type of TAI time returned by time. Its default value is "tai10".

OBJECT METHODS

$tai->time()

Returns a floating-point number of seconds elapsed since the epoch.

$tai->calculate_base(%options)

calculate_base uses the POSIX monotonic clock, the leapsecond list, and the system clock to calculate $tai->{tm_base}, which is the difference between the POSIX monotonic clock and the TAI time. This difference is used by time to calculate the TAI time from the POSIX monotonic clock time.

This method is normally only called by new, but can be called explicitly to recalculate $tai->{tm_base} if one of its dependencies is changed.

It takes some of the same options as new, and they have the same effect:

base_time => $seconds
fine_tune => 0 or 1

It has no return value.

$tai->load_leapseconds(%options)

load_leapseconds finds the local copy of the IETF leapseconds list file, reads it, and populates the object's ls_ar attribute. If it cannot find any file it uses the values in @Time::TAI::Simple::FALLBACK_LEAPSECONDS_LIST instead.

This method, too, is normally only called by new, but can be called explicitly as needed to re-initialize $tai->{ls_ar}.

For now it takes only one option, which has the same effect as passing it to <new>:

leapseconds_pathname => "/home/tai/leap-seconds.list"

It returns 1 on success, 0 on failure.

$tai->download_leapseconds(%options)

download_leapseconds tries to download the IETF leapseconds file so it can be loaded by the load_leapseconds method. It iterates through a list of URLs (any provided via the leapseconds_pathname parameter first, and an internal list after) and saves the first file it is able to download to either the pathname specified by the leapseconds_pathname parameter or a sensible location appropriate to the operating system type.

This method can be called by new, but only when the download_leapseconds parameter is passed to new with a value which resolves to true.

It takes two options, which have the same effects as passing them to new:

download_urls => [$url1, $url2, ...]
leapseconds_pathname => "/home/tai/leap-seconds.list"

It returns 1 on success, 0 on failure.

EXAMPLES

Some simple scripts wrapping this module can be found in bin:

tai-download-leapseconds

Attempts to download the IETF leapseconds file. Will write the pathname of the downloaded file to STDOUT and exit 0, or write an error to STDERR and exit 1. Pass it the -h option to see its options.

On UNIX hosts, it is recommended that a symlink be made in /etc/cron.monthly to /usr/local/bin/tai-download-leapseconds so that it updates the system's leapseconds file as updates become available.

tai

Prints the current time. Shows TAI-10 by default. Pass it the -h option to see its options.

TODO

Needs support for negative leapseconds.

Needs support for Linux's POSIX CLOCK_TAI, when available, now that that's properly exposed in recent Linux releases. Currently blocked on POSIX::RT::Timer but if an update is too long in the coming I may just roll my own.

Needs more unit tests.

Does new need changes to be made thread-safe?

Test _fine_tune under other versions of perl, find out if the constant factor needs to be version-specific.

Do something smart with ls_tm and dl_tm, like an optional feature which tries to refresh the leapsecond list periodically when stale.

THREADS

Not tested, but its dependencies are purportedly thread-safe, and I think the time method, and the tai, tai10, and tai35 functions should be thread-safe. Not so sure about new.

BUGS

Probably. In particular, the Windows compatability code is not tested, nor do I have access to a Windows environment in which to test it. I doubt that the paths in @Time::TAI::Simple::LEAPSECOND_WINDOWS_PATHNAME_LIST are sufficient for all environments.

Also, some corners were cut in bin/tai, particularly in the --iso code, which means its output will not be precisely correct for locales with timezones whose time offsets are not whole hours.

Please report relevant bugs to <ttk[at]ciar[dot]org>.

Bugfix patches are also welcome.

SEE ALSO

DateTime has a subtract_datetime_absolute method which will give the actual difference between two times, just like taking the difference between two TAI times.

If you are a scientist, you might want Time::TAI or Time::TAI::Now.

An alternative approach to solving the problem of leapsecond-induced bugs is Time::UTC_SLS, "UTC with Smoothed Leap Seconds".

AUTHOR

TTK Ciar, <ttk[at]ciar[dot]org>

COPYRIGHT AND LICENSE

Copyright 2014-2022 by TTK Ciar

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.