Modernized <time.h> for ISO C

Markus Kuhn

Warning: Section 1 attempts to mimic the style of a chapter of the ISO C standard and thus is a bit dry on first reading without background information. For more tutorial information, start with the rationale in section 2 and the quoted references in section 3.

1 Specification

1.1 Components of time

The header <time.h> provides in addition to the facilities specified in ISO C 89 the following new features:

The macros defined are

  TIME_UTC
  TIME_TAI
  TIME_MONOTONIC
  TIME_PROCESS
  TIME_THREAD
  TIME_LOCAL
  TIME_SYNC
  TIME_RESOLUTION

all of which expand to an integral constant expression of type int and all of which should have disjoint bits set to one.

The types declared are

  struct xtime

which represents a point on some time scale or a duration in time and

  timezone_t

which holds information related to a timezone. This information can for instance include data related to a geographical region regarding its historic or planned timezone and daylight-saving time changes or an algorithmic description of rules for such changes, all of which can be used to convert between a struct xtime numeric time and some local-time and calendar-date representation of this time.

The xtime structure shall contain at least the following members:

  int_fast64_t sec;
  int_fast32_t nsec;

The types are those defined in <stdint.h>, but these type names and the other type names from <stdint.h> are not included automatically by <time.h>.

The xtime structure describes a point in time relative to some reference point, which is referred to as the epoch. The member sec identifies the one second long time interval that starts sec seconds after the epoch. Negative sec values refer to second intervals before the epoch. The member nsec contains the number of completed nanoseconds from the start of the second interval identified by sec to the described point in time. The nsec value is always in the range 0 to 999_999_999, except during an inserted leap second, which extends the second interval identified by sec to two seconds, and where nsec is in the range 1_000_000_000 to 1_999_999_999 if the described point in time falls within an inserted leap second.

Note: Coordinated Universal Time (UTC), the modern version of Greenwich Mean Time (GMT), is the international atomic-clock time scale on which almost all time services and official local-time definitions are based. Leap seconds are occasionally inserted into UTC as an additional second 23:59:60 at the end of a UTC month in order to keep UTC and UT1 (another timescale defined by the rotation of the earth) from drifting apart by more than 900 ms. Leap seconds are simultaneously inserted into all local times derived from UTC. UTC and leap seconds are defined in ITU-R Recommendation TF.460-4.

The types time_t, clock_t, and all related functions and macros could be declared deprecated, but are still required in the form defined in ISO C 89/99 for backward compatibility with existing applications.

1.2 Time manipulation functions

1.2.1 The xtime_get function

Synopsis

  int xtime_get(struct xtime *xtp, int clock_type);

Description

The xtime_get function determines the current time and writes it into *xtp. Several types of time are provided, and the value of clock_type selects among them. The following clock_type values are defined by this standard:

