[comp.std.c] Questions about mktime

darcy@druid.uucp (D'Arcy J.M. Cain) (01/19/91)

I am trying to write an ANSI compatible mktime() and I have a few
questions.

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.

Is there a simple way to adjust for the time zone?  I considered
    t += timezone;
but that doesn't handle daylight saving time.  I then considered
    t += tp->tm_isdst ? altzone : timezone;
but I can't rely on the tm_isdst being correct, or can I?  My
feeling is that the user can throw in whatever date and time and
mktime() should be able to figure out time zones.

Any help appreciated.

-- 
D'Arcy J.M. Cain (darcy@druid)     |
D'Arcy Cain Consulting             |   There's no government
West Hill, Ontario, Canada         |   like no government!
+1 416 281 6094                    |

msb@sq.sq.com (Mark Brader) (01/21/91)

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.

datangua@watmath.waterloo.edu (David Tanguay) (01/21/91)

In article <1991Jan20.205039.7056@sq.sq.com> msb@sq.sq.com (Mark Brader) writes:
|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".
[...]
|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.

Now what do we do with the double DST that Newfoundland has/had?
Not only do you have to figure out if DST is in effect, you have to figure
out exactly how much of a time shift DST represents.
-- 
David Tanguay            Software Development Group, University of Waterloo

msb@sq.sq.com (Mark Brader) (01/23/91)

I wrote:
| 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".
and then referred to "adjust[ing] the computed time by 3600 seconds".

This drew the followup:
> Now what do we do with the double DST that Newfoundland has/had?

It's "had", by the way; they only tried it for (the usual Daylight
Saving Time period during) one year.

The Standard says that "the local time zone and Daylight Saving Time
are implementation-defined" and makes no distinction between different
positive values of tm_isdst.  If I was an implementer having to deal
with Newfoundland, I'd be inclined to say that localtime() would assign
1 to tm_isdst for a 1-hour advance, and 2 for a 2-hour advance.  This is
clearly Standard-conforming.

I'd also have it that mktime() would assume that a time specified with
tm_isdst == 1 was 1 hour advanced, and one with tm_isdst >= 2 was 2 hours
advanced.  It is *not* clear to me that this would be Standard-conforming,
as the actual wording about mktime() and tm_isdst is essentially just
what I said above -- and even that is only in a footnote, thus not
technically part of the Standard.  But I think it is in the spirit of
the Standard's intent, and if this approach was submitted for an
Interpretation Ruling, then I'd expect it to pass.

During the public reviews, I requested the addition of language to
cover this situation, something along the lines of "an implementation
may define different positive tm_isdst values associated with different
Daylight Saving Times that may be in effect."  This was rejected, as I
recall, on the grounds that "the Committee feels that this is clear
enough as is."

Of course, the other tricky thing for the implementer is that the
algorithms and data structures used have to be able to allow for
multiple different DSTs.  But there's nothing intrinsically difficult
about that; the only problem is thinking of it in the first place.
-- 
Mark Brader		    "It can be amusing, even if painful, to watch the
SoftQuad Inc., Toronto	     ethnocentrism of those who are convinced their
utzoo!sq!msb, msb@sq.com     local standards are universal."	-- Tom Chapin

This article is in the public domain.

datangua@watmath.waterloo.edu (David Tanguay) (01/23/91)

|> Now what do we do with the double DST that Newfoundland has/had?
|
|It's "had", by the way; they only tried it for (the usual Daylight
|Saving Time period during) one year.
|
|If I was an implementer having to deal
|with Newfoundland, I'd be inclined to say that localtime() would assign
|1 to tm_isdst for a 1-hour advance, and 2 for a 2-hour advance.

OK until Newfoundland tries a 90 minute DST. How about 2 meaning "figure
it out", e.g., with an environment variable, or an OS call.
Given that they were dissatisfied enough with 1 hour to try 2 hours, I
think that 90 minutes is a possibility. It would even put them in phase
with the rest of N.A. :-)
-- 
David Tanguay            Software Development Group, University of Waterloo

