two responses by Clive Feather to my comments on draft C9x <time.h>

Paul Eggert eggert at
Sat Jun 20 00:11:45 UTC 1998

[forwarded with permission]

------- Start of forwarded message -------
Date: Thu, 18 Jun 1998 17:32:40 +0100
From: "Clive D.W. Feather" <clive at>
Subject: My Response to PC-US0010 (Eggert)


Firstly, let me cover the history of these changes. Some years ago I and
some friends identified a number of abiguities and discrepencies in
<time.h>, which we described in various Defect Reports. Given the
opportunity to make changes that C9X represented, I wrote a formal
proposal to fix the problems I saw. After WG14 made some changes, this
proposal was accepted. It was open to Paul, or anyone else, to write and
submit a formal proposal for any problems he saw. This explains, I hope,
why there are no reentrant versions of the functions in <time.h> - no-
one expressed any desire to have them added (or if they did, they didn't
do anything about it).

7.16.1 para 3:

Replacing tm_zone with tm_gmtoff (except that it ought to be called
tm_utcoff) that is measured in seconds: I don't have any objections to
this. Appropriate other values need to be multiplied by 60. Um, the type
needs to be changed to "long" to give it sufficient range.

The tm_ext and tm_extlen members are not a kludge, but rather an anti-
kludge ! The issue is not one of incomplete specifications, but rather
of future-proofing. Suppose that C0X (the next revision after C9X)
wishes to make a change to struct tmx. If it added extra fields to the
structure, existing objects linked with a new library might access
beyond the end of the object. It isn't sufficient to use the tm_version
field for this, because a struct tmx with version 2 in the library might
be copied to a struct tmx in the code which is only large enough for the
version 1 fields.

There are a number of ways to solve this problem (for example, you could
add a tm_len field to the struct tmx, which is required to be set to the
size of the struct tmx object as declared). WG14 felt the adopted
proposal was optimal. para 4:

I don't understand your objection. The paragraph you cite means, in

    the normalization process shall not alter a broken-down time
    that was generated by the normalization process

If the implementation sets tm_isdst to 0 or 1 when normalizing, then it
will remain that way a second time. If the implementation doesn't, your
program is in trouble already.

I think you're misunderstanding the process. To normalize a struct tm,
the implementation should do the equivalent of:
- - copy it to a struct tmx
- - set the additional fields and tm_isdst as described
- - normalize the result
- - copy the relevant fields back, except for tm_isdst which is just set
  to negative, zero, or positive as needed.
Nothing stops the implementation setting tm_isdst to 1. The
"implementation defined positive value" is used in the struct tmx
interpretation of this field, and need not be preserved in the struct tm
version (since the normalization process should regenerate it each
time). para 3:

If tm_isdst is negative the zone information is not available, so the
implementation should assume that "local time" has allowed for any DST
in effect - in this case, will set X2 to 0 in the algorithm. If
tm_isdst is extremely large the behaviour is undefined - see
para 2. para 1:

A negative tm_isdst, in both struct tm and struct tmx, means
"unavailable". If POSIX.1 tries to give it a different meaning, POSIX.1
is broken. Note that you never need to handle negative DST: take all the
UTC offsets that the locale uses, and make the most negative the base
time with all others being DST (in other words, replace negative
"summer" time with positive "winter" time). para 2:

These limits were chosen to allow calculations to be done in longs
without having to make excessive effort to avoid overflow. Your
statement that you can't calculate today's date is wrong:

    tm_mday = time_t_now / 86400;
    tm_sec  = time_t_now % 86400

However, I do understand your concern. I have no objection in principle
to changing these limits, which express a tradeoff between implementer
and user; it is up to WG14 as a whole to decide whether to relax or
remove them. para 3:

I'll address each paragraph separately.

(1) You discuss adding 1 day when there's a leap second present. The
algorithm assumes that "day" means 86400 seconds at all times, "minute"
means 60 seconds, and so on. If you can construct an alternative
algorithm I'll be please to look at it.

(2) S and D *are* determined - while they are implementation-defined in
some circumstances (those when X1 and X2 are used), the implemenation
should only be able to pick one value for any given unambiguous input
and environment.

