Newsgroups: comp.std.c Path: utzoo!censor!geac!sq!msb From: msb@sq.sq.com (Mark Brader) Subject: Re: Questions about mktime() Message-ID: <1991Jan20.205039.7056@sq.sq.com> Organization: SoftQuad Inc., Toronto, Canada References: <1991Jan18.225155.7310@druid.uucp> Date: Sun, 20 Jan 91 20:50:39 GMT Lines: 101 This started out as a private response, hence the references to the original poster as "you". I decided that the issues were of sufficient general interest that I'm posting even though I don't have all the details worked out. There are a suprising number of tricky points here, as in all calendar-related problems, and it seems worthwhile to at least call attention to them. > I am using the tm_year, tm_mon tm_mday, tm_hour, tm_min and tm_sec > members of the tm structure to calculate the number of seconds. If > the information in the other members happens to conflict with the > result must I return an error or can I safely ignore the others? > According to K&R2 the argument to mktime is not a const. Does this > mean that I can modify the structure? It seems to me that it would > be convenient to reset it with localtime() to set the other members > to sane values. You are, in fact, *required* to do almost precisely this. Specifically, the initial values of tm_wday and tm_yday are required to be ignored, and the final values of all members are required to be "forced to the proper range" -- thus a date of, say, October 92nd, 1990, is acceptable on input but mktime() must change tm_mon, tm_mday, and tm_year to show January 1st, 1991. One way to do this is to do exactly what you propose above: first compute the numerical time (of type time_t), then call localtime() on it. The tricky part here is that you have to deal with really bizarre "dates" like the -2000th day of the 200th month of the year, and tm_mday to be in the right range for the month that you end up with. It may be easier to deal with these directly first. Work from the largest units down to the smallest, so that you get the month lengths right. For example, you could begin with the months by doing something like: str.tm_year += str.tm_mon/12; /* but see just below */ str.tm_mon %= 12; However, you have to deal with negative tm_mon too, and / and % aren't well-defined for negative arguments, so this isn't quite sufficient. Then, iteratively, while tm_mday is too large, subtract from it the length of the current month and advance to the next month -- remembering to update tm_year too if necessary, and to allow for leap years in computing the length of the month. A cleverer but more complex algorithm would avoid iterating over too many years. Again, negative tm_mday must also be handled. At this point it is straightforward to compute the numerical time and call localtime(), which will resolve any bizarreness in the time of day. The motivation for including the "bizarre date" functionality, by the way, is that it means that mktime() can be used for calculating what the date that is, say, 1000 days before or after some other particular date. > but I can't rely on the tm_isdst being correct, or can I? This is the other tricky part -- you have to handle it yourself. The rule is that if tm_isdst is positive, you have to assume Daylight Saving Time; if zero, you have to assume Standard Time; if negative, you have to "attempt to determine whether Daylight Saving Time is in effect for the specific time". It says "attempt" because of systems that don't understand time changes in the first place -- it would be a very low quality implementation that would just give up. In the January 1st example above, assume now that the time indicated is 12:30 am, and tm_isdst equals 1. Assuming that the geographic location is Canada rather than, say, New Zealand (where January is in the summer), then mktime() must correct tm_isdst to 0, and set the time and date to 11:30 pm, December 31st, 1990. One way to do this would be to first assume Standard Time, adjusting the computed time by 3600 seconds if tm_isdst was positive; then call localtime() on the computed time, and see what it gives for tm_isdst; if it's 1, adjust the computed time by 3600 seconds and call localtime() again. If tm_isdst was originally positive or zero, you're done. But if tm_isdst was originally negative, you now have to check whether it's changed between the two calls to localtime()! If so, you were given a time near a clock change and asked to determine whether it was Daylight Saving or Standard. If the time was actually during the hour that's skipped when the clock moves forward, then you were given a "calendar time that cannot be represented" and you should return (time_t)-1. (For this reason, robust programs should avoid calling mktime() with a time that might fall into that interval and with tm_isdst negative.) The ambiguous times that are repeated when the clock moves backwards are also a problem, but here the Standard is silent and it would seem you're free to choose either option if one of these times was specified with tm_isdst negative. (A good quality implementation would document what it does with this case, and perhaps even provide a diagnostic warning.) I recommend buying a copy of the Standard. (US$56 including shipping from ANSI, something similar from Global Engineering Documents, see the FAQ list for how to order.) -- Mark Brader "It is impractical for the standard to attempt to SoftQuad Inc., Toronto constrain the behavior of code that does not obey utzoo!sq!msb, msb@sq.com the constraints of the standard." -- Doug Gwyn This article is in the public domain.