TIME_UTC
The epoch for this clock is 1970-01-01 00:00:00 in Coordinated Universal Time (UTC). Since UTC was not defined in its current form before 1972, we define this epoch such that on precisely 1972-01-01 00:00:00 UTC the xtp->sec value jumps to 2*365*86400 and xtp->nsec jumps to 0. From then on, at the start of every normal UTC second, xtp->sec increases by one and xtp->nsec starts to count from 0 to 999_999_999 during the following second. UTC leap seconds are handled in a special way: At the start of an inserted leap second (23:59:60), xtp->sec does not increase and remains at the same value as for the previous second (23:59:59), while xtp->nsec continues to count from 1_000_000_000 to 1_999_999_999 during the leap second. After a deleted leap second (at the jump from 23:59:58.999... to 00:00:00), xtp->sec increases by two and xtp->nsec starts to count normally from 0. In other words, xtp->sec always contains the number of non-leap seconds since the start of 1970, as if the insertion or deletion of leap seconds in UTC never had happened.
TIME_TAI
The epoch for this clock is 1970-01-01 00:00:00 in International Atomic Time (TAI), which means that on precisely 1972-01-01 00:00:00 UTC the xtp->sec value increases to 2*365*86400+10 and xtp->nsec jumps to 0. From then on, at the start of every TAI/UTC second, including UTC leap seconds, xtp->sec increases by one and xtp->nsec starts to count from 0 to 999_999_999 during the following second. In other words, xtp->sec always contains the number of completed SI seconds since the start of 1970 in TAI, that is it counts all inserted and does not count any deleted UTC leap seconds.
TIME_MONOTONIC
The epoch for this clock is at some time before the first C program is started on this computer following a system reset. From then on, at the start of every further second, xtp->sec increases by one and xtp->nsec counts from 0 to 999_999_999 during the following second. The implementation shall make every effort to ensure that the time between a number of xtp->sec increments is as close as possible to the duration of the same number of SI seconds. This means that leap seconds should not affect this clock, nor should manual resets or adjustments of a system-wide UTC, TAI, or local-time clock affect it. The implementation is allowed to adjust the duration of the second of this clock to match the duration of an SI second more closely once it has learnt the frequency error of its local oscillator by comparing it with an external reference time signal. With this clock type, xtp->sec shall never be negative and xtp->nsec shall always be in the range 0 to 999_999_999.
TIME_PROCESS
The epoch for this clock is at some time during the generation of the current process. From then on, xtp->sec and xtp->nsec provide a best effort indication of how many seconds plus how many nanoseconds all threads of the current process have been utilizing the processor so far. With this clock type, xtp->sec shall never be negative and xtp->nsec shall always be in the range 0 to 999_999_999.
TIME_THREAD
This clock is like TIME_PROCESS, but processor utilization is determined per thread for the current thread of execution, and not per process.

If any of the above clock_type values is combined with a bit-wise or operator with TIME_RESOLUTION before being passed to the function, then the value written into *xtp will be a time interval that describes the resolution of this clock and not the current value of this clock.

Note: The implementation is only required to provide its best-effort estimate for the value of any of the above clocks. Precise UTC and TAI representation and correct leap-second treatment will usually only be possible with a connection to an external reference clock that provides for instance data on the current UTC and TAI-UTC values plus a leap second announcement. On systems without such a connection, the implementation should provide the best available estimate for UTC. The implementation of TIME_TAI is optional and the majority of systems is not expected to implement TIME_TAI, because TAI data is not part of many time signal services and stored TAI-UTC tables that allow to convert the widely available UTC signals into TAI would have to be updated roughly every six months with every new IERS Bulletin C mailing. Implementations are allowed to offer additional non-standard clock types (for instance TIME_UT1 for the current UTC+DUT1 time, which is broadcast by some time services), and implementations are allowed to offer additional clock property macros to request more information about the clock than just the resolution.

Returns

On success, if the requested clock type was available, the function shall return a value r with (r & clock_type) == clock_type. If xtp != NULL then the value of the requested clock shall be stored in *xtp.

If the requested clock type was not available or the requested clock type was not known, the function shall return a value r with (r & clock_type) == 0 and *xtp will be undefined.

If the requested clock type was available and was TIME_UTC or TIME_TAI, and if the system was recently in contact with an external reference clock and can assure with high certainty that the provided time is still correct within one second, then a value r with (r & (clock_type | TIME_SYNC)) == (clock_type | TIME_SYNC) should be returned.

If the requested clock type was not available and the implementation knows only some local time in some unspecified time zone, then the function shall return a value r with (r & (clock_type | TIME_LOCAL)) == TIME_LOCAL and if xtp != NULL then this local-time value shall be written into *xtp in a way such that the epoch is 1970-01-01 00:00:00 in this local time, not counting during any inserted and counting for all deleted time intervals for timezone adjustments or leap seconds in this local time.

1.2.2 The xtime_delay function

Synopsis

  void xtime_delay(long sec, long nsec);

Description

The function shall wait for at least sec + nsec / 109 seconds on the TIME_MONOTONIC clock. The function shall return immediately if this value is not positive.

Note: Signal handlers can be called while this function is waiting, but it will not return prematurely due to the arrival of a signal. The POSIX.1 function nanosleep provides a similar service that is interruptible by signals.