gwc@root.co.uk (Geoff Clare) (01/23/91)

In <1991Jan20.205039.7056@sq.sq.com> msb@sq.sq.com (Mark Brader) writes:

>  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.

I don't think this is right.  My understanding is that a "calendar
time that cannot be represented" refers to the case where the resulting
return value from mktime() would be outside the range of the time_t type.
The context in which the text appears seems to support this:

    "Returns
    
       "The mktime function returns the specified calendar time
    encoded as a value of type time_t.  If the calendar time cannot
    be represented, the function returns the value (time_t)-1."

The paragraph talks specifically about the return value of type time_t,
so it is clear to me that "cannot be represented" refers to what values
can fit in a time_t.  I think the non-existent times Mark refers to are
meant to be treated the same way as ambiguous times.

>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.

I agree that either result is acceptable in this case, but the standard
isn't really silent on this.  It says,

    "A negative value for tm_isdst causes the mktime function to attempt
    to determine whether Daylight Saving Time is in effect for the
    specified time."

The words "attempt to determine", in my view, are there specifically to
cater for the problems at change-over times.  I believe this applies
both to the ambiguous times when changing from dst to std, and to the
non-existent times when changing from std to dst.  In these cases
mktime() is allowed to treat input times with a negative tm_isdst as
either standard or daylight time.  I.e. there are two possible return
values for such times, and both are equally valid.  From the user's
point of view, getting a sensible return value is greatly preferable
to getting (time_t)-1.

-- 
Geoff Clare <gwc@root.co.uk>  (Dumb American mailers: ...!uunet!root.co.uk!gwc)
UniSoft Limited, London, England.   Tel: +44 71 729 3773   Fax: +44 71 729 3273

gwyn@smoke.brl.mil (Doug Gwyn) (01/24/91)

In article <1991Jan23.052427.17720@sq.sq.com> msb@sq.sq.com (Mark Brader) writes:
>The Standard says that "the local time zone and Daylight Saving Time
>are implementation-defined" and makes no distinction between different
>positive values of tm_isdst.  If I was an implementer having to deal
>with Newfoundland, I'd be inclined to say that localtime() would assign
>1 to tm_isdst for a 1-hour advance, and 2 for a 2-hour advance.  This is
>clearly Standard-conforming.

There is no need to do this, if it is clear what the DST rules are (for
all relevant time periods) for the given local time zone.  I would say
that the time zone should really have been given a different name if
one portion of it required local clocks to be set differently from
other portions of the "same" time zone.  Note that we have a similar
situation with certain U.S. communities that passed local ordinances
exempting them from the national DST laws.  I feel that such variations
are better dealt with via timezone name strings than special conventions
about the values of tm_isdst.

diamond@jit345.swstokyo.dec.com (Norman Diamond) (01/24/91)

[Attributions were already missing from the message I'm following up to; sorry]

>>> Now what do we do with the double DST that Newfoundland has/had?
>>
>>It's "had", by the way; they only tried it for (the usual Daylight
>>Saving Time period during) one year.

Doesn't matter.  A user can still call the library with a calendar time
that fell in that range.

(I understand that some country had a 30 minute DST for a while too.)
--
Norman Diamond       diamond@tkov50.enet.dec.com
If this were the company's opinion, I wouldn't be allowed to post it.

msb@sq.sq.com (Mark Brader) (01/24/91)

> > If I was an implementer having to deal
> > with Newfoundland, I'd be inclined to say that localtime() would assign
> > 1 to tm_isdst for a 1-hour advance, and 2 for a 2-hour advance.
> 
> OK until Newfoundland tries a 90 minute DST. How about 2 meaning "figure
> it out", e.g., with an environment variable, or an OS call.

That's no good; you can't ask people to check that sort of thing in a
portable program.  The answer is that, when they decide to implement
90-minute DST, you release a new version of your C implementation(*) so
that, during 90-minute DST, tm_isdst is 3.  Or so that tm_isdst is 2, 3, 4
respectively for 60, 90, and 120 minutes.  It doesn't really matter which
-- provided that localtime() and mktime() agree on what the convention is,
and that, if mktime() is handed a positive tm_isdst value that localtime()
wouldn't return, it takes some sensible, documented action that assumes
some flavor of DST.

