Questions about mktime()

Mark Brader msb at sq.sq.com
Mon Jan 21 07:50:39 AEST 1991


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 at sq.com      the constraints of the standard."  -- Doug Gwyn

This article is in the public domain.



More information about the Comp.std.c mailing list