mktime() function (was: Re: UNIX-Time ...)

John F. Haugh II jfh at rpp386.cactus.org
Sat Dec 1 12:21:19 AEST 1990


This is the last and final mktime()-like function.  It has been
tested for several million values of "time" and seems to work
quite perfectly.  One of the tests covered a random time over a
several year stretch, comparing the value input to localtime()
against the value returned by mktime() when handed the output
of localtime().  It was also tested for over 10 million other
values as well, all of which returned the same exact value for
"time" that was initially fed to localtime().

This version includes support for the tm_isdst structure member,
which is something which Boyd left out of his original version,
and which is responsible for the ambiguity between which 1:51:45
his function computed the time for last October 28th.

Below are the performance results of executing the two functions
1,000 times each.  The driver function consists of

main ()
{
	long	i, t;
	struct	tm	tm;

	i = 12345678L;
	tm = *(localtime (&i));

	for (i = 0;i < 100000000;i += 100000)
#ifdef	BOYD
		t = gmsecs (&tm);
#else
		t = mktime (&tm);
#endif
}

% timex ./bfoo
execution complete, exit code = 1

real	        21.81
user	        21.76
sys	         0.05
% timex ./jfoo
execution complete, exit code = 1

real	         1.79
user	         1.73
sys	         0.04
% calc 21.81 / 1.79
	 12.18435754189944

OK, so it's only 12 times faster instead of 15.  Sue me ;-)  Both
were compiled with optimization turned on (-Ox) on a 16Mhz 386.
Your milage may vary.  I really didn't mean for this to be an
attack on Boyd's code - I had initially made my suggestion about
making a more educated guess to improve upon his code, not to
replace it.

And now for the source code ...

----- cut and save as mktime.c -----
/*
 * Copyright 1990, John F. Haugh II
 * All rights reserved.
 *
 * Use, duplication, and disclosure prohibited without
 * the express written permission of the author.
 *
 * Non-commercial (not for profit) source code distribution
 * is permitted, provided this notice remains intact.
 */

#include <time.h>

/*
 * days and juldays are used to compute the number of days in the
 * current month, and the cummulative number of days in the preceding
 * months.
 */

static	short	days[12] = {
	31,	28,	31,	30,	31,	30,	/* JAN - JUN */
	31,	31,	30,	31,	30,	31 };	/* JUL - DEC */

static	short	juldays[12] = {
	0,	31,	59,	90,	120,	151,	/* JAN - JUN */
	181,	212,	243,	273,	304,	334 };	/* JUL - DEC */

static time_t
dtime (tm, time)
struct	tm	*tm;
time_t	time;
{
	struct	tm	*sm;
	time_t	diff;
	time_t	julian1, julian2;

	sm = localtime (&time);

	julian1 = ((tm->tm_year - 70) * 365) +	/* days in whole years */
		(((tm->tm_year + 1) - 70) / 4);	/* days in leap years */
	julian1 += juldays[tm->tm_mon] +	/* days in whole months */
		(tm->tm_mon > 1 && (tm->tm_year % 4) == 0 ? 1:0); /* leap day */
	julian1 += tm->tm_mday - 1;		/* days so far this month */

	julian2 = ((sm->tm_year - 70) * 365) +	/* days in whole years */
		(((sm->tm_year + 1) - 70) / 4);	/* days in leap years */
	julian2 += juldays[sm->tm_mon] +	/* days in whole months */
		(sm->tm_mon > 1 && (sm->tm_year % 4) == 0 ? 1:0); /* leap day */
	julian2 += sm->tm_mday - 1;		/* days so far this month */

	diff = (julian1 - julian2) * (24L*3600);	/* add the days */
	diff += (tm->tm_hour - sm->tm_hour) * (3600L);	/* add the hours */
	diff += (tm->tm_min - sm->tm_min) * (60L);	/* add the minutes */
	diff += (tm->tm_sec - sm->tm_sec);		/* add the seconds */

	if (daylight && ((tm->tm_isdst == 0) != (sm->tm_isdst == 0))) {
		if (tm->tm_isdst)
			diff -= (timezone - altzone);
		if (sm->tm_isdst)
			diff += (timezone - altzone);
	}
	return diff;				/* that's how far off */
}

time_t
mktime (tm)
struct	tm	*tm;
{
	time_t	init, diff;
	int	i;

	/*
	 * Let's validate the tm structure.
	 */

	if (! tm || tm->tm_year < 0 || tm->tm_year > 200
		|| tm->tm_mon < 0 || tm->tm_mon > 11
		|| tm->tm_mday < 0	/* other half later */
		|| tm->tm_hour < 0 || tm->tm_hour > 23
		|| tm->tm_min < 0 || tm->tm_min > 59
		|| tm->tm_sec < 0 || tm->tm_sec > 59)
		return -1L;

	if (tm->tm_mon == 2 && tm->tm_year % 4 == 0) {
		if (tm->tm_mday > 29)
			return -1L;
	} else if (tm->tm_mday > days[tm->tm_mon])
		return -1L;

	/*
	 * POSIX says that tzset() must be called to get the
	 * timezone information. [ "AS-IF" tzset() were called ]
	 */

	tzset ();

	/*
	 * The first guess is within a few hours, the second
	 * guess should get it exactly right.  A third guess
	 * may be needed in some cases.
	 *
	 * In practice the first call is exact and the second
	 * call is only needed to verify this.  Bizarre behavior
	 * around daylight savings time could trip this up, so
	 * I go again just to make sure.
	 */

	if ((init = dtime (tm, 0L)) == 0)
		return init;

	if ((diff = dtime (tm, init)) == 0)
		return init;

	if ((diff = dtime (tm, init += diff)) == 0)
		return init;

	/*
	 * The difference had better be less than a minute
	 * by this time or the date is probably invalid
	 * some how.
	 */

	if (diff < -60 || diff > 60)
		return -1;

	return init + diff;
}
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh at rpp386.cactus.org
"SCCS, the source motel!  Programs check in and never check out!"
		-- Ken Thompson



More information about the Alt.sources mailing list