Paul Eggert wrote:
The ISO committee in charge of the C language has issued a draft for C9x, the next major revision to C. Section 7.16 of this draft C standard proposes a major overhaul of the functions and datatypes defined in <time.h>.
I've submitted the following comments to the ISO committee for their review.
I have submitted the following memo to the comittee, in order to handle Paul's comment. I shall appreciate any comment about this from the people listening at this list, since it appears to me that they are among the most informed persons on these matters. I noticed in the archives the PC-US0011, from Paul Eggert, and particularly the point #14 about the extensions introduced by C9X to the time functions. I intended to propose a change to the draft to solve these issues. Of course, I do not comment about the points of detail regarding the wordings, but I try to stick at the most basic problems. I believe that if we want to go beyond the present (C90) state of time functions, the following needs are to be covered (in this order): 1) remove the dependancy to the internal static buffers 2) doing 1) in a way compatible with POSIX.1 (*_r functions) 3) having a way to specify the timezone (other than the local and UTC ones) [when timezones are considered as UTC offsets] 4) doing 3), including when passing a DST shift, or a change of rules, i.e. when timezones are considered as a portion of the world 5) handling explicitely leapseconds Notes: There are three kinds of internal static buffers: - the buffers specified in the Standard which hold the return values of asctime, ctime, gmtime and localtime - the buffer to hold informations about the timezone - the buffer to hold the locale informations for strftime As we all know, the third kind is bound to the locale model, so I shall not go further in this area. Unfortunately, the POSIX.1 *_r functions remove the dependency on only the 1st kind. So the removal of the dependency to the 2nd kind will require inventing new stuff. Then, I believe POSIX's ctime_r and asctime_r can be written using the present standard library, with code like char * asctime_r (const struct tm *timeptr, char *p ) { char *old_loc; GET_MUTEX_LOCK(locale_lock); /* if the locale is shared */ old_loc = setlocale(LC_TIME, "C"); strftime(p, 26, "%c\n", timeptr); setlocale(LC_TIME, old_loc); RELEASE_MUTEX_LOCK(locale_lock); return p; } (in part because the behavior of strftime is now better specified in the "C" locale). And if I am wrong, then I believe wordings should be improved for this to be correct (I know the problem about setlocale to *not* be called from inside the library, but this can be solved by the implementor using an internal alias). So, to solve localtime_r/gmtime_r needs and point #3, I then propose two new functions (to be compared with zonetime and mkxtime from C9X draft) and a new structure [and perhaps another type]. Remarks around brackets are for you to comment on! The structure is named struct tzinfo, and its first field, named tz_gmtoff, is a long containing the offset in seconds from UTC to a timezone, with positive values meaning ahead of UTC. The structure might contain other [unspecified ? implementation-dependent ?] fields, for example to specify DST rules, but they should be designed such as when initialized to zero, the designated timezone holds a constant offset with UTC. The functions are: struct tm * zonetime ( const time_t * timer, const struct tzinfo * tz, /* if NULL, use local time */ struct tm * timeptr); /* if NULL, use internal */ time_t timezone ( struct tm * timeptr, const struct tzinfo * tz); /* if NULL, use local time */ The meaning of these functions should be obvious when I say that the usual functions can be expressed with them: time_t mktime( struct tm* timeptr ) { return timezone(timeptr, NULL); } time_t timegm( struct tm* timeptr ) { /* Here, I used the fact that tz_gmtoff is the first field of the structure, and that an partly-initialized structure is filled with zeroes */ return timezone(timeptr, &{0} ); } struct tm * localtime( const time_t * timer ) { return zonetime(timer, NULL, NULL); } struct tm * gmtime( const time_t * timer ) { return zonetime(timer, &{0}, NULL); } struct tm * localtime_r( const time_t * timer, struct tm* p ) { return zonetime(timer, NULL, p); } struct tm * gmtime_r( const time_t * timer, struct tm* p ) { return zonetime(timer, &{0}, p); } struct tm * CD1_zonetime( const time_t * timer, int value ) { return zonetime(timer, &{value}, p); } Another usefull call is when you parse a date with an explicit timezone indication, like in an e-mail client, and want to deal with: just transform "+hhmm" into a count of seconds sec, and call t = timezone(&tm, &{sec}); Of course, zonetime is allowed to return a NULL pointer if the given inputs are not valid (and so must be allowed localtime, as pointed out in PC-US0011#13), or if the offset between timer and UTC cannot be determined (as it is currently the case with gmtime); this is the same for timezone. (BTW: all names are just indications, they are open to discussion; timezone is probably a bad choice, since it was used in some versions of UNIX). Then, the type of tz_gmtoff need not be `long'; it needs only to be a numeric type capable of holding all the integers in the range [-89999, 89999] -- which would be enough to satisfy POSIX.1, plus another value meaning _LOCALTIME like in CD1. It might be worth adding a type gmtoff_t (or utcoff_t) for this. This is important if we think about the ways to retrieve the correct offset of the timezone to UTC *after* the call of the function, in particular in the cases of time zones with DST changes, like is local time. So an alternative model might be: struct tm * zonetime ( const time_t * timer, const struct tzinfo * tz, /* if NULL, use local time */ struct tm * timeptr; /* if NULL, use internal */ gmtoff_t * offsetptr); /* if NULL, do not store */ time_t timezone ( struct tm * timeptr, const struct tzinfo * tz; /* if NULL, use local time */ gmtoff_t * offsetptr); /* if NULL, do not store */ Another possibility is to add another function, that returns the offset of the time zone from a struct tm; like gmtoff_t gmtoff ( const struct tm *timeptr ); or perhaps gmtoff_t gmtoff ( const struct tm *timeptr, const struct tzinfo *tz ); (The Comittee might consider turning gmt to utc; but that is another story). Then, to extend this to handle "real" time zones, instead of just their offsets, we need to go a little further. C standard choose to *not* describe in details the behavior, and I feel it can be considered too heavy for some implementations (need to update, for example). Also, specifying point #4 might be very tricky in the context of a International Standard (like Israel or Saudian rules). So I believe point #4 should be left as a QoI issue (but should be available as natural extension, of course). OTOH, in the realm of POSIX, there is a quite natural extension to this mechanism which fits the need: struct tzinfo *tzalloc ( const char *tzspec); /* if NULL, use local time */ where tzspec has the same format as the contents of the POSIX.1 TZ environment variable (with the usual extensions, such as Olson's : prefix, allowed). This function yields a null pointer if given an invalid specification; the storage allocated by this function can be freed with `free'. Having a mechanism to dynamicaly allocate storage is required, because the most powerful implementations will store in struct tzinfo historical information, which grows with time, and thus requires this struct to be implemented as a VLA. This design fits pretty well in Olson's code, where it only adds small things. It is also pretty light (when compared to the actual <time.h> stuff), and eases upgrading current implementations, more limited in functionnalities (like "only USA rules are used" found in numerous std libc coming from the USA ;-), or even "I only know localtime" flavors). Please vendors that may have a different point of view do write it to me. It also have the property of *not* breaking current binary compatibility, as it does not extend the meaning of anything in struct tm (but merely stores the necessary informations in a different place). But please keep reading. However, it has a major flaw: it does not permit an explicit handling of leap seconds. And I do not how to solve this: I do not want to add new parameters (too expensive for almost no gain in day-to-day use), and I do not want to introduce a new kind of structure (too complex I believe). (Another problem is that I do not know how to express the rules about leap seconds in the context of the library, while allowing an implementation to optionaly support it... see the point of Paul Eggert in his PC about mkxtime on +1 day on June 30th, 1997, midnight, which in the current draft doesn't yield July 1st). For the leapseconds, there is first a very delicate inter- operability problem, since POSIX.1 request time_t value to *not* record any leap second information at all. So I have no practical solution here, outside the one proposed in CD1, i.e. to extend struct tm to have a new field storing this information. This (and another open problem, how to print the actual time zone name) leads to another question: why does the Comittee introduced a new type for the time functions, instead of extending struct tm? In the ausence of the answer to this question, I stay with an open alternative on how to end this proposal: a) extending struct tm explicitely (adding fields), which perhaps might require using tm_isdst field as a flag to request/signal C9X behavior (tm_isdst is superseeded by the informations in the struct tzinfo) b) requesting indirectly implementations to insert the new fields to support the whole spec, but keeping them invisible to the "conforming" user (as do tz package or BSD right now) c) not extending struct tm and adding some more arguments to the new functions (or additional functions) to collect other informations and of course d) CD1's solution, creating a structure for extending (still a correct possibility), replacing the tm_ext stuff with a pointer to a tzinfo struct Waiting for your welcome and certainly useful comments, Antoine