(*) Better yet, design the implementation in the first place with future
changes of this sort in mind.  The various DST algorithms are best chosen
from a data file anyway, so that they can be changed when someone changes
the DST dates.  So, design that file so that it also includes the mapping
from all known DST offsets to tm_isdst values.  Then when they introduce
90-minute DST, you just add a couple of lines to the file.

Thanks for raising the matter; this is actually an important point.
Even if you don't have to deal with Newfoundland Time, what would you
do if *your* place of abode decided to have 2-hour DST for, say, 2 months
in the summer?  It's happened before...

-- 
Mark Brader			"Computers get paid to extract relevant
SoftQuad Inc., Toronto		 information from files; people should not
utzoo!sq!msb, msb@sq.com	 have to do such mundane tasks."  -- Ian Darwin

This article is in the public domain.

msb@sq.sq.com (Mark Brader) (01/29/91)

> > 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.
> 
> I don't think this is right.  My understanding is that a "calendar
> time that cannot be represented" refers to the case where the resulting
> return value from mktime() would be outside the range of the time_t type.

Well, it certainly does refer to that case; the question is whether it
refers to the skipped-hour case as well.  I would argue that it does,
on the grounds that an impossible time, ipso facto, cannot be represented.
But it might also be argued that an impossible time is merely a special
case of one of the values being out of range, and therefore must be
handled without a -1 being returned.  The wording referring specifically
to the "calendar time" hints at the latter interpretation, but I don't
think this is conclusive.  A ruling would be a good idea.

> > 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.
> 
> I agree that either result is acceptable in this case, but the standard
> isn't really silent on this.  It says,
>     "A negative value for tm_isdst causes the mktime function to attempt
>     to determine whether Daylight Saving Time is in effect for the
>     specified time."
> The words "attempt to determine", in my view, are there specifically to
> cater for the problems at change-over times. ...

Certainly not.  Systems that don't know about clock changes at all, where
localtime() will return tm_isdst negative, are also catered for by this.
My assumption was that this was intended to refer *only* to such systems.

> From the user's
> point of view, getting a sensible return value is greatly preferable
> to getting (time_t)-1.

I disagree.  From *my* point of view as a user, if my system knows about
DST, then my giving tm_isdst<0 to mktime() is an assertion that the time
I gave was wall clock time, and if I give it a time that doesn't exist,
I want it to tell me so.  To me it's just like a division by zero.


| > The Standard says that "the local time zone and Daylight Saving Time
| > are implementation-defined" and makes no distinction between different
| > positive values of tm_isdst.  If I was an implementer having to deal
| > with Newfoundland, I'd be inclined to say that localtime() would assign
| > 1 to tm_isdst for a 1-hour advance, and 2 for a 2-hour advance.  This is
| > clearly Standard-conforming.
| 
| There is no need to do this, if it is clear what the DST rules are (for
| all relevant time periods) for the given local time zone.

It appears to be Standard-conforming to used a single positive tm_isdst
value, but you get improved functionality by using multiple such values,
so I would expect high-quality implementations to do so.  Still considering
Newfoundland, they had 2-hour DST in 1988 and 1-hour DST in other years.
Suppose that the applicable time is Newfoundland Time, and time_t t
contains a calendar time in the summer of 1988, and I say:

	struct tm s = localtime(t);
	s.tm_year++;		/* advance by one year */
	s.tm_isdst = -1;	/* negative --> "please determine DST" */
	t = mktime(s);

Then I expect t to have increased by (365*24-1)*3600, allowing for the
change in DST rules between the two years.  On the other hand, if I omit
the statement "s.tm_isdst = -1;", then I expect t to have increased by
(365*24)*3600.  This is analogous to the behavior that the standard
requires to occur if, rather than s.tm_year++, I did s.tm_mon++ and
crossed a clock change in doing so.  But if the implementation uses a
single positive tm_isdst value for different DST's, it can't behave
that way.

