1 DRAFT Modernized <time.h> API for ISO C

1.0 Informal introduction

This document specifies a proposed extension to the standard C application programming interface (API) for time. It adds new primitives suitable for applications requiring higher-quality access to timestamps. Please see the Rationale in section 2 for a discussion about these extensions.

This document is drafted as an extension to the standard C99 library, standardized in ISO/IEC 9899:1999. It is suitable for use in other extensions to the C standard, notably POSIX.1, which is standardized in ISO/IEC 9945-1:1996.

To avoid repetition, this document does not repeat the existing <time.h> specification already present in C99. Instead, it specifies only the proposed extensions to the standard.

1.1 Components of time

The macros defined are

  CLOCK_LOCAL
  CLOCK_MONOTONIC
  CLOCK_TAI
  CLOCK_REALTIME

each of which expands to an integer constant expression with a clock type value, suitable as an argument to xtime_get;

  CLOCK_SYSTEM

which expands to an integer expression with a clock type value that is supported by the implementation;

  CLOCKOPT_MAXSTAMP
  CLOCKOPT_MINSTAMP
  CLOCKOPT_NOW
  CLOCKOPT_PRECISION
  CLOCKOPT_RESOLUTION
  CLOCKOPT_UTC_OFFSET

each of which expands to an integer constant expression with a clock option value, which can be added to a clock type like CLOCK_REALTIME; and

  XTIME_MIN
  XTIME_MAX

which expand to integer constant expressions of type xtime_t, giving the minimum and maximum values for the type.

The types declared are

  xtime_t

which is a signed integer type used for timestamps and other values; and

  timezone_t

which is an object type that can hold all or part of the information needed to translate between a clock type's timestamp and a broken-down time.

An error number is a positive value E such that strerror(E) != NULL. An erroneous value is a negative value E such that xtime_errno(E) != 0.

A timestamp T is an integer that identifies the point in time that is T intervals after a clock type's reference point, called an epoch. The size of the intervals depends on the clock type. Negative timestamps identify points of time before the epoch. A clock type's timestamps form a contiguous subrange of the xtime_t values. XTIME_MIN and erroneous values are not timestamps.

When the true result falls between one representable value and the next, functions described in this section always return the lesser value.

1.2 Time manipulation functions

1.2.1 The xtime_get function

Synopsis

  xtime_t xtime_get(int flag);

Description

The xtime_get function returns information according to flag, which shall be the sum of a clock type and a clock option.

The following clock types are defined by this standard:

CLOCK_LOCAL
This clock uses local-time seconds, with the pseudo-epoch 2000-03-01 00:00:00 using the current UTC offset. When an inserted leap second or a UTC offset decrease occurs, the clock is adjusted backward; when a deleted leap second or UTC offset increase occurs, the clock is adjusted forward. The clock therefore counts the number of seconds since the epoch, ignoring leap seconds and UTC offset changes, and is ambiguous when the UTC offset decreases or when a leap second is inserted.
CLOCK_MONOTONIC
This clock uses SI seconds with epoch no later than the first return of xtime_get. Leap seconds do not affect this clock, nor do manual resets or adjustments of a system-wide UTC, TAI, or local-time clock. The implementation may adjust the duration of the second of this clock to match the duration of an SI second more closely once it has learned the frequency error of its local oscillator by comparing it with an external reference time signal.
CLOCK_TAI
This clock uses International Atomic Time (TAI) seconds with an epoch that is no later than the first return of xtime_get, and that is an integral number of seconds away from 2000-03-01 00:00:00 Coordinated Universal Time (UTC). Timestamps before the epoch represent atomic time using best historical practice.
CLOCK_REALTIME
This clock uses universal-time seconds, with the epoch 2000-03-01 00:00:00 UTC. After an inserted leap second occurs, the clock is adjusted backward by one second; when a deleted leap second is skipped, the clock is adjusted forward by one second. The clock therefore counts the number of seconds since the epoch, ignoring completed leap seconds, and is ambiguous during an inserted leap second, and for one second after the inserted leap second. Timestamps before the current UTC regime was instituted (at 1972-01-01 00:00:00 UTC) represent universal time using best historical practice; in earlier regimes, universal seconds varied in length and there were no leap seconds or ambiguous timestamps.
The xtime_get function shall support at least one clock type. The CLOCK_SYSTEM macro indicates the preferred supported clock type.

