Modernized <time.h> for ISO C
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.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
which represents a point on some time scale or a duration in time and
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
int xtime_get(struct xtime *xtp, int clock_type);
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:
- 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.
- 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.
- 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.
- 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.
- 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.
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
void xtime_delay(long sec, long nsec);
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
int tz_prep(timezone_t **tz, const char * restrict tzstring);
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.
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
char *tz_error(timezone_t *tz);
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.
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
void tz_free(timezone_t *tz);
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
long tz_jump(timezone_t *tz, struct xtime *xtp, int forward);
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.
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
int xtime_make(struct xtime *xtp, const struct tm *tmptr, const timezone_t *tz);
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.
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
int xtime_breakup(struct tm *tmptr, const struct xtime *xtp, const timezone_t *tz);
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.
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
int xtime_conv( struct xtime *dst, int dst_clock_type, const struct xtime *src, int src_clock_type);
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.
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
size_t strfxtime(char * restrict s, size_t maxsize, const char * restrict format, const struct xtime *xtp, const timezone_t *tz);
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:
- 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).)
- is replaced by the locale’s abbreviated timezone name or no character if none is determinable.
- is replaced by the locale’s unabbreviated timezone name or no character if none is determinable.
- 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.
- 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".
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.
The ISO C 89 <time.h> functions have a number of serious shortcomings:
- Lack of resolution. The time_t type has traditionally been (and still is for backwards compatibility) a second counter since some epoch, providing only a resolution of one second. The standard theoretically allows a floating point type for time_t, but even a 64-bit FP type cannot represent the resolution of modern CPU bus-cycle counters. In addition, the absolute error of floating point types grows with distance from the epoch, which can be problematic in many applications.
- Lack of arithmetic support for the time_t type. Since the encoding of time_t was not defined in the standard, it was not practical to even add or subtract a specified time interval in a portable way.
- Lack of a UTC equivalent of mktime. It was not possible to convert a broken-down UTC time in a portable way into time_t.
- Lack of reentrant functions. Some ISO C 89 time functions return pointers to static buffers and are therefore not efficiently implementable in a multi-threading safe way. POSIX.1c had to replace all these functions.
- Lack of control over the timezone. The old API knows only about some unspecified local time and with restrictions it also supports UTC, which leaves something to be desired in an age of global networking.
- Lack of a reliable interval time scale. The old API does not provide a separate clock for time-interval measurements that is not affected by clock adjustments (including leap seconds). It also did not provide access to TAI for those systems that had access to this form of time (e.g. via GPS or PTP).
- Lack of leap-second representation. The ISO C 89 standard did not provide practical provisions for dealing with UTC leap seconds. On POSIX systems, time_t deliberately does not count inserted leap seconds, in the interest of simple and robust date arithmetic. Integer and floating point encodings of time_t do not provide a representation of leap seconds that would allow calculation of the correct length of time intervals without the use of a leap-second table that requires periodic updating. (On the other hand, this may not be a real problem. A good case can be made that leap seconds should not actually be represented as any special value for 23:59:60 in computer applications: while they could be displayed as such, what would their arithmetic properties be? A more robust way to deal with leap seconds would be a standardized way to stear around them, such as UTC-SLS.)
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:
- The POSIX.1 standard (ISO/IEC 9945-1:1996) fixed the multi-threading problems. It defined additional versions of some functions, which write the result into a user-provided buffer instead of returning a pointer to a static buffer. It also tried to fix the problem of undefined arithmetic in time_t, by defining an integer encoding with a 1970 epoch for this type. However, this encoding lacks a value for leap seconds and allows 32-bit implementations that will overflow rather soon (2038-01-19 03:14:08). POSIX.1 also does not provide a reentrant interface for working in multiple timezones. The POSIX.2 (ISO/IEC 9945-2:1993) date function specifies a number of additional format specifiers that should also be in C’s time formatting function, but these extensions do not yet cover all requirements of ISO 8601.
- The new <time.h> revision by Clive Feather, which was proposed in an earlier ISO C 9X draft, does not address multi-threading safety. It continues to use time_t as its primary time representation and therefore cannot introduce a mandatory encoding for backwards compatibility reasons (POSIX uses UTC seconds since 1970, Microsoft uses some float type, some people use TAI seconds, etc.). Support for time_t arithmetic is provided by extending the range of mktime such that it becomes for instance possible to add an hour to a time_t value by converting it into a struct tm, then adding 3600 to tm_sec or 1 to tm_hour and then converting back via localtime. The problem that strftime has to output values that are not part of struct tm is attacked by providing a struct tmx version of the broken-down time with a variable-length extension field. All these are rather complicated, non-straight-forward, and not very robust extensions. The portable time_t arithmetic via mkxtime is relatively inefficient and the semantic is dubious in the context of leap seconds.
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:
- Instead of leaving the type of the seconds counter implementation defined, we require it to be at least 64-bits long using the new ISO C 99 <stdint.h> facilities. This prevents all the overflow problems that 32-bit POSIX implementations will face on January 19, 2038. A 64-bit second counter can represent a time interval of over 0.5 * 1012 years, which generously exceeds the lifetime of the solar system.
- Nanoseconds are a reasonable lower bound for the resolution, because a nanosecond is shorter than the precision of the best available clocks, and relativistic limits will make it unlikely that subnanosecond clock precisions will become available in commonly accessible timescales. It has been argued that CPU bus cycles could eventually get as short as 0.1 ns (3 cm clock-signal wavelength) and that therefore higher precision timestamps might be desirable one day. However, this API is primarily designed to provide access to time references. For access to a high-resolution CPU-cycle counter – as required for benchmarking and code optimization – it is much more prudent to give applications direct access to the integer value of a hardware counter and also to an estimate of the current frequency of this counter. This can be done in a much simpler function, preferably one with constant and precisely documentable execution time. Such a function or macro, while no doubt useful, is outside the scope of this proposal. A function like xtime_get() has to perform much more complex algorithms than one would like to have in a simple cycle-counter access function for low-level profiling.
- The ugly structure member prefix tv_ that POSIX.1 uses was removed, because the original reason for them – some ancient pre-ISO-C compilers that put members of all structs into the same namespace – no longer applies, and the tv_ prefix in the struct timespec was an unfortunate typo anyway (should have been ts_).
- The idea of representing leap seconds as 32-bit nanosecond overflows is a very elegant solution to the leap second problem. This allows combining easy UTC arithmetic with a proper representation of leap seconds. The same functions that print and convert UTC timestamps with this encoding can also be used to handle TAI timestamps, as both UTC and TAI now have the same mapping between second-counter and broken-down representations of time. In addition, the correct encoding of UTC timestamps does not depend on the presence of a leap-second table.
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.
- Dan Bernstein’s libtai is a library for handling all internal timestamps in an application in TAI.
- David Tribble developed several other time API draft proposals for ISO C 200X:
- Paul Eggert also wrote an alternative proposal, which is derived from this proposal but replaces struct xtime with an integer type.
- Jonathan Lennox has also written a proposal for thread-safe time zone information, in part inspired by this proposal, and implemented it as a patch to the Olson TZ library.
- A proposal for handling leap seconds with fewer hazards for information systems is the UTC with Smoothed Leap Seconds (UTC-SLS).
- If you want to submit a formal comment on the draft ISO C standard to the ISO/IEC JTC1/SC22/WG14 committee, then please have a look at the NCITS Announcement of the Public Review Comment Period for ISO/IEC FCD 9899 for instructions.
- If you are interested in the time APIs of other languages, the Ada95 Reference Manual (ISO/IEC 8652:1995) defines a common purpose package Ada.Calendar in section 9.6, and a more sophisticated high-resolution, monotonic clock package Ada.Real_Time in section D.8.
- There are also the CORBA Time Service and Enhanced View of Time Service specifications by OMG.
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/