Subject: mktime(3) fails to convert a time in the "spring forward gap"
To: NetBSD Userlevel Technical Discussion List <tech-userlevel@NetBSD.ORG>
From: Greg A. Woods <woods@weird.com>
List: tech-userlevel
Date: 10/05/2002 16:36:18
It seems NetBSD's (and FreeBSD, at least 4.7-RC's) mktime(3) fails to
convert a time in the "spring forward gap".
I noticed this because the libc mktime() is rejected as broken when
building Emacs 21.2 on NetBSD.
Is this worth fixing? Apparently some people think it is a problem, and
I suppose any utility that actually tries to use mktime() at a current
time during that gap will get a somewhat surprising failure. I haven't
even tried looking for a fix yet, or I would have sent a PR. Should I
just report this to tz@elsie.nci.nih.gov and let any fix filter down?
Here's a slightly modified version of the program from the emacs
configure test unit which demonstrates the bug and which I suppose could
be used in a regression test:
/*
* tmktime.c - test mktime(3) -- stolen from emacs-21.2 aclocal.m4
*
* Test program from Paul Eggert (eggert@twinsun.com)
* and Tony Leneis (tony@plaza.ds.adp.com).
*/
#ifdef BSD
# define HAVE_SYS_TIME_H /* defined */
# define HAVE_UNISTD_H /* defined */
# define HAVE_ALARM /* defined */
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include <stdio.h>
#include <errno.h>
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#if !HAVE_ALARM
# define alarm(X) /* empty */
#endif
static time_t time_t_max;
/* Values we'll use to set the TZ environment variable. */
static const char *const tz_strings[] = {
(const char *) 0, /* XXX does this have the desired effect? */
"TZ=", /* not in original test. */
"TZ=GMT0",
"TZ=JST-9",
"TZ=EST+3EDT+2,M10.1.0/00:00:00,M2.3.0/00:00:00"
};
#define N_STRINGS (sizeof (tz_strings) / sizeof (tz_strings[0]))
/*
* Fail if mktime fails to convert a date in the "spring-forward" gap.
* Based on a problem report from Andreas Jaeger.
*
* glibc (up to about 1998-10-07) failed this test), NetBSD up to 1.6 fails
* this test, as does FreeBSD up to 4.7-RC.
*/
static void
spring_forward_gap()
{
struct tm tm;
/*
* Use the portable POSIX.1 specification "TZ=PST8PDT,M4.1.0,M10.5.0"
* instead of "TZ=America/Vancouver" in order to detect the bug even on
* systems that don't support the Olson extension, or don't have the
* full zoneinfo tables installed.
*/
putenv("TZ=PST8PDT,M4.1.0,M10.5.0");
tm.tm_year = 98;
tm.tm_mon = 3;
tm.tm_mday = 5;
tm.tm_hour = 2;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_isdst = -1;
errno = 0;
if (mktime(&tm) == (time_t) -1) {
fprintf(stderr, "mktime(): fails for time in spring-forward gap: %s\n", strerror(errno));
exit(1);
}
}
static void
mktime_test(now)
time_t now;
{
struct tm *lt;
if ((lt = localtime(&now)) && mktime(lt) != now) {
fprintf(stderr, "localtime() and mktime() disagree on 'now = %ld'!\n", (long) now);
exit(1);
}
now = time_t_max - now;
if ((lt = localtime(&now)) && mktime(lt) != now) {
fprintf(stderr, "localtime() and mktime() disagree on 'time_t_max - now = %ld'!\n", now);
exit(1);
}
}
/*
* Based on code from Ariel Faigon.
*/
static void
irix_6_4_bug()
{
struct tm tm;
tm.tm_year = 96;
tm.tm_mon = 3;
tm.tm_mday = 0;
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_isdst = -1;
mktime(&tm);
if (tm.tm_mon != 2 || tm.tm_mday != 31) {
/* XXX also fails on BSDI-1.1 */
fprintf(stderr, "mktime(): failed irix 6.4 bug\n");
exit(1);
}
}
static void
bigtime_test(j)
int j;
{
struct tm tm;
time_t now;
tm.tm_year = tm.tm_mon = tm.tm_mday = tm.tm_hour = tm.tm_min = tm.tm_sec = j;
now = mktime(&tm);
if (now != (time_t) -1) {
struct tm *lt = localtime(&now);
if (!(lt
&& lt->tm_year == tm.tm_year
&& lt->tm_mon == tm.tm_mon
&& lt->tm_mday == tm.tm_mday
&& lt->tm_hour == tm.tm_hour
&& lt->tm_min == tm.tm_min
&& lt->tm_sec == tm.tm_sec
&& lt->tm_yday == tm.tm_yday
&& lt->tm_wday == tm.tm_wday
&& ((lt->tm_isdst < 0 ? -1 : 0 < lt->tm_isdst)
== (tm.tm_isdst < 0 ? -1 : 0 < tm.tm_isdst)))) {
fprintf(stderr, "mktime(): fails bigtime test: j = %d\n", j);
exit(1);
}
} else {
/*
* Some values of j fail -- we don't really care about those --
* we only care when it works to know that localtime() gets the
* same values back that mktime() used.
*/
#ifdef DEBUG
printf("mktime(): failed to convert bigtime: j = %d\n", j);
#endif
}
}
int
main(argc, argv)
int argc;
char *argv[];
{
time_t t,
delta;
int i,
j;
/*
* This test makes some buggy mktime() implementations loop. Give up
* on all tests after 240 seconds. A mktime() slower than that isn't
* worth using anyway. (XXX BSDI-1.1 on i386 sx25 takes several
* minutes -- how long does a VAX 11/750 take to run this?)
*/
alarm(240);
for (time_t_max = 1; 0 < time_t_max; time_t_max *= 2)
continue;
time_t_max--;
delta = time_t_max / 997; /* a suitable prime number */
for (i = 0; i < N_STRINGS; i++) {
if (tz_strings[i])
putenv(tz_strings[i]);
for (t = 0; t <= time_t_max - delta; t += delta)
mktime_test(t);
mktime_test((time_t) 60 * 60);
mktime_test((time_t) 60 * 60 * 24);
for (j = 1; 0 < j; j *= 2)
bigtime_test(j);
bigtime_test(j - 1);
}
irix_6_4_bug();
spring_forward_gap();
exit(0);
/* NOTREACHED */
}
--
Greg A. Woods
+1 416 218-0098; <g.a.woods@ieee.org>; <woods@robohack.ca>
Planix, Inc. <woods@planix.com>; VE3TCP; Secrets of the Weird <woods@weird.com>