1.3 Timezone manipulation functions

A timezone in this context is the information necessary to convert between a struct xtime UTC value and a struct tm broken-down or string-formatted local time. This information can include not only a fixed time offset in minutes between UTC and the local time, but also algorithms that determine how this offset varies during the year due to daylight-saving times regulations and even tables that determine how the offset and the daylight-saving time regulations change over the years. The following functions convert a timezone description that is provided as a text string into an in-memory representation of the timezone. Since timezone information can include tables of variable length, dynamic memory allocation and deallocation functions are provided.

1.3.1 The tz_prep function

Synopsis

  int tz_prep(timezone_t **tz, const char * restrict tzstring);

Description

Allocate memory for and create the in-memory representation of the timezone specified in tzstring. This standard does not specify the syntax of the timezone description in tzstring. If tzstring == NULL, then some externally defined default timezone shall be used.

Returns

On success, the function shall set *tz to the address of the allocated timezone_t data structure and shall return 0. If there was a failure during memory allocation, the function shall set *tz == NULL and shall return -1. If there was a problem with interpreting tzstring, the function shall set *tz to the address of the allocated timezone_t data structure, shall then write into **tz further information about the cause of the problem for evaluation by tz_error, and shall return 1.

Implementation Advice: The supported tzstring values can be full algorithmic descriptions of the timezone, for instance in the TZ format defined in ISO/IEC 9945-1:1996 (POSIX.1) section 8.1.1, such as "CET-1CEST,M3.5.0/2,M10.5.0/3" for Central Europe in 1999. They can also be names of geographic locations or timezones, which are then translated by a configuration database lookup into a detailed description. A possible convention is for example to name a region with common timezone rules after the most populated area in this region, such as "Europe/Paris". If tzstring == NULL is specified, the default timezone can be determined, for instance, using environment variable TZ or, failing that, using a system-wide configuration file.

1.3.2 The tz_error function

Synopsis

  char *tz_error(timezone_t *tz);

Description

After tz_prep has signalled an error by returning another value than 0, this function can be used to generate a readable error message about the cause of the problem by looking at the timezone_t value allocated by tz_prep. If tz == NULL or if no error has occurred, then this shall also be indicated by an appropriate message. The language used in the message should depend on the locale. The implementation is free to arbitrarily select between the locale that was active at the time of the call to tz_prep or that active when this function is called.

Returns

The function returns a pointer to a zero-terminated text string that contains a message. This text string is usually not accessible any more for the application after the next call to tz_error or tz_free with the same tz value. Calls to any of these functions with other tz values generated by tz_prep shall not affect this text string (multi-threading safety).

Implementation Advice: There are several ways in which this function can be implemented in a multi-threading safe way. One is to let tz_prep generate the text message and store it somewhere in **tz, such that tz_error just has to return a pointer into this timezone_t value. Another is that tz_error determines the error cause by examining data found in the timezone_t value, and allocating space for the string on the heap. The pointer to this string would then not only be returned by tz_error but would also have to be stored in the timezone_t value such that tz_free can deallocate it again. A conforming portable application should not change the locale between calling tz_prep and tz_error.

1.3.3 The tz_free function

Synopsis

  void tz_free(timezone_t *tz);

Description

This function deallocates the timezone_t data structure that was allocated before by a tz_prep call which returned with a non-negative value. The function shall perform no action if tz == NULL.

1.3.4 The tz_jump function

Synopsis

  long tz_jump(timezone_t *tz, struct xtime *xtp, int forward);

Description

This function allows an application to find all non-continuities in a broken-down time representation defined by *tz about which the implementation is aware. Such non-continuities are all inserted or deleted time intervals that are not predictable by a continuously running 24h-clock and the normal Gregorian calendar. This includes leap seconds and daylight-saving time start and end, perhaps even calendar exceptions. If tz == NULL, then only UTC leap seconds shall be indicated. The value *xtp shall be interpreted on the TIME_UTC clock and defines the point in time where the search for the next non-continuity starts. A non-continuity on this start point shall be ignored in the search. After the function returns, *xtp shall contain the point in time where the non-continuity begins if one had been found. If forward != 0 then the function shall search for the next non-continuity after *xtp. If forward == 0, then the first non-continuity before *xtp shall be searched.