The following clock options are defined by this standard:

CLOCKOPT_MAXSTAMP
The clock type's maximum timestamp; it shall be positive.
CLOCKOPT_MINSTAMP
The clock type's minimum timestamp; it shall be nonpositive.
CLOCKOPT_NOW
The current time for the given clock type, as a value representing the number of clock-type intervals since the epoch.
CLOCKOPT_PRECISION
The number of intervals per second for the clock type; it shall be positive.
CLOCKOPT_RESOLUTION
The number of times that the clock type ticks per second; it shall be positive.
CLOCKOPT_UTC_OFFSET
The clock's offset from UTC, in clock type intervals. A positive value means that the clock is ahead of UTC.

Returns

If an error is detected, the xtime_get function returns an erroneous value. Otherwise the function returns the requested value.

Example

  xtime_t now = xtime_get(CLOCKOPT_NOW | CLOCK_REALTIME);
  if (now < 0)
    printf("UTC not supported: %s\n", strerror(xtime_errno(now)));

1.2.2 The xtime_delay function

Synopsis

  void xtime_delay(xtime_t xt);

Description

The xtime_delay function waits for at least xt CLOCK_MONOTONIC intervals. The function returns immediately if xt is not positive.

1.3 Time zone manipulation functions

A time zone in this context is the information necessary to convert between a timestamp and a struct tm broken-down or string-formatted local time. This information can include not only a fixed time offset between UTC and the local time, but also algorithms that determine how the UTC 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 time zone description that is provided as a text string into an in-memory representation of the time zone. Since time zone information can include tables of variable length, the size of the in-memory representation is returned.

1.3.1 The tz_prep function

Synopsis

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

Description

The tz_prep function stores into tz (which is of *size bytes) a handle for the time zone specified in tzstring. The handle can be used only with clock_type timestamps. Update *size to reflect the number of bytes actually needed to represent the time zone.

The syntax for tzstring shall include the TZ format defined in ISO/IEC 9945-1:1996 (POSIX.1) section 8.1.1 (e.g. "CET-1CEST,M3.5.0/2,M10.5.0/3" for Central Europe in 1999); it is a full algorithmic description of the time zone.

If tzstring == NULL, then some externally defined default time zone shall be used.

By convention, at program startup, getenv("TZ") returns a value suitable for use as tzstring.

Returns

If an error is detected, the tz_prep returns an error number; the contents of the buffer and of *size are indeterminate.

Otherwise, the function returns zero and updates *size to reflect the number of bytes needed to store the time zone handle. If tz != NULL, it also stores the time zone handle into the buffer identified by tz if the previous value of *size was no less than the new value with the contents of the buffer being indeterminate otherwise.

Example

  const char *tzstring = "PST8PDT,M4.1.0,M10.5.0";
  size_t size = 0;
  timezone_t tz = malloc((tz_prep(NULL, &size, CLOCK_SYSTEM, tzstring), size));
  if (tz == NULL)
    printf("out of memory\n");
  else {
    int e = tz_prep(tz, size, CLOCK_SYSTEM, tzstring);
    if (e != 0)
      printf("Invalid time zone spec `%s': %s\n", tzstring, strerror(e));
  }

1.3.2 The tz_get function

Synopsis

  xtime_t tz_get(int option,
                 xtime_t xt,
                 const timezone_t * restrict tz);

Description

The tz_get function reports information about the timestamp region around xt according to option, which shall be a clock option. In the timestamp region, the broken-down time representation defined by tz is handled uniformly: conversion is predictable by a uniform 24h-clock and the normal Gregorian calendar with no leap seconds, UTC offset changes, or clock rate adjustments. Outside a region boundary, conversion is no longer predictable by the same set of rules, either because of a discontinuity at the boundary due to a UTC offset change or a leap second, or because the clock rate is adjusted at the boundary, or because values outside the boundary are not timestamps, or because the implementation does not have the conversion rules available.

The discontinuity for a leap second occurs at 00:00:00 UTC.

