doc/kernel/timeutil.rst
.. _timeutil_api:
Time Utilities ##############
Overview
:ref:kernel_timing_uptime in Zephyr is based on the a tick counter. With
the default :kconfig:option:CONFIG_TICKLESS_KERNEL this counter advances at a
nominally constant rate from zero at the instant the system started. The POSIX
equivalent to this counter is something like CLOCK_MONOTONIC or, in Linux,
CLOCK_MONOTONIC_RAW. :c:func:k_uptime_get() provides a millisecond
representation of this time.
Applications often need to correlate the Zephyr internal time with external
time scales used in daily life, such as local time or Coordinated Universal
Time. These systems interpret time in different ways and may have
discontinuities due to leap seconds <https://what-if.xkcd.com/26/>__ and
local time offsets like daylight saving time.
Because of these discontinuities, as well as significant inaccuracies in the clocks underlying the cycle counter, the offset between time estimated from the Zephyr clock and the actual time in a "real" civil time scale is not constant and can vary widely over the runtime of a Zephyr application.
The time utilities API supports:
converting between time representations <timeutil_repr>synchronizing and aligning time scales <timeutil_sync>comparing, adding, and subtracting representations <timeutil_manip>For terminology and concepts that support these functions see
:ref:timeutil_concepts.
Time Utility APIs
.. _timeutil_repr:
Time scale instants can be represented in multiple ways including:
Seconds since an epoch. POSIX representations of time in this form include
time_t and struct timespec, which are generally interpreted as a
representation of "UNIX Time" (see :rfc:8536#section-2).
Calendar time as a year, month, day, hour, minutes, and seconds relative to
an epoch. POSIX representations of time in this form include struct tm.
Keep in mind that these are simply time representations that must be interpreted relative to a time scale which may be local time, UTC, or some other continuous or discontinuous scale.
Some necessary transformations are available in standard C library
routines. For example, time_t measuring seconds since the POSIX EPOCH is
converted to struct tm representing calendar time with gmtime() <https://pubs.opengroup.org/onlinepubs/9699919799/functions/gmtime.html>__.
Sub-second timestamps like struct timespec can also use this to produce
the calendar time representation and deal with sub-second offsets separately.
The inverse transformation is not standardized: APIs like mktime() expect
information about time zones. Zephyr provides this transformation with
:c:func:timeutil_timegm and :c:func:timeutil_timegm64.
To convert between struct timespec and k_timeout_t durations,
use :c:func:timespec_to_timeout and :c:func:timespec_from_timeout.
.. code-block:: c
k_timeout_t to;
struct timespec ts;
timespec_from_timeout(K_FOREVER, &ts);
to = timespec_to_timeout(&ts); /* to == K_FOREVER */
timespec_from_timeout(K_MSEC(100), &ts);
to = timespec_to_timeout(&ts); /* to == K_MSEC(100) */
.. doxygengroup:: timeutil_repr_apis
.. _timeutil_sync:
There are several factors that affect synchronizing time scales:
CONFIG_SYS_CLOCK_TICKS_PER_SEC Hertz, while an external time
source may provide data in whole or fractional seconds (e.g. microseconds).Synchronization or alignment between time scales is done with a multi-step process:
timeutil_sync_config records the nominal rates of a reference
time scale/source (e.g. TAI) and a local time source
(e.g. :c:func:k_uptime_ticks).timeutil_sync_instant records the representation of a single
instant in both the reference and local time scales.timeutil_sync_state provides storage for an initial instant, a
recently received second observation, and a skew that can adjust for
relative errors in the actual rate of each time scale.timeutil_sync_ref_from_local() and
:c:func:timeutil_sync_local_from_ref() convert instants in one time scale
to another taking into account skew that can be estimated from the two
instances stored in the state structure by
:c:func:timeutil_sync_estimate_skew... doxygengroup:: timeutil_sync_apis
.. _timeutil_manip:
timespec ManipulationChecking the validity of a timespec can be done with :c:func:timespec_is_valid.
.. code-block:: c
struct timespec ts = {
.tv_sec = 0,
.tv_nsec = -1, /* out of range! */
};
if (!timespec_is_valid(&ts)) {
/* error-handing code */
}
In some cases, invalid timespec objects may be re-normalized using
:c:func:timespec_normalize.
.. code-block:: c
if (!timespec_normalize(&ts)) {
/* error-handling code */
}
/* ts should be normalized */
__ASSERT(timespec_is_valid(&ts) == true, "expected normalized timespec");
It is possible to compare two timespec objects for equality using :c:func:timespec_equal.
.. code-block:: c
if (timespec_equal(then, now)) {
/* time is up! */
}
It is possible to compare and fully order (valid) timespec objects using
:c:func:timespec_compare.
.. code-block:: c
int cmp = timespec_compare(a, b);
switch (cmp) {
case 0:
/* a == b */
break;
case -1:
/* a < b */
break;
case +1:
/* a > b */
break;
}
It is possible to add, subtract, and negate timespec objects using
:c:func:timespec_add, :c:func:timespec_sub, and :c:func:timespec_negate,
respectively. Like :c:func:timespec_normalize, these functions will output
a normalized timespec when doing so would not result in overflow.
On success, these functions return true. If overflow would occur, the
functions return false.
.. code-block:: c
/* a += b */
if (!timespec_add(&a, &b)) {
/* overflow */
}
/* a -= b */
if (!timespec_sub(&a, &b)) {
/* overflow */
}
/* a = -a */
if (!timespec_negate(&a)) {
/* overflow */
}
.. doxygengroup:: timeutil_timespec_apis
.. _timeutil_concepts:
Concepts Underlying Time Support in Zephyr
Terms from ISO/TC 154/WG 5 N0038 <https://www.loc.gov/standards/datetime/iso-tc154-wg5_n0038_iso_wd_8601-1_2016-02-16.pdf>__
(ISO/WD 8601-1) and elsewhere:
Civil time <https://en.wikipedia.org/wiki/Civil_time>__ generally refers
to time scales that legally defined by civil authorities, like local
governments, often to align local midnight to solar time.International Atomic Time <https://en.wikipedia.org/wiki/International_Atomic_Time>__ (TAI) is a time
scale based on averaging clocks that count in SI seconds. TAI is a monotonic
and continuous time scale.
Universal Time <https://en.wikipedia.org/wiki/Universal_Time>__ (UT) is a
time scale based on Earth’s rotation. UT is a discontinuous time scale as it
requires occasional adjustments (leap seconds <https://en.wikipedia.org/wiki/Leap_second>) to maintain alignment to
changes in Earth’s rotation. Thus the difference between TAI and UT varies
over time. There are several variants of UT, with UTC <https://en.wikipedia.org/wiki/Coordinated_Universal_Time> being the most
common.
UT times are independent of location. UT is the basis for Standard Time (or "local time") which is the time at a particular location. Standard time has a fixed offset from UT at any given instant, primarily influenced by longitude, but the offset may be adjusted ("daylight saving time") to align standard time to the local solar time. In a sense local time is "more discontinuous" than UT.
POSIX Time (see :rfc:8536#section-2) is a time scale
that counts seconds since the "POSIX epoch" at 1970-01-01T00:00:00Z (i.e. the
start of 1970 UTC). UNIX Time is an extension of POSIX
time using negative values to represent times before the POSIX epoch. Both of
these scales assume that every day has exactly 86400 seconds. In normal use
instants in these scales correspond to times in the UTC scale, so they inherit
the discontinuity.
The continuous analogue is UNIX Leap Time which is UNIX time plus all leap-second corrections added after the POSIX epoch (when TAI-UTC was 8 s).
A positive leap second was introduced at the end of 2016, increasing the difference between TAI and UTC from 36 seconds to 37 seconds. There was no leap second introduced at the end of 1999, when the difference between TAI and UTC was only 32 seconds. The following table shows relevant civil and epoch times in several scales:
==================== ========== =================== ======= ============== UTC Date UNIX time TAI Date TAI-UTC UNIX Leap Time ==================== ========== =================== ======= ============== 1970-01-01T00:00:00Z 0 1970-01-01T00:00:08 +8 0 1999-12-31T23:59:28Z 946684768 2000-01-01T00:00:00 +32 946684792 1999-12-31T23:59:59Z 946684799 2000-01-01T00:00:31 +32 946684823 2000-01-01T00:00:00Z 946684800 2000-01-01T00:00:32 +32 946684824 2016-12-31T23:59:59Z 1483228799 2017-01-01T00:00:35 +36 1483228827 2016-12-31T23:59:60Z undefined 2017-01-01T00:00:36 +36 1483228828 2017-01-01T00:00:00Z 1483228800 2017-01-01T00:00:37 +37 1483228829 ==================== ========== =================== ======= ==============
The Zephyr tick counter has no concept of leap seconds or standard time offsets and is a continuous time scale. However it can be relatively inaccurate, with drifts as much as three minutes per hour (assuming an RC timer with 5% tolerance).
There are two stages required to support conversion between Zephyr time and common human time scales:
The API around :c:func:timeutil_sync_state_update() supports the first step
of converting between continuous time scales.
The second step requires external information including schedules of leap seconds and local time offset changes. This may be best provided by an external library, and is not currently part of the time utility APIs.
If an application requires civil time accuracy within several seconds then UTC could be used as the stable time source. However, if the external source adjusts to a leap second there will be a discontinuity: the elapsed time between two observations taken at 1 Hz is not equal to the numeric difference between their timestamps.
For precise activities a continuous scale that is independent of local and solar adjustments simplifies things considerably. Suitable continuous scales include:
Because C and Zephyr library functions support conversion between integral and calendar time representations using the UNIX epoch, UNIX Leap Time is an ideal choice for the external time scale.
The mechanism used to populate synchronization points is not relevant: it may involve reading from a local high-precision RTC peripheral, exchanging packets over a network using a protocol like NTP or PTP, or processing NMEA messages received a GPS with or without a 1pps signal.
timespec ConceptsOriginally from POSIX, struct timespec has been a part of the C standard
since C11. The definition of struct timespec is as shown below.
.. code-block:: c
struct timespec { time_t tv_sec; /* seconds / long tv_nsec; / nanoseconds */ };
The tv_nsec field is only valid with values in the range [0, 999999999]. The
tv_sec field is the number of seconds since the epoch. If struct timespec is
used to express a difference, the tv_sec field may fall into a negative range.