Returns

A return value of zero indicates that the implementation is not aware of any non-continuity in the requested search direction and *xtp will remain unmodified.

A positive return value r indicates that a time interval of r seconds is inserted into the time scale starting at the time *xtp in the timezone *tz. Unless the non-continuity is just a leap second, this means that the clocks in this timezone are turned back r seconds at *xtp and that the broken-down time representations during the r seconds that follow *xtp are a repetition of the r seconds that preceded *xtp. For example in the case of an inserted leap second, the function will return 1 at the point in time that corresponds to the start of the leap second (23:59:60 UTC) and xtp->nsec == 1_000_000_000. In the case of a switch from summer time back to winter time, the function will return 3600 if the difference is one hour, and *xtp will be set to the start of the repeated hour, that is to the start of winter time.

A negative return value r indicates that a time interval of r seconds is deleted from the time scale at the time *xtp in the timezone *tz. This means that the clocks in this timezone are turned forward r seconds when the time *xtp is reached. For example in the case of a deleted leap second, the function will return -1 and write into *xtp the point in time that corresponds to the start of the second after the deleted leap second (00:00:00 UTC). In the case of a switch from winter time to summer time, the function will return -3600 if the difference is one hour, and *xtp will be set to the start of summer time.

1.4 Time conversion functions

1.4.1 The xtime_make function

Synopsis

  int xtime_make(struct xtime *xtp,
                 const struct tm *tmptr,
                 const timezone_t *tz);

Description

This function interprets the broken-down time in *tmptr as a local time in the timezone specified in *tz and writes the corresponding value of the TIME_UTC clock type into *xtp. If tz == NULL then the *tmptr values are interpreted in UTC on the Gregorian calendar. Since struct tm provides only second resolution, the function sets xtp->nsec = 0 or in case tmptr->tm_sec == 60 then it sets xtp->nsec = 1_000_000_000.

Returns

On success the function shall write the requested time into *xtp if xtp != NULL and shall return 0. If the conversion is not possible because the values in *tmptr did not correspond to a valid broken-down time in the specified timezone, then the function shall return -1 and *xtp will be undefined.

Note: Example implementation (incomplete)

1.4.2 The xtime_breakup function

Synopsis


  int xtime_breakup(struct tm *tmptr,
                    const struct xtime *xtp,
                    const timezone_t *tz);

Description

This function interprets the value in *xtp as a TIME_UTC clock type value and writes the corresponding broken-down local time in the timezone specified in *tz into *tmptr. If tz == NULL then the written *tmptr values are in UTC.

Returns

On success the function shall write the requested time into *tmptr if tmptr != NULL and shall return 0. If the conversion is not possible then the function shall return -1 and *tmptr will be undefined.

1.4.3 The xtime_conv function

Synopsis


  int xtime_conv(      struct xtime *dst, int dst_clock_type,
                 const struct xtime *src, int src_clock_type);

Description

This function converts struct xtime values between different clock types and gives this way the application access to the information that the implementation has available about the relationship between the various clock types. The value *src as it would have been returned by clock type src_clock_type is converted into the value that would at the same time have been returned by clock type dst_clock_type and stored in *dst. The implementation of the conversions between all clock types is optional.

Returns

On success the function shall write the result into *dst if *dst != NULL and shall return 0. If the conversion is not possible because the implementation lacks the necessary information, then the function shall return -1 and *dst will be undefined.

Implementation Advice: Implementations which have a built-in leap-second table should provide access to this table via xtime_conv in the form of supported conversion between the clock types TIME_UTC and TIME_TAI. Implementations can also offer the application to convert TIME_MONOTONIC values into TIME_TAI or TIME_UTC values as soon as these clocks have been adjusted using an external reference. This way, TIME_MONOTONIC values that were measured at a time when the implementation had not yet been able to determine UTC or TAI can later be converted as soon as contact with the reference clock is established.

