lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


There's no portable way, in either C or even POSIX, to convert a struct tm
into a time_t value without the system applying the local timezone offset.
struct tm is presumed to express localtime.

Some systems provide timegm(), but others don't. I'm writing a comprehensive
binding to OpenSSL (bignum, X.509, SSL sessions, etc) for my cqueues
project, and I needed (well, wanted) to convert ASN.1 time objects to a
timestamp.

Below is the code for a portable timegm, which turned out to be surprisingly
simple (eventually). Assuming a double with a 53-bit significand, it can
generate the unix timestamp for any time between -9999-01-01 and 9999-12-31,
and then some.

I realize this code presumes Unix-style timestamps. But I figured it might
still be a worthwhile addition to Lua, to provide optional symmetry with the
"!" format specifier to os.date. Are there actually any systems around that
don't use similar timestamps? A different epoch isn't an issue. The hard
part would be dealing with leap seconds--which POSIX conveniently defines
away--or some other encoding scheme.


#define CLAMP(i, min, max) (((i) < (min))? (min) : ((i) > (max))? (max) : (i))

static _Bool isleap(int year) {
	if (year >= 0)
		return !(year % 4) && ((year % 100) || !(year % 400));
	else
		return isleap(-(year + 1));
} /* isleap() */

static int yday(int year, int mon, int mday) {
	static const int past[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
	int yday = past[CLAMP(mon, 0, 11)] + CLAMP(mday, 1, 31) - 1;

	return yday + (mon > 1 && isleap(year));
} /* yday() */

static int tm_yday(const struct tm *tm) {
	return (tm->tm_yday)? tm->tm_yday : yday(1900 + tm->tm_year, tm->tm_mon, tm->tm_mday);
} /* tm_yday() */

static int leaps(int year) {
	if (year >= 0)
		return (year / 400) + (year / 4) - (year / 100);
	else
		return -(leaps(-(year + 1)) + 1);
} /* leaps() */

static double tm2unix(const struct tm *tm, int gmtoff) {
	int year = tm->tm_year + 1900;
	double ts;

	ts = 86400.0 * 365.0 * (year - 1970);
	ts += 86400.0 * (leaps(year - 1) - leaps(1969));
	ts += 86400 * tm_yday(tm);
	ts += 3600 * tm->tm_hour;
	ts += 60 * tm->tm_min;
	ts += CLAMP(tm->tm_sec, 0, 59);
	ts += (year < 1970)? gmtoff : -gmtoff;

	return ts;
} /* tm2unix() */