comments on draft ISO C9x changes to <time.h>

Antoine Leca Antoine.Leca at renault.fr
Mon Jun 15 07:46:39 UTC 1998


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



More information about the tz mailing list