Re: [PATCH 10/10] timegm: add portable implementation (Solaris support)

Subject: Re: [PATCH 10/10] timegm: add portable implementation (Solaris support)

Date: Mon, 05 Nov 2012 07:47:22 -0800

To: Tomi Ollila

Cc: notmuch@notmuchmail.org

From: Blake Jones


> Yet another idea for an alternative. Compile by entering 'sh xtimegm.c'
> and then run ./xtimegm
> 
> Simple cases seems to work. Dst change may (or then may not) give one
> hour difference to the expected. The test "coverage" could be easily
> expanded to that ;)
> 
> Hmm, I also found this:
> http://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00004.html
> which does 2 mktime's and one gmtime_r (without allocating tg!)...
> Pick any of these 3 -- or something different (e.g. less NIH if there is)

Of these two, I would probably lean slightly toward the latter, in that
it relies more on libc for the time zone handling logic.

But in general, this seems to me like a case where an explicit
implementation like mine is less prone to failure, because it doesn't
need to worry about time zones at all.  The other approaches rely on
letting libc do all the hard work of time zone manipulation, and then
reading the tea leaves to find a way to undo it.  I would guess that if
some change in the time standards is going to break one of these
implementations, it's going to be some new time zone specification, not
a change in the number of days in a month. :)

For what it's worth, I used the attached program to test my
implementation, and it passed.

Blake

#include <time.h>

static int
leapyear(int year)
{
	return ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0));
}

/*
 * This is a simple implementation of timegm() which does what is needed
 * by parse-time-string.c -- just turns the "struct tm" into a GMT time_t.
 * It does not normalize any of the fields of the "struct tm", nor does
 * it set tm_wday or tm_yday.
 */
static time_t
timegm(struct tm *tm)
{
	int	monthlen[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 },
	};
	int	year, month, days;

	days = 365 * (tm->tm_year - 70);
	for (year = 70; year < tm->tm_year; year++) {
		if (leapyear(1900 + year)) {
			days++;
		}
	}
	for (month = 0; month < tm->tm_mon; month++) {
		days += monthlen[leapyear(1900 + year)][month];
	}
	days += tm->tm_mday - 1;

	return ((((days * 24) + tm->tm_hour) * 60 + tm->tm_min) * 60 +
	    tm->tm_sec);
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void
tm_test(int niter)
{
	const int	monthlen[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 },
	};
	int		i;
	struct tm	tm, *tmp;
	time_t		t;

	for (i = 0; i < niter; i++) {
		tm.tm_year = (lrand48() % 67) + 70;
		tm.tm_mon = lrand48() % 12;
		do {
			t = (lrand48() % 31) + 1;
		} while (t > monthlen[leapyear(1900 + tm.tm_year)][tm.tm_mon]);
		tm.tm_mday = t;
		tm.tm_hour = lrand48() % 24;
		tm.tm_min = lrand48() % 60;
		tm.tm_sec = lrand48() % 60;

		t = timegm(&tm);
		tmp = gmtime(&t);

		if (tmp->tm_sec != tm.tm_sec ||
		    tmp->tm_min != tm.tm_min ||
		    tmp->tm_hour != tm.tm_hour ||
		    tmp->tm_mday != tm.tm_mday ||
		    tmp->tm_mon != tm.tm_mon ||
		    tmp->tm_year != tm.tm_year) {
			printf("%4d-%02d-%02d %02d:%02d:%02d -> "
			    "%4d-%02d-%02d %02d:%02d:%02d\n",
			    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
			    tm.tm_hour, tm.tm_min, tm.tm_sec,
			    tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
			    tmp->tm_hour, tmp->tm_min, tmp->tm_sec);

		}
	}
}

void
time_test(int niter)
{
	int		i;
	time_t		st, et;
	struct tm	*tmp;

	for (i = 0; i < niter; i++) {
		st = (time_t)lrand48();

		tmp = gmtime(&st);
		et = timegm(tmp);

		if (st != et) {
			printf("%d -> %d (%4d-%02d-%02d %02d:%02d:%02d)\n",
			    st, et,
			    tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
			    tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
		}
	}
}

int
main(void)
{
	const int	niter = 10000000;

	srand48(getpid());

	tm_test(niter);
	time_test(niter);

	return (0);
}

Thread: