A proposal for thread-safe time zone information. A set of extensions to ISO C (99) and IEEE POSIX (200x). by Jonathan Lennox, Columbia University. Version 3. Summary: A new data type is defined, 'struct tzinfo', that represents the time zone information for a particular region. Versions of the ISO C time conversion functions are defined that take this data type as an argument. Definitions: Time zone: The set of information necessary to correctly convert bidirectionally between a time_t and a struct tm, for a particular geographic region. Wall clock time: The system's best approximation of the local time in its physical location, independent of the physical location of any user. Functions defined in this proposal: Functions manipulating struct tzinfo values: Name tz_prep -- prepare a time zone object, based on a descriptive string. Synopsis #include int tz_prep(struct tzinfo** tz, const char *tzstring); Description The tz_prep() function creates a new time zone object, corresponding to the given time zone name. tzstring is a pointer to a string, or NULL. The two defined values of tzstring are NULL, representing the system's best approximation of its wall clock time, and the zero-length string "", representing the system's best approximation of Coordinated Universal Time (UTC). On successful completion, *tz will contain a pointer to a struct tzinfo, contains all necessary information to represent times in the specified time zone. It has no externally-visible elements. POSIX extension: In a POSIX-based environment, the syntax of tzstring is the same as that of the "TZ" environment variable, including the implementation-defined values beginning with ':'. Return Value On successful completion, tz_prep() returns a value of 0 and fills in *tz. On failure, an error number is returned and no resources are allocated. Errors The tz_prep() function shall fail if: ENOMEM Not enough memory is available to create the time zone object. ENOENT No known time zone corresponds to tzname. The tz_prep() function may fail if: * There is a problem with the system's configuration (such as an on-disk time zone database) which caused retrieval of the time zone information to fail unexpectedly. In this case any appropriate error number may be returned. Rationale and commentary Systems which define extensions beyond POSIX (such as the Olson time zone names) for the "TZ" environment variable should support the same extensions for tzstring. Under the POSIX rules, strings such as "UTC0" can also represent UTC, but the empty string "" is the portable representation. This representation is also correctly locale-independent -- "UTC0" in fr_FR generates times with the incorrect time zone abbreviation "UTC", whereas "" in that locale should use the correct time zone abbreviation "TUC" if the time zone code correctly supports locales. This interface is designed so that tz_prep(getenv("TZ")) will return an object describing the default time zone object that non-thread-aware versions of the time functions will use by default, provided TZ (if set) is set to a valid time zone name. Question for discussion: should there be an ISO C way of saying "the timezone which localtime() and mktime() would use by default"? Currently this is only possible for POSIX, using thee tz_prep(getenv("TZ")) idiom mentioned above. Name tz_free -- Free resources allocated for a time zone object Synopsis #include void tz_free(struct tzinfo* tzobj); Description The tz_free() function frees the resources allocated for a time zone object returned by a call to tz_prep(). Return Value None. Errors None. Rationale and commentary None needed. Functions using struct tzinfo to manipulate time values: Name time_make - derive a time_t from a struct tm, in a specified time zone. Synopsis #include int time_make(time_t *clock, struct tm *tm, const struct tzinfo *tz); Description This function interprets the broken-down time in *tm as a local time in the timezone specified in *tz, and writes the corresponding value into *clock, using the same encoding as that of the values returned by the 'time' function. The original values of the tm_wday and tm_yday components of *tm are ignored, and the original values of the other components are not restricted to their normal ranges. (A positive or zero value for tm_isdst causes time_make() to presume initially that summer time (for example, Daylight Saving Time) is or is not in effect for the specified time, respectively. A negative value for tm_isdst causes the time_make() function to attempt to divine whether summer time is in effect for the specified time.) On successful completion, the values of the tm_wday and tm_yday components of *tm are set appropriately, and the other components are set to represent the specified calendar time, but with their values forced to their normal ranges; the final value of tm_mday is not set until tm_mon and tm_year are determined. Returns time_make returns 0 on sucessful completion, sets *clock, and normalizes *tm. On failure, it returns an error number and leaves the values of *clock and *tm undefined. Errors time_make() shall fail if: ERANGE *tm does not represent a time representable by a time_t value. time_make() may fail if: EINVAL *tm does not represent a possible time in the timezone *tz. (For instance, a leap-forward interval.) Rationale and commentary This function is the generalization of the ISO C function mktime() and the BSD/tzcode function timegm(). The name and calling conventions of this function are inspired by Markus Kuhn's proposed xtime_make(). struct xtime is a much more sophisticated and better-defined representation of time than time_t; this proposal is designed to be less ambitious while still leaving room for these future improvements. Following Kuhn, this function corrects a flaw of mktime()/timegm(). Those functions use the value (time_t)-1 to represent an error return status. However, this value can also be a correct translation of a struct tm representing the time December 31, 1969, 23:59:59 GMT (assuming POSIX time_t's). This function, instead, uses an out-of-band method to indicate error conditions, leaving the entire time_t space free to represent valid values. Given an impossible time value for the time zone (e.g. one that occurs during a leap-forward interval), implemenetations have a choice of failing and returning EINVAL, or normalizing *tm to a nearby valid time. Name time_breakup - derive a struct tm from a time_t, in a specified time zone. Synopsis #include int time_breakup(struct tm *result, const time_t *clock, const struct tzinfo *tz); Description Fill in *result with the values corresponding to *clock in the time zone *tz. Return value time_breakup() returns 0, and fills in *result, always. Errors None. Rationale and commentary This function is the generalization of the ISO C functions localtime() and gmtime(), and the POSIX functions localtime_r() and gmtime_r(). It is inspired by Markus Kuhn's xtime_breakup(). The function assumes that a 'struct tzinfo' will be able to do something reasonable over the whole range of a time_t. This isn't necessarily true; it might be sensible to define an ERANGE error return value? This function omits the localtime side effect that it sets the value of the global variable char *tzname[2]. It's not clear how best to handle the BSD/tzcode tm_zone field of struct tm. This is defined as a char*, but since there is no tm_free() function, the data it points to cannot be dynamically allocated by time_breakup(). One possibility would be to have tm_zone point into data stored within 'struct tz', but then it would be invalidated when tz_free() was called. A second possibility would be to define tm_zone as char tm_zone[TZNAME_MAX], but this would break binary compatibility. A third possibility would be to deprecate tm_zone, and have time_breakup leave it NULL. Zone names can be obtained by calling strftime with a "%Z" format string (assuming POSIX strftime). Name strftime_z - generate a text representation of a time value, in a given time zone. Synopsis #include size_t strftime_z(char * restrict buf, size_t maxsize, const char * restrict format, const struct tm * restrict timeptr, const struct tzinfo * restrict tz); Description strftime_z() formats the information from 'timeptr' into the buffer 'buf' according to the format specified by 'format' and the system locale. The format specified by 'format' is the same as that of strftime(). POSIX note: All POSIX extensions to strftime() apply to strftime_z() as well. Return value On successful completion, strftime_z fills in 'buf' and returns the number of bytes converted. On failure, strftime_z returns the number of bytes of buffer that would be required to fully perform the conversion (including the terminating NUL), and leaves 'buf' in an indeterminate state. Errors None. Rationale and commentary This function is a generalization of strftime(). The return value of this function has changed from strftime(), however. strftime() returns 0 if the buffer is not large enough. This function, instead, follows the example of ISO C 99's snprintf(), which allows an appropriately-sized buffer to be allocated in one step after a failure, rather than requiring a binary search. The error status can be easily checked by checking (ret <= maxbuf). The formatting strings of ISO C strftime() are not affected by the time zone, so this function is strictly speaking not necessary in a pure-C environment. POSIX strftime(), however, has the '%z' and '%Z' conversions, which require knowledge of the relevant time zone. It has been argued that this function is redundant, because time_breakup() could embed time zone information into struct tm (either in extension fields or private fields), and thus the standard POSIX strftime could suffice to convert struct tm's in a thread-safe way. However, this is only true for struct tm's created from time_breakup() or the mktime() family. Creation of struct tm values "by hand" is still fairly common, and a time zone specification is needed to print these. Name strftime_zl - generate a text representation of a time value, in a given time zone and locale. Synopsis #include size_t strftime_zl(char * restrict buf, size_t maxsize, const char * restrict format, const struct tm * restrict timeptr, const struct tzinfo * restrict tz, const locale_t * restrict l); Description (This is an extension based on Ulrich Drepper's thread-aware locale proposal; see rationale.) strftime_z() formats the information from 'timeptr' into the buffer 'buf' according to the format specified by 'format' and the locale specified by 'l'. Other than the source of locale information, this is in all ways identical to strftime_z. Rationale and commentary This function follows Ulrich Drepper's thread-safe locale proposal; it is the combination of his strftime_l and strftime_z defined above. See for details. This function should only be implemented if the rest of Drepper's proposal is as well. The _zl suffix is ugly. More natural would be to define a single function which takes all the necessary thread-safe data as arguments, but I don't want to force implementation of this proposal to be blocked on Drepper's. Thread-support functions Name pthread_settz -- set time zone object for the current POSIX thread. Synopsis #include #include int pthread_settz(const struct tzinfo *tz); Description (This function is only relevant in a POSIX system with the Threads option.) pthread_settz() sets the time zone object to be used for time functions called from the current POSIX thread. The time zone setting for all other threads remains the same. Once pthread_settz() has been called, all calls to the functions localtime(), localtime_r(), ctime(), ctime_r(), asctime(), asctime_r(), mktime(), and strftime() in the thread from which it was called will use the specified time zone rather than the process-wide time zone. If pthread_settz() is called with a NULL pointer as its argument, the current thread will again use the global time zone object. pthread_settz() leaves the values of the global variable tzname, and the XSI global variables daylight and timezone, undefined. Return value Upon successful completion, pthread_settz() returns a value of zero. Otherwise, an error code is returned to indicate an error. Errors pthread_settz() may fail if: ENOMEM Insufficient memory exists to store the time zone information for the thread. EAGAIN The system lacked the necessary resources to associate the time zone information with the thread. Rationale and commentary This function allows existing code which uses the ISO C APIs to work correctly, unmodified, in a threaded environment. The extended parts of the POSIX APIs may not work correctly unmodified. This function would typically be implemented as a wrapper around pthread_setspecific(). It's not clear if a parallel pthread_gettz() is also needed. General rationale and commentary Thanks to many comments from the people of the tz@elsie.nci.nih.gov mailing list. Much inspiration for this work was drawn from Markus Kuhn's proposed extended time APIs for ISO C 200x, and from Ulrich Drepper's proposed thread-aware locale functions. Markus Kuhn's proposed time zone API defined an additional time zone manipulation function tz_jump(), which gets the next or previous discontinuity in a given time zone. This is a potentially useful function, but it was omitted here so as to keep the API definitions modest. (Additionally, it should probably wait on the proper definition of an extended time type, to avoid unnecessary redundancy.) Time zone specific versions of asctime[_r] and ctime[_r] were omitted, as they can be constructed trivially out of the functions defined here. A time zone specific version of strptime is not necessary. The only zone-aware function of strptime that I know of is the BSD extension to scan "%Z"; POSIX 200x does not define this, and it generally doesn't seem to work very well as zone abbreviations are ambiguous. (BSD strptime only recognizes the current local timezone, in standard and summer forms, and "GMT". Even this is not necessarily unambiguous: consider for example the Australian "EST" meaning both "Eastern Standard Time" and "Eastern Summer Time".) wcsftime_z and wcsftime_zl functions are needed, analogous to strftime_z and strftime_zl for wide strings. Their definitions are obvious analogies to the existing functions. Changes from Version 2: The structure was renamed to 'struct tzinfo' from 'struct timezone', because the latter structure is already defined on BSD systems. (It is the deprecated second argument to gettimeofday().) Clarified that pthread_settz() leaves the state of global variables undefined. Corrected incorrect errno value in the time_make() rationale. Changes from Version 1: The time-zone creation and destruction functions were renamed from newtz() and freetz() to tz_prep() and tz_free(). This puts them in a name space, and aligns them with Markus Kuhn's functions. (These functions are slightly different from Kuhn's, though, in that they are defined to return errno values on errors.) tzdup() was dropped as not useful. mktime_z() and localtime_z() were renamed to time_make() and time_breakup(), by analogy with Kuhn's xtime_* functions. Zone-specific versions of asctime(), ctime(), and strptime() were dropped as unnecessary. The text was greatly clarified to make clear the distinctions between ISO C extensions, POSIX extensions, and rationale and commentary. The externally-visible tz_name elements of struct tz were dropped. The strftime_zl function was defined.