two responses by Clive Feather to my comments on draft C9x <time.h>
[forwarded with permission] ------- Start of forwarded message ------- Date: Thu, 18 Jun 1998 17:32:40 +0100 From: "Clive D.W. Feather" <clive@on-the-train.demon.co.uk> 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. 7.16.2.3 para 4: I don't understand your objection. The paragraph you cite means, in effect: 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). 7.16.2.4 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, 7.16.2.6 will set X2 to 0 in the algorithm. If tm_isdst is extremely large the behaviour is undefined - see 7.16.2.6 para 2. 7.16.2.6 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). 7.16.2.6 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. 7.16.2.6 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). 7.16.3.5: 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 error. 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. 7.16.2.6 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. Conclusion: 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@linx.org> Tel: +44 1733 705000 | (on secondment from | Home: <cdwf@i.am> Fax: +44 1733 353929 | Demon Internet) | <http://i.am/davros> 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@on-the-train.demon.co.uk> 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@linx.org> Tel: +44 1733 705000 | (on secondment from | Home: <cdwf@i.am> Fax: +44 1733 353929 | Demon Internet) | <http://i.am/davros> Written on my laptop; please observe the Reply-To address ------- End of forwarded message -------
participants (1)
-
Paul Eggert