1.4.4 The strfxtime function

Synopsis

  size_t strfxtime(char * restrict s,
                   size_t maxsize,
                   const char * restrict format,
                   const struct xtime *xtp,
                   const timezone_t *tz);

Description

The strfxtime function places characters into the array pointed to by s as controlled by the string pointed to by format. The format shall be a multibyte character sequence, beginning and ending in its initial shift state. The format string consists of zero or more conversion specifiers and ordinary multibyte characters. A conversion specifier consists of a % character, possibly followed by an E or O modifier character (described below), possibly followed by a sequence of digits that indicate the requested minimum width for the conversion, followed by a character that determines the behavior of the conversion specifier. All ordinary multibyte characters (including the terminating null character) are copied unchanged into the array. If copying takes place between objects that overlap, the behavior is undefined. No more than maxsize characters are placed into the array.

All conversion specifiers supported by strftime are also supported by strfxtime, with the following extensions and modifications:

%k
If the current broken-down time appears multiple times in the specified timezone (for instance as the last hour of summer time and as the first hour of winter time), then this conversion specifier is substituted by "A" during the first appearance, by "B" during the second appearance, and so on. Otherwise it is replaced by no character, unless the conversion specifier was %1k, in which case it is replaced by a space. (This A/B convention is part of the official local-time notation in some countries (e.g., Germany).)
%l
is replaced by the locale’s abbreviated timezone name or no character if none is determinable.
%L
is replaced by the locale’s unabbreviated timezone name or no character if none is determinable.
%z
is replaced by the offset from UTC in the ISO 8601 basic format "-0430" (meaning 4 hours 30 minutes behind UTC, west of Greenwich), or by no characters if no timezone offset is determinable. If the format specifier is %:z, then it is replaced by the ISO 8601 extended format "-04:30" with a colon separating the hour and minute field. If the offset to UTC is an integral number of hours, then the minute field and the colon are omitted.
%Z
is identical to %z, except that the minute field is always present when the hour field is present, even if it is zero.

The format specifiers %H, %M, and %S can also be used to generate fractional parts of hours, minutes, or seconds, as required for some ISO 8601 notations. Fractional output is obtained by following the % with either a full stop or a comma, followed by a decimal number indicating the requested number of fractional digits. The choice of either a full stop or a comma indicates whether a decimal dot or a decimal comma shall be used in the output. The output is always rounded downwards such that a shorter output is guaranteed to be the prefix of an output with a larger number of fractional digits. For instance if "%H:%M:%.1S" results in "04:40:00.0", then "%,2H" will result in "04,66".

Returns

If the total number of resulting characters including the terminating null character is not more than maxsize, the strftime function returns the number of characters placed into the array pointed to by s not including the terminating null character. Otherwise, zero is returned and the contents of the array are indeterminate.

2 Rationale

The ISO C 89 <time.h> functions have a number of serious shortcomings:

Considering the large number of problems, a clean-slate redesign of the time API seemed preferable to incremental improvements. Several previous attempts to improve C’s time API have remained unsatisfactory:

All these problem can easily be solved by simply giving up time_t and introducing a new time type with a carefully defined encoding. The definition of struct xtime used here is inspired by the POSIX.1 struct timespec, which again was inspired by the struct timeval interface of the BSD Unix gettimeofday system call, so there exists plenty of practical experience with this encoding.

The improvements that struct xtime brings compared to its POSIX and BSD predecessors are:

The POSIX.1 approach of not specifying the behaviour of time_t in the vicinity of a UTC leap second can lead to incompatible behaviour in network-synchronized distributed systems. Defining an encoding of a time scale that is completely based on TAI is also not feasible, because TAI is not widely publicized. End users usually expect UTC-based external representations of time, and forcing implementations to represent everything in a TAI-based time scale without leap seconds would cause in practice numerous wrong timestamps caused by out-of-date UTC-TAI tables in systems (as demonstrated by the experience with complaints from owners of early GPS receivers).