The behavior is undefined if xt is not a timestamp, or if tz is not the address of a valid time zone handle of the same clock type as the timestamp.

Returns

If an error is detected, the tz_get function returns an erroneous value. Otherwise, the returned value depends on the option value, as follows:

CLOCKOPT_MAXSTAMP
The region's minimum timestamp.
CLOCKOPT_MINSTAMP
The region's maximum timestamp.
CLOCKOPT_RESOLUTION
The actual number of times that the clock currently ticks per second; it shall be nonnegative.
CLOCKOPT_UTC_OFFSET
The UTC offset for clocks within the region, in clock type intervals.

Example

  xtime_t now = xtime_get(CLOCKOPT_NOW | CLOCK_SYSTEM);
  xtime_t resolution = xtime_get(CLOCKOPT_RESOLUTION | CLOCK_SYSTEM);
  xtime_t utc_offset = tz_get(CLOCKOPT_UTC_OFFSET, now, tz);
  int e = xtime_errno(utc_offset);
  if (e != 0)
    printf("unknown UTC offset: %s\n", strerror(e));
  else
    printf("%g hours east of UTC\n", utc_offset / (3600.0 * resolution));

1.4 Time conversion functions

1.4.1 The xtime_make function

Synopsis

  xtime_t xtime_make(xtime_t xt,
                     const struct tm *tmptr,
                     const timezone_t *tz);

Description

This function adds to the timestamp xt the number of years, months, days, hours, minutes, and seconds (in that order) indicated by the members of *tmptr, according to the time zone handle addressed by tz, and returns the resulting timestamp. If tz == NULL then the *tmptr values are interpreted in UTC.

The behavior is undefined if xt is not a timestamp, or if tz is not the address of a valid time zone handle of the same clock type as the timestamp.

Returns

If an error is detected, the function returns an erroneous value. Otherwise, the function returns the requested timestamp.

1.4.2 The xtime_breakup function

Synopsis

  int xtime_breakup(struct tm *tmptr,
                    xtime_t xt,
                    const timezone_t * restrict tz);

Description

This function converts the timestamp xt to a corresponding broken-down local time in the time zone specified by tz into *tmptr. If tz == NULL then the written *tmptr values are in UTC.

The behavior is undefined if xt is not a timestamp, or if tz is not the address of a valid time zone handle of the same clock type as the timestamp.

Returns

If an error is detected, the function returns an error number. Otherwise, the function stores the requested time into *tmptr and returns zero.

1.4.3 The xtime_conv function

Synopsis

  xtime_t xtime_conv(xtime_t src, int src_clock_type, int dst_clock_type);

Description

This function converts xtime_t values between different clock types. The value src as it would have been returned by clock type src_clock_type|CLOCKOPT_NOW is converted into the value that would at the same time have been returned by clock type dst_clock_type|CLOCKOPT_NOW.

Returns

If the xtime_conv function detects an error, it returns an erroneous value. Otherwise, the function returns the requested timestamp.

1.4.4 The xtime_conv_time function

Synopsis

  time_t xtime_conv_time(xtime_t src, int src_clock_type);

Description

This function converts src to a time_t value. The timestamp must be of clock type src_clock_type.

Returns

If the xtime_conv function detects an error, it returns (time_t) -1. Otherwise, the function returns the requested time.

1.4.5 The time_conv_xtime function

Synopsis

  xtime_t time_conv_xtime(time_t src, int dst_clock_type);

Description

This function converts src to an xtime_t value of dst_clock_type clock type.

Returns

If the time_conv_xtime function detects an error, it returns an erroneous value. Otherwise, the function returns the requested timestamp.

1.4.6 The xtime_format function

Synopsis

  int xtime_format(char * restrict s,
                   size_t *size,
                   const char * restrict format,
                   xtime_t xt,
                   const timezone_t * restrict tz);

Description

The xtime_format function places bytes 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 *size characters are placed into the array.

All conversion specifiers supported by strftime are also supported by xtime_format, with the following extensions:

%q
If the current broken-down time appears multiple times in the specified time zone (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. This A/B convention is part of the official local-time notation in some countries (e.g., Germany).
%Q
is replaced by a string value suitable as an tzstring argument to tz_prep, which can be used to reestablish a time zone object with effect equivalent to tz.
%s
is replaced by the value of the timestamp as a signed decimal integer.
%EZ
is replaced by the locale's time zone name or abbreviation, or no characters if none is determinable.
%z
is replaced by the offset from UTC in the ISO 8601 basic format (e.g. "-0430" meaning 4 hours 30 minutes behind UTC, west of Greenwich), or by no characters if no time zone offset is determinable.
%:z
is replaced by the ISO 8601 extended format "-04:30" with a colon separating the hour and minute field. If the UTC offset is an integral number of hours, then the minute field and the colon are omitted. If the UTC offset is not an integral number of minutes, then a colon and second field are appended.
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 period or a comma, followed by a decimal number indicating the requested number of fractional digits. The choice of either a period or a comma indicates whether a decimal-point period or 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".

The behavior is undefined if xt is not a timestamp, or if tz is not the address of a valid time zone handle of the same clock type as the timestamp.

Returns

If the xtime_format function detects an error, it returns an error number. Otherwise it returns zero and updates *size to reflect the number of bytes needed to store the formatted string, including the terminating null. If *size's new value is no greater than its old one, the function also stores the formatted string into s; otherwise, the contents of s are indeterminate.

1.5 Error reporting function

1.5.1 The xtime_errno function

Synopsis

  int xtime_errno(xtime_t xt);

Description

The xtime_errno function inspects a value returned by one of the functions described in this section to determine whether it is erroneous.

Returns

If xt is an erroneous value, the function returns an error number. Otherwise, it returns zero.

2 Rationale

(Please see the NIST Glossary of Time and Frequency Terms for an introduction to the terminology used in this section. Section 2.1 summarizes key notions of timekeeping.)

2.0 Introduction to the rationale

This API aims to fix many shortcomings in the ISO C <time.h> API. It is inspired by Markus Kuhn's struct xtime proposal, and attempts to improve on it in the following ways:

Like the struct xtime API, this API does not require complete support for leap seconds. An implementation can choose to not support leap seconds at all by reporting an error whenever CLOCK_TAI is requested; it can also provide only limited leap second support, based on the previous or next leap second, by having xtime_conv fail when converting TAI timestamps outside a local window of known leap seconds.

Several suggestions have been made to rename the identifiers in the struct xtime proposal, or to remove some features; this proposal sticks to the names and features of the earlier proposal for now, to ease comparison between the two proposals, except that it renames strfxtime to xtime_format to avoid collisions with draft C9x. The suggestions can be acted on after the proposals have been more thoroughly worked out.

2.1 Components of time (rationale)

Civil timekeeping is based on Coordinated Universal Time (UTC), which is roughly equivalent to the older Greenwich Mean Time standard. Implementations with access to UTC should support CLOCK_REALTIME.

Some implementations do not have access to UTC; instead, they have only a local-time clock. Such clocks are problematic during UTC offset changes, which typically occur twice per year during daylight-saving time changes; they also have problems when locales change time zones. Implementations with access to such clocks should support CLOCK_LOCAL.

The best current-time standard is International Atomic Time (TAI), whose clocks tick accurately with standard (SI) seconds. Implementations with access to TAI (e.g. via a GPS satellite receiver) should support CLOCK_TAI; when coupled with a leap second table, this allows correct support of leap seconds within the window of the table.

Since 1972, UTC clocks have ticked in lockstep with TAI clocks, except for leap seconds, which are periodically inserted at the end of a UTC month to keep UTC synchronized with the earth's rotation. (Leap seconds might also need to be deleted if the earth speeds up, but this has never happened and is unlikely.) Since the earth is slowing down irregularly, leap seconds need to be inserted at irregular intervals, and cannot be predicted far in advance. The decision to insert leap seconds is made by the International Earth Rotation Service (IERS); e.g. see the Relationship between TAI and UTC. Leap seconds are simultaneously inserted into all local times derived from UTC.

UTC has been the basis of civil time since 1961, but between 1961 and 1972 it was synchronized to atomic time using a different method, which did not involve leap seconds. UTC's predecessor Universal Time (UT) was introduced in 1925, replacing GMT. For hosts requiring high precision historical timestamps, these time scales form the basis of CLOCK_REALTIME and CLOCK_LOCAL. However, for most practical purposes, these time scales are all equivalent to GMT, and hosts not requiring high precision timestamps need not make any distinction between them and GMT.

Similarly, TAI has been in force since 1970; its predecessors date back to July 1955. There is no standard relationship between atomic time and universal time before July 1955; for earlier timestamps, implementations can either estimate the relationship, or refuse to convert between the two forms.

Some time protocols (e.g. NTP) provide TAI with an unknown epoch, and report only a single leap second, near the time when the leap second actually occurs. Implementations can model this information by supplying TAI conversions only within the window of known leap seconds.

CLOCK_MONOTONIC should be derived from an internal, monotonically nondecreasing clock. It differs from CLOCK_TAI in two ways. First, CLOCK_MONOTONIC is monotonically nondecreasing (hence its name), whereas CLOCK_TAI can go backwards when the clock is reset. Second, CLOCK_MONOTONIC's seconds can start right in the middle of a TAI second, whereas CLOCK_TAI's seconds are supposed to be synchronous with true TAI seconds.

The new type xtime_t is intended to allow implementations to use a wider integer than the old time_t type without introducing backward compatibility problems. Typically, xtime_t will be a 64-bit integer to allow enough range and precision for most applications' needs. Historically, time_t is typically 32 bits and has only 1-s precision.

The new type timezone_t is an opaque type that can hold part of a buffer describing a time zone rule set. An implementation can make it equivalent to char, but making it a unique type will promote better type-checking.

The functions of this section typically return error indications inline, as part of an xtime_t value. This simplifies the interface, and allows errors to propagate nicely instead of being lost due to sloppy programming; it is reminiscent of the NaNs of IEEE floating-point arithmetic. One possible implementation is to let XTIME_MIN+n represent error number n.

2.2 Time manipulation functions (rationale)

2.2.1 The xtime_get function (rationale)

The implementation is required to provide only 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 estimates for TAI and UTC. Implementations may offer additional non-standard clock types (for instance CLOCK_UT1 for the current UTC+DUT1 time, which is broadcasted by some time services).

If flag is CLOCK_MONOTONIC, successful calls to xtime_get return monotonically nondecreasing values. This is not true for the other clocks, which can be adjusted by means not described in this standard.

If two or more clock options are added together, the result is undefined.

2.2.2 The xtime_delay function (rationale)

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.

2.3.1 The tz_prep function (rationale)

The externally defined default time zone should not depend on the value of the TZ environment variable; that is, it is meant to be a system setting, not a user-preference setting like TZ. The implementation may support the Olson extension to POSIX TZ strings (e.g. "Europe/Paris" for the time in Paris) allows names of geographic locations or time zones, which are then translated by a configuration database lookup into a detailed description.

2.4.3 The xtime_conv function (rationale)

The implementation of the conversions between all clock types is optional.

An implementation with 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 CLOCK_REALTIME and CLOCK_TAI. Implementations can also offer the application to convert CLOCK_MONOTONIC values into CLOCK_TAI or CLOCK_REALTIME values as soon as these clocks have been adjusted using an external reference. This way, CLOCK_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.

Implementations should report an error when asked to perform conversions that require unknown information. For example, it is an error to convert between TAI and UTC timestamps far in the future, since exact conversion requires knowledge of future leap seconds, which cannot be predicted more than a few months ahead. In locales where the future UTC offset is unpredictable due to political volatility, implementations should also refuse to convert between far-future UTC and local time.

2.6 Credits

This proposal emerged out of discussions on the tz mailing list, and I am especially thankful for valuable suggestions from Markus Kuhn, who wrote the spec that this one is based on. Other commenters include D. J. Bernstein, Clive D.W. Feather, Antoine Leca, Joseph Myers, and Chris Newman.

Please send comments and suggestions for improvement to:

Paul Eggert

$Id: timeapi.html,v 1.3 2000/01/22 02:20:44 eggert Exp eggert $ -- http://www.twinsun.com/tz/timeapi.html