mktime problems when adjusting tm_isdst

Paul Eggert eggert at twinsun.com
Tue Oct 10 04:10:58 UTC 1995


With TZ=Europe/Paris, when I invoke mktime on the equivalent of
`1996-01-01 00:00:00 tm_isdst=1', it returns the equivalent of
`1995-12-31 23:09:21 tm_isdst=0'.  Surely the result should be
`1996-12-31 23:00:00 tm_isdst=0'.

(This is tzcode95d compiled on Solaris 2.4.)

The problem seems to be that mktime discovers that the requested time
exists only in standard time, so it corrects it by subtracting the
first known GMT offset using DST and adding the first known GMT offset
not using DST.  But Europe/Paris currently uses different DST rules
(and a different GMT offset) than it did in 1916 and 1911, respectively.

The obvious workaround for this case is to change time1() so that it
uses the nearest DST and non-DST transitions just before the requested
time, instead of the first possible DST and non-DST transitions.  But
this breaks down in other cases.  For example, in Europe/Paris at
1940-06-15 00:00:00, the GMT offset is 2 hours, tm_isdst=1 and there's
a 1-hour DST offset, but at the most recent non-DST period the GMT
offset is 0 hours, so mktime will wrongly adjust the time by 2 hours.

There's a deeper problem here.  How should mktime adjust for DST in a
locale that has multiple DST offsets?  For example, suppose
TZ=America/St_Johns and I invoke mktime on the equivalent of
`1989-01-01 00:00:00 tm_isdst=1'.  Newfoundland used a 2-hour DST
offset in summer 1988 and a 1-hour DST offset in summer 1989, so
should the requested time be adjusted by 1 hour or by 2?

One possible way to address the deeper problem, suggested by Mark
Brader, is to represent different DST offsets by using different
tm_isdst values.  For example, instead of simply being 1 when some
sort of DST is in effect, tm_isdst could be the DST offset, in seconds
modulo INT_MAX+1.  That way, mktime can tell which DST offset was used
to derive its input time, and it can adjust the time correctly when it
finds that a different DST offset is needed.

I think this change would make it easy to fix the Europe/Paris bug
mentioned above.  But it's a nontrivial change to the tz code and I
worry that it might break some applications that assume that
tm_isdst==1 means ``use normal DST''.


Here's a test program to reproduce the bug describe above.  Run it
with TZ set to Europe/Paris as built from tzdata95i.tar.gz, and give
it the arguments `1996-01-01 00:00:00 1'.

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

#define TM_YEAR_BASE 1900

static void
print_tm (tp)
     struct tm *tp;
{
  printf ("%04d-%02d-%02d %02d:%02d:%02d yday %03d wday %d isdst %d",
	  tp->tm_year + TM_YEAR_BASE, tp->tm_mon + 1, tp->tm_mday,
	  tp->tm_hour, tp->tm_min, tp->tm_sec,
	  tp->tm_yday, tp->tm_wday, tp->tm_isdst);
}

int
main (argc, argv)
     int argc;
     char **argv;
{
  time_t t;
  struct tm tm;
  char trailer;

  if (argc == 4
      && (sscanf (argv[1], "%d-%d-%d%c",
		  &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &trailer)
	  == 3)
      && (sscanf (argv[2], "%d:%d:%d%c",
		  &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &trailer)
	  == 3)
      && (sscanf (argv[3], "%d%c", &tm.tm_isdst, &trailer) == 1))
    {
      tm.tm_year -= TM_YEAR_BASE;
      tm.tm_mon--;
      t = mktime (&tm);
      printf ("mktime returns %ld == ", (long) t);
      print_tm (&tm);
      printf ("\n");
    }
  else
    printf ("Usage:\t%s YYYY-MM-DD HH:MM:SS ISDST # Test given time.\n",
	    argv[0]);

  return 0;
}



More information about the tz mailing list