Re: bug in mktime() normalization?
Date: Tue, 19 Aug 97 14:13:13 -0400 From: Tom Peterson (USG) <tomp@zk3.dec.com> In mktime(), or rather time2(), I noticed that a change was made a while back which removed the following seconds normalization: ! if (yourtm.tm_sec >= SECSPERMIN + 2 || yourtm.tm_sec < 0) ! normalize(&yourtm.tm_min, &yourtm.tm_sec, SECSPERMIN); Instead, these seconds are now saved aside into saved_seconds and only added back in after an appropriate match is found using the remaining normalized data. This was to handle leap seconds properly. On systems that support leap seocnds, it's incorrect to normalize the seconds count first, since you don't know how many seconds there are per minute until you determine which minutes you're talking about. For example, with leap second support, mktime should adjust an input value of ``1997-07-01 00:00:-1'' UTC (i.e. tm_sec == -1) so that it becomes 1997-06-30 23:59:60 UTC, but with the code above, mktime generated 1997-06-30 23:59:59 UTC which is incorrect. Here's what UNIX98 has to say regarding this:
From your quote it appears that UNIX98 says the same thing that the C Standard says, which is to say, not much. The C Standard spec for mktime is terribly ambiguous and you've found one of the ambiguities. The C Standard says that mktime can return -1 if attempted in the ``spring forward gap'', but it doesn't say how mktime should determine whether one is in the gap.
Any user program that messes in this area is relying on behavior that is not guaranteed by any standard. For what it's worth, the GNU C library's mktime agrees with the tz mktime on your example, whereas Solaris 2.5.1 and BSD/OS 3.0 have the other reasonable interpretation (i.e. both mktime invocations yield 575002800).
Here's what UNIX98 has to say regarding this:
From your quote it appears that UNIX98 says the same thing that the C Standard says, which is to say, not much. The C Standard spec for mktime is terribly ambiguous and you've found one of the ambiguities. The C Standard says that mktime can return -1 if attempted in the ``spring forward gap'', but it doesn't say how mktime should determine whether one is in the gap.
Any user program that messes in this area is relying on behavior that is not guaranteed by any standard. For what it's worth, the GNU C library's mktime agrees with the tz mktime on your example, whereas Solaris 2.5.1 and BSD/OS 3.0 have the other reasonable interpretation (i.e. both mktime invocations yield 575002800).
Back when I worked at Sun on the timezone code, this issue came up and I could have changed mktime() to return -1 in this case. If I remember right, the standard says mktime() returns -1 if the time cannot be represented and, after thinking about, I concluded that the "spring forward gap" could be represented. As I saw it, the only time that could not be represented was time outside the range of a time_t. I didn't think the standards explicitly mentioned this topic, but now that I no longer work at Sun and don't even work on things remotely related to what I was doing before, I don't see the current standards much. alan perry
This was to handle leap seconds properly. On systems that support leap seocnds, it's incorrect to normalize the seconds count first, since you don't know how many seconds there are per minute until you determine which minutes you're talking about.
Similarly, it seems we don't really know which minute we're talking about until the excess seconds are factored in (enter chicken & egg). Rather than rejecting a tm struct simply because when we set aside it's seconds, it doesn't match a valid tm, I wonder if it's possible to do "something like" the following: 1) save the seconds aside 2) normalize rest of tm struct without seconds 3) if resulting tm falls inside a gap: a) determine the last valid tm before the gap b) save the difference (hours, minutes, etc) between our tm and last valid tm aside 4) using last valid tm or valid resulting tm from above (we'll call this tm1), create another tm (tm2) factoring in the saved seconds 5) scan the interval between tm1 and tm2 and factor in any leap seconds or additional gaps that that fall between them 6) add any difference from (3b) above 7) attempt tm match again & see if we fall into a gap a) if so, reject tm b) if not, tm is good I'm sure there's plenty of bugs in this algorithm, but perhaps the idea holds merit.
The C Standard says that mktime can return -1 if attempted in the ``spring forward gap'', but it doesn't say how mktime should determine whether one is in the gap.
I agree that the standards are vague about how to determine if a tm struct is in the gap. However, it seems clear to me that a tm_sec value that adds hours or even days to a tm struct should be considered before assuming the time falls between a transition gap.
Any user program that messes in this area is relying on behavior that is not guaranteed by any standard.
True, but regarding "in the gap" handling, the standards do seem to lean in the direction of falling back or forward to the previous or next valid time. For example, the VSX4/XPG4 and PCTS test suites assume this. For these standards, item (7b) in algorithm above would be replaced with falling back or moving forward according to the value of tm_isdst.
For what it's worth, the GNU C library's mktime agrees with the tz mktime on your example, whereas Solaris 2.5.1 and BSD/OS 3.0 have the other reasonable interpretation (i.e. both mktime invocations yield 575002800).
You can also add DIGITAL UNIX to the 575002800 category above. Not sure about other UNIX flavors. - Tom
participants (3)
-
Alan Perry -
Paul Eggert -
Tom Peterson