| I would say
| that the time zone should really have been given a different name if
| one portion of it required local clocks to be set differently from
| other portions of the "same" time zone. ...

Well, clearly, however you tell your system which time zone is to be used,
you also have to tell it in some way what DST rules apply.  Besides the
examples of places that don't use DST, or didn't use it during certain
years, there are also places that used it for abnormal periods during
certain years, like most of the USA in 1974.  And part of Newfoundland is
in Atlantic Time, and thus differed from the rest of the Atlantic Time
zone during the DST period during 1988.  It's not obvious to me that the
time zone name is the best way to pass such information, but it's certainly
one reasonable way.

Oh, yes, for curious foreigners: Newfoundland Standard Time is 3 hours
30 minutes west of UTC/GMT; Atlantic Standard Time, 4 hours west.
-- 
Mark Brader			"[This computation] assumed that everything
SoftQuad Inc., Toronto		 would work, a happy state of affairs found
utzoo!sq!msb, msb@sq.com	 only in fiction."	-- Tom Clancy

This article is in the public domain.

msb@sq.sq.com (Mark Brader) (01/29/91)

Oops.  I just posted an article containing this example:
> 	struct tm s = localtime(t);
> 	s.tm_year++;		/* advance by one year */
> 	s.tm_isdst = -1;	/* negative --> "please determine DST" */
> 	t = mktime(s);

This should of course be:
	struct tm *s = localtime(&t);
	s->tm_year++;		/* advance by one year */
	s->tm_isdst = -1;	/* negative --> "please determine DST" */
	t = mktime(s);

Sorry about that.  I was forgetting, of course, that localtime() predates
the passing of types wider than int as arguments.
-- 
Mark Brader, SoftQuad Inc., Toronto, utzoo!sq!msb, msb@sq.com
	"I'm a little worried about the bug-eater," she said.  "We're embedded
	in bugs, have you noticed?"		-- Niven, "The Integral Trees"

This article is in the public domain.

darcy@druid.uucp (D'Arcy J.M. Cain) (01/30/91)

I previously asked what mktime() was supposed to do.  Thanks for all the
responses.  However, changing the code to incorporate the suggestions
has led to a few more questions.

I understand that tm_mday of -1 means the last day of the previous month.
Obvously 1 would be the first of the current month so where does 0 lie?
Armstrong Day if the month is Archemedies?  :-)

I have to normalize day and month, but how about hours minutes and seconds?
It seems reasonable to do that as well.  I'm thinking of doing it anyway as
it wouldn't break anything if normal times were sent.  If I do this I guess
I should normalize times before day and month right?

As soon as I have something finished I will post the code.

-- 
D'Arcy J.M. Cain (darcy@druid)     |
D'Arcy Cain Consulting             |   There's no government
West Hill, Ontario, Canada         |   like no government!
+1 416 281 6094                    |

steve@taumet.com (Stephen Clamage) (02/01/91)