This API does not specify whether the system’s internal clock runs on UTC, TAI, or on a completely independent (monotonic) time base. It provides applications equally with access to all three types of time scales and fully supports implementations that are unaware of TAI. Implementations of this API are not required to store an up-to-date UTC-TAI table or to keep track of how the internal monotonic time is related to UTC and TAI, but if the information is available to the implementation, then the user can access it in a portable way via the xtime_conv function. This way, this API does not put extraordinary burdens on the implementor, but at the same time allows sophisticated implementations to offer their full UTC/TAI functionality to the user.

The TIME_MONOTONIC clock type is offered for two reasons. The first one is that a reliable leap-second free time-scale is required for algorithms like “Wait 3000 ms before opening the valve”, even on systems that do not have access to TAI, and access to TAI is also not required otherwise for such applications. The second reason is that many applications require a time-scale that is guaranteed to be available right from system start, but devices without a reliable battery-backed clock have to acquire UTC first after startup from an external source, and even devices with a battery clock can drift so far away that a crude adjustment is necessary, which would affect wait loops if UTC were the only timebase available.

The problem that struct tm does not provide all information that an efficient and extensible time-string formatting function might need is solved elegantly by just defining strfxtime such that the struct xtime and timezone values are provided directly to this function, avoiding the inefficient and lossy detour via mktime that was unfortunately necessary in ISO C 89.

This approach avoids that we need significant extensions of mktime and clumsy mechanisms for future extensions in a second struct tmx as one of the ISO C 9X draft proposals suggested.

None of the previous drafts provided a clean interface to handle timezone information as an object of its own right and in a multi-threading safe way. Timezone information can be a comprehensive data structure that needs a compilation step in order to be transformed into an efficient in-memory representation. The textual description format of a time zone can be complex, therefore an error message facility is necessary to help users in getting a correct configuration. An API with three functions similar to the POSIX.2 regular expression functions has been provided for this.

The old mktime, gmtime, and localtime functions have been replaced by the two new functions xtime_make and xtime_breakup, which provide a simpler and yet much more functional interface. They accept a universal timezone specification as a parameter and are not bound to a single timezone. The old functions allowed breaking up a time only for local time and UTC, and a time could only be composed in local time and not in UTC. The new functions do not return pointers to internal static buffers like their original equivalents and are therefore multi-threading safe. They also allow the user to switch between arbitrarily many timezones and provide two-way conversion for all of these. The supported timezones can have very complex relationships with UTC, for instance it is no problem for an application to offer via this API the local time for some Mars robot. The role of the xtime_make and xtime_breakup functions in this new API is also much less important: While in ISO C 89 mktime, gmtime, and localtime were the only way of performing portable arithmetic on time_t, with the new API, arithmetic can be performed directly on struct xtime, since the encoding is fully defined there. These functions are also not relevant any more for printing a time since, in contrast to strftime, strfxtime operates directly on the second-counter time and the timezone descriptor.

The list of conversion specifiers of strftime was much shorter than needed, and a number of new ones had to be added in order to support the POSIX.2 date functionality, all ISO 8601 date and time formats, the European Union summer time directive regulations for denoting the last hour of summer time and the first hour of winter time, and customary locale based abbreviated and full names of timezones. Most of the new conversion specifiers are already provided in ISO C 99, so we have to add only a few additional ones.

3 Related information

Readers not very familiar with concepts such as leap seconds, TAI, UTC, UT1, NTP, and modern PLL-based kernel clock implementations, are invited to have a look at the references listed on the author's Computer time resouces page.

This proposal emerged out of discussions on the tz mailing list, and I am especially thankful for valuable suggestions from Paul Eggert, Joseph Myers, Antoine Leca , Clive Feather, Chris Newman, Nelson Beebe, and others.

Suggestions and comments on this text are very welcome!
created 1998-09-09 – last modified 2004-08-03 – http://www.cl.cam.ac.uk/~mgk25/c-time/