Bug in localtime.c

Gibson_Roger/HEMEL_ENGINEERING_SERVERS at uniplex.co.uk Gibson_Roger/HEMEL_ENGINEERING_SERVERS at uniplex.co.uk
Fri Feb 23 12:31:54 UTC 1996


From: Roger Gibson (rxg at uniplex.co.uk)

I have found a bug in 'localtime.c' for which I am sending a proposed fix.
The problem occurs when 'mktime' is called with tm_mday <= 0, tm_mon > 1
and tm_year is a leap year or the year after a leap year. For such a case,
the normalization is performed incorrectly, and yields a date which is one
day out. The following example program demonstrates this:

--------------------------------

#include <stdio.h>
#include <time.h>

int main()
{
	struct tm tm;
	char buf[256];
	memset(&tm, 0, sizeof tm);
	tm.tm_year = 96; /* 1996 */
	tm.tm_mon = 2; /* March */
	tm.tm_mday = 0;
	tm.tm_hour = 12;
	mktime(&tm);
	/* Should give Feb 29 */
	strftime(buf, sizeof buf, "%C", &tm);
	printf("%s\n", buf);
	return 0;
}

--------------------------------

To deal with cases like this, the 'time2' function decrements the
year, and adds the corresponding number of days to 'tm_mday'.  The
problem is that the number of days is taken to be that in the year
after it has been decremented, whereas it is actually that in the year
which contains the end of February which is spanned by the interval.
It is necessary to take 'tm_mon' into account in determining this.

The same problem occurs in the part of the code which deals with
values of 'tm_mday' which exceed the number of days in a year.

The version of 'time2' below contains my attempt to fix these
problems.  It seems to work, but I'd like to know if you concur.

--------------------------------
Version information:

static char     elsieid[] = "@(#)localtime.c    7.44";

(Still present in version 7.55.)
--------------------------------
static time_t
time2(tmp, funcp, offset, okayp)
struct tm * const	tmp;
void (* const		funcp) P((const time_t*, long, struct tm*));
const long		offset;
int * const		okayp;
{
	register const struct state *	sp;
	register int			dir;
	register int			bits;
	register int			i, j ;
	register int			saved_seconds;
	time_t				newt;
	time_t				t;
	struct tm			yourtm, mytm;

	*okayp = FALSE;
	yourtm = *tmp;
	if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
		return WRONG;
	if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
		return WRONG;
	if (normalize_overflow(&yourtm.tm_year, &yourtm.tm_mon, MONSPERYEAR))
		return WRONG;
	/*
	** Turn yourtm.tm_year into an actual year number for now.
	** It is converted back to an offset from TM_YEAR_BASE later.
	*/
	if (increment_overflow(&yourtm.tm_year, TM_YEAR_BASE))
		return WRONG;
	while (yourtm.tm_mday <= 0) {
		if (increment_overflow(&yourtm.tm_year, -1))
			return WRONG;
		if (yourtm.tm_mon >= 2)
			yourtm.tm_mday += year_lengths[isleap(yourtm.tm_year + 1)];
		else
			yourtm.tm_mday += year_lengths[isleap(yourtm.tm_year)];
	}
	while (yourtm.tm_mday > DAYSPERLYEAR) {
	        if (yourtm.tm_mon >= 2)
			yourtm.tm_mday -= year_lengths[isleap(yourtm.tm_year + 1)];
		else
			yourtm.tm_mday -= year_lengths[isleap(yourtm.tm_year)];

		if (increment_overflow(&yourtm.tm_year, 1))
			return WRONG;
	}
	for ( ; ; ) {
		i = mon_lengths[isleap(yourtm.tm_year)][yourtm.tm_mon];
		if (yourtm.tm_mday <= i)
			break;
		yourtm.tm_mday -= i;
		if (++yourtm.tm_mon >= MONSPERYEAR) {
			yourtm.tm_mon = 0;
			if (increment_overflow(&yourtm.tm_year, 1))
				return WRONG;
		}
	}
	if (increment_overflow(&yourtm.tm_year, -TM_YEAR_BASE))
		return WRONG;
	if (yourtm.tm_year + TM_YEAR_BASE < EPOCH_YEAR) {
		/*
		** We can't set tm_sec to 0, because that might push the
		** time below the minimum representable time.
		** Set tm_sec to 59 instead.
		** This assumes that the minimum representable time is
		** not in the same minute that a leap second was deleted from,
		** which is a safer assumption than using 58 would be.
		*/
		if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
			return WRONG;
		saved_seconds = yourtm.tm_sec;
		yourtm.tm_sec = SECSPERMIN - 1;
	} else {
		saved_seconds = yourtm.tm_sec;
		yourtm.tm_sec = 0;
	}
	/*
	** Calculate the number of magnitude bits in a time_t
	** (this works regardless of whether time_t is
	** signed or unsigned, though lint complains if unsigned).
	*/
	for (bits = 0, t = 1; t > 0; ++bits, t <<= 1)
		continue;
	/*
	** If time_t is signed, then 0 is the median value,
	** if time_t is unsigned, then 1 << bits is median.
	*/
	t = (t < 0) ? 0 : ((time_t) 1 << bits);
	for ( ; ; ) {
		(*funcp)(&t, offset, &mytm);
		dir = tmcomp(&mytm, &yourtm);
		if (dir != 0) {
			if (bits-- < 0)
				return WRONG;
			if (bits < 0)
				--t;
			else if (dir > 0)
				t -= (time_t) 1 << bits;
			else	t += (time_t) 1 << bits;
			continue;
		}
		if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst)
			break;
		/*
		** Right time, wrong type.
		** Hunt for right time, right type.
		** It's okay to guess wrong since the guess
		** gets checked.
		*/
		/*
		** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's.
		*/
		sp = (const struct state *)
			(((void *) funcp == (void *) localsub) ?
			lclptr : gmtptr);
#ifdef ALL_STATE
		if (sp == NULL)
			return WRONG;
#endif /* defined ALL_STATE */
		for (i = 0; i < sp->typecnt; ++i) {
			if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
				continue;
			for (j = 0; j < sp->typecnt; ++j) {
				if (sp->ttis[j].tt_isdst == yourtm.tm_isdst)
					continue;
				newt = t + sp->ttis[j].tt_gmtoff -
					sp->ttis[i].tt_gmtoff;
				(*funcp)(&newt, offset, &mytm);
				if (tmcomp(&mytm, &yourtm) != 0)
					continue;
				if (mytm.tm_isdst != yourtm.tm_isdst)
					continue;
				/*
				** We have a match.
				*/
				t = newt;
				goto label;
			}
		}
		return WRONG;
	}
label:
	newt = t + saved_seconds;
	if ((newt < t) != (saved_seconds < 0))
		return WRONG;
	t = newt;
	(*funcp)(&t, offset, tmp);
	*okayp = TRUE;
	return t;
}





More information about the tz mailing list