(3) It is C code (as should be clear from the typeface). There is no
possibility of overflow if the limits of paragraph 2 are kept to, and
there are no promotions or conversions happening as far as I can tell.

(4) There was a typo: it should read:

    determined by the implementation, or
    (tm_isdst >= 0 ? tm_isdst : 0)
    if the offset cannot be determined.

(5) I wrestled with this problem myself and failed to come up with a
good answer. Effectively D and S are the TAI date and time since some
epoch. X1 is the leap second count at the determined time, and X2 is the
local offset from UTC (for example, this might be specified by the TZ
environment variable and whether DST has taken effect). Both of these
are, as you say, dependent on D and S. Again, can you find a better way
of expressing it ?

(6) Yes, an error does seem to have crept into the definition of D. The
first line should read:

    D = Y * 365 + QUOT(Z,400) * 97 + REM(Z,400) / 4 - REM(Z,400) / 100 +

The rest of the expression is correct (including the Y at the start).

The zonetime function is the struct tmx version of localtime and gmtime,
but instead of offering only 2 choices of zone it offers any zone.

You're right that the definition should read TAI-UTC, not UT1-UTC. My

7.16.1 para 2:

The limits of 14400 are correct. This allows you to adjust by up to 10
days (not 1 day) in an unnormalized time without risking accidentally
using _LOCALTIME. Ideally _LOCALTIME would be something like INT_MAX or
LONG_MIN. para 3:

The macros can't hit overflow with the limits of paragraph 2. Of course,
if these limits are changed the macro needs redesigning. It's a pity
that C has no defined way to round integer division towards minus
infinity and a modulo function that doesn't have a singularity at 0. I
know Z can be redefined more simply, but I consider such code too clever
for its own good in a definitive document like the Standard.


While there are some minor changes that need making, and a couple of
others that could be made, the new <time.h> is still IMO better than the
old one.

It's up to WG14 next week whether to allow the more significant changes
that you're asking for, such as a different normalization algorithm.
However, if they are ready to consider it, I'm happy to work with you
and others to come up with a proposal that is likely to be acceptable.

- -- 
Clive D.W. Feather   | Regulation Officer, LINX | Work: <clive at>
Tel: +44 1733 705000 | (on secondment from      | Home: <cdwf at>
Fax: +44 1733 353929 |  Demon Internet)         | <>
Written on my laptop; please observe the Reply-To address
------- End of forwarded message -------

------- Start of forwarded message -------
Date: Fri, 19 Jun 1998 17:23:06 +0100
From: "Clive D.W. Feather" <clive at>
Subject: Re: My Response to PC-US0010 (Eggert)


I've been doing a little more thought about the normalisation process.
What I did was wrote half the process - convert unnormalized time to a
number of TAI seconds since some epoch. Rather than writing the other
half and risking inconsistencies, it seemed better to simply express it
as requiring the normalized time to produce the same result. What I was
trying to do originally was ensure that the Standard explains exactly
what is meant by (say) day 37 of month 14 of year 1234 (especially when
there may or may not be a leap year involved), what happens in 1752 or
1582 or whenever, and so on. To a large extent I don't care what the
answers are, so long as there *are* answers. Note that C89 can be
reasonably read as generating nonsense answers if there is any
inconsistency in the input data.

You've made me spot one gap in my logic. If you set tm_leapsecs to 0
before normalizing, the time is effectively TAI, while if you set it to
_NO_LEAP_SECONDS it is effectively UTC. The output, however, is always
UTC (unless there is no leap second database, in which case tm_leapsecs
is set to _NO_LEAP_SECONDS). There's no trivial way to convert it to
TAI, unless you know of an algorithm. Do we need a _USE_TAI value as
well (indicating that the result should be TAI) ?

- -- 
Clive D.W. Feather   | Regulation Officer, LINX | Work: <clive at>
Tel: +44 1733 705000 | (on secondment from      | Home: <cdwf at>
Fax: +44 1733 353929 |  Demon Internet)         | <>
Written on my laptop; please observe the Reply-To address
------- End of forwarded message -------

More information about the tz mailing list