darcy@druid.uucp (D'Arcy J.M. Cain) writes:

>I understand that tm_mday of -1 means the last day of the previous month.
>Obvously 1 would be the first of the current month so where does 0 lie?

No, the valid range of tm_mday is 1 to 31.  Thus in a call to mktime,
a value of 0 must refer to the day before the first of the given month --
that is, the last day of the previous month.  Then -1 is the next-to-last
day of the previous month.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

karl@ima.isc.com (Karl Heuer) (02/03/91)

>[So, what should mktime() return in the neighborhood of DST changeover,
>when the argument represents a clock time which is (depending on season)
>impossible or ambiguous?]

Personally, I think we should've handled DST in the same way as leap days and
leap seconds: just add another hour to the legal range of the variable.  Then
Spring Changeover Day would end at 23:00, and Fall Changeover Day would end at
25:00.

It is left as an exercise to the reader to determine how 24:30 is denoted in
those backward countries that use 12-hour clocks, such as the United States.

Karl W. Z. Heuer (karl@ima.isc.com or uunet!ima!karl), The Walking Lint

darcy@druid.uucp (D'Arcy J.M. Cain) (02/08/91)

Many thanks to everyone for their comments and suggestions on the mktime
function.  I have finished a version of mktime which I am posting here
(it's only about 150 lines) to see if anyone has any further comments
or suggestions.  Note the CHECK_INVALID manifest constant is used to
include code to check for the invalid times that occur for one hour
each year in the spring - e.g. 0230h on the first Sunday in April.  I
personally don't include that #define.  I prefer mktime to try to return
some sort of time without having to pre-check for that period.  I did
however test both versions.  sending the function 3 periods around
both changovers gave the following result without CHECK_DEFINE:

Sun Apr  7 01:30:00 1991 isdst: 0
Sun Apr  7 03:30:00 1991 isdst: 1 - NOTE: Extra hour added
Sun Apr  7 03:30:00 1991 isdst: 1
Sun Oct 27 01:30:00 1991 isdst: 1
Sun Oct 27 02:30:00 1991 isdst: 0
Sun Oct 27 03:30:00 1991 isdst: 0

With CHECK_DEFINE included I got the following:

Sun Apr  7 01:30:00 1991 isdst: 0
Wed Dec 31 18:59:59 1969 isdst: -1 - NOTE: Time corresponds to -1 return
Sun Apr  7 03:30:00 1991 isdst: 1
Sun Oct 27 01:30:00 1991 isdst: 1
Sun Oct 27 02:30:00 1991 isdst: 0
Sun Oct 27 03:30:00 1991 isdst: 0

Anyway, here is the code.

------------------------------ cut here --------------------------------
/*
mktime
Written by D'Arcy J.M. Cain

Many thanks to the various netters who helped with the various issues
involved here.  In particular thanks, in no particular order, to:
  Mark Brader (msb@sq.sq.com)
  David Tanguay (datangua@watmath.waterloo.edu)
  Geoff Clare (gwc@root.co.uk)
  Doug Gwyn (gwyn@smoke.brl.mil)

mktime converts the local time in the structure *tp into calendar time.

*/

#include    <types.h>
#include    <time.h>

#ifdef      __MSDOS__
#define		altzone		((time_t)(timezone + 3600))
#endif

/* CAUTION: side effects */
#define		is_leap(x)	(((!((x)%4)&&(x)%100)||!(((x)+1900)%400))?1:0)

static int  yday_size[2][12] = {
    { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
    { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
};

static int	mon_size[2][12] = {
	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
	{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};

/*e.g: normalize(&tm_secs, &tm_hour, 60); */
static void	normalize(int *x, int *y, int f)
{
	if (*x > f)
	{
		*y += *x/f;
		*x %= f;
	}

	/* can't rely on modulus for negative numbers */
	while (*x < 0)
	{
		*x += f;
		(*y)--;
	}
}

time_t	mktime(struct tm *tp)
{
    long unsigned	k;
    time_t			t, t1;
	struct tm		tm;

    tzset();					/* set up time zone */

	/* normalize the time */
	normalize(&tp->tm_sec, &tp->tm_min, 60);
	normalize(&tp->tm_min, &tp->tm_hour, 60);
	normalize(&tp->tm_hour, &tp->tm_mday, 24);

	/* normalize the month first */
	normalize(&tp->tm_mon, &tp->tm_year, 12);

	/* days to months a little tricky */
	while (tp->tm_mday < 1)
	{
		if (--tp->tm_mon < 0)
		{
			tp->tm_year--;
			tp->tm_mon += 12;
		}

		tp->tm_mday += mon_size[is_leap(tp->tm_year)][tp->tm_mon];
	}

	while (tp->tm_mday > (k = mon_size[is_leap(tp->tm_year)][tp->tm_mon]))
	{
		tp->tm_mday -= k;

		if (++tp->tm_mon > 12)
		{
			tp->tm_year++;
			tp->tm_mon -= 12;
		}
	}

    k = tp->tm_year/4;			/* number of 4 year groups */
    t = (k * 1461) - 1;			/* number of days */
    k = tp->tm_year % 4;		/* number of years beyond group */
    t += (k * 365);				/* add number of days */
    if (k)						/* if not group break year */
        t++;					/* add one day */

	/* Since the epoch starts at Jan 1/70, we have to subtract the */
	/* of days from Jan 1/00.  This is actually 25567 days.  See */
	/* below for the explanation of the discrepancy */
    t -= 25568;					/* t = # days to 00:00:00 Jan 1/70 */

    t += yday_size[is_leap(tp->tm_year)][tp->tm_mon];

	/* Add the number of days in month.  Note that we should really */
	/* subtract 1 from the day first but we effectively did this */
	/* above when we subtracted an extra day (25568 instead of 25567) */
    t += tp->tm_mday;			/* # days to given day at 00:00:00 */

	/* now add in the number of seconds in the day */
    t = (t * 24) + tp->tm_hour;
    t = (t * 60) + tp->tm_min;
    t = (t * 60) + tp->tm_sec;   /* total number of seconds */

	/* if caller thinks he/she knows what time zone then believe them */
	if (tp->tm_isdst == 0)
		t += timezone;
	else if (tp->tm_isdst > 0)
		t += altzone;
	/* otherwise we have to figure it out */
	else
	{
		/* guess dst first */
		t1 = t + altzone;
		tm = *localtime(&t1);

		/* see if the guess matches the reality */
		if (tm.tm_hour == tp->tm_hour && tm.tm_min == tp->tm_min)
			t = t1;
		else

/* if CHECK_INVALID is defined then we attempt to check for the invalid */
/* time case e.g. a time of 0230h on the first sunday in April will */
/* return -1 - personally I don't think this is polite behaviour */
#ifdef	CHECK_INVALID
		{
			t1 = t + timezone;
			tm = *localtime(&t1);

			if (tm.tm_hour == tp->tm_hour && tm.tm_min == tp->tm_min)
				t = t1;
			else
				return(-1);
		}
#else
			t += timezone;
#endif
	}

    *tp = *localtime(&t);		/* set other fields in structure */
    return(t);
}
-----------------------------------------------------------------------

-- 
D'Arcy J.M. Cain (darcy@druid)     |
D'Arcy Cain Consulting             |   There's no government
West Hill, Ontario, Canada         |   like no government!
+1 416 281 6094                    |

karl@ima.isc.com (Karl Heuer) (02/14/91)

In article <1991Feb8.062954.13509@druid.uucp> darcy@druid.uucp (D'Arcy J.M. Cain) writes:
>time_t	mktime(struct tm *tp) { ...
>    *tp = *localtime(&t);		/* set other fields in structure */
>    return(t);
>}

Sorry, this violates the Standard.  "The implementation shall behave as if
no other library function calls [localtime()]." [4.12.3].  In particular,
the user has the right to assume that the static structure maintained by
localtime() is not destroyed by a call to mktime().

Fix: move the guts of localtime() into void _do_time(time_t, struct tm *),
reimplement localtime(time_t *pt) as _do_time(*pt, &statictm), and change
"*tp = *localtime(&t);" in mktime() to "_do_time(t, tp)".

Karl W. Z. Heuer (karl@ima.isc.com or uunet!ima!karl), The Walking Lint

darcy@druid.uucp (D'Arcy J.M. Cain) (02/15/91)

In article <1991Feb14.032409.27062@dirtydog.ima.isc.com> Karl Heuer writes:
>In article <1991Feb8.062954.13509@druid.uucp> darcy@druid.uucp (D'Arcy J.M. Cain) writes:
>>    *tp = *localtime(&t);		/* set other fields in structure */
>Sorry, this violates the Standard.  "The implementation shall behave as if
>no other library function calls [localtime()]." [4.12.3].  In particular,
> ...
>Fix: move the guts of localtime() into void _do_time(time_t, struct tm *),

Ouch.  anyone know where I can get a PD localtime?

-- 
D'Arcy J.M. Cain (darcy@druid)     |
D'Arcy Cain Consulting             |   There's no government
West Hill, Ontario, Canada         |   like no government!
+1 416 281 6094                    |