Вопрос

I have been playing with mktime, and I noticed a weird, and inconsistent, behavior.

I provide it with a date which is not during DST (daylight saving time), but with tm_isdst is set to 1, what mktime usually does is change tm_isdst to 0, and adjust the time accordingly, shifting by 1 hour.

However, in the time period roughly in years 1928..1933 (couldn't find a different range), the behavior is different. The tm_isdst field is set to 0, but the time does not change. This results in weirdness when performing time computations etc.

I have a tiny test program which for a given input date prints: original struct tm, struct tm after calling mktime on it, mktime result, and struct tm which is the result of calling localtime on the mktime result (should represent the same moment in time as the original one).

The output is:

2013-01-01 12:00:00 (off=0, dst=1) -> 2013-01-01 11:00:00 (off=-28800, dst=0) ->   1357066800 -> 2013-01-01 11:00:00 (off=-28800, dst=0)
1927-01-01 12:00:00 (off=0, dst=1) -> 1927-01-01 11:00:00 (off=-28800, dst=0) ->  -1356930000 -> 1927-01-01 11:00:00 (off=-28800, dst=0)
1929-01-01 12:00:00 (off=0, dst=1) -> 1929-01-01 12:00:00 (off=-28800, dst=0) ->  -1293768000 -> 1929-01-01 12:00:00 (off=-28800, dst=0)
1932-01-01 12:00:00 (off=0, dst=1) -> 1932-01-01 12:00:00 (off=-28800, dst=0) ->  -1199160000 -> 1932-01-01 12:00:00 (off=-28800, dst=0)
1934-01-01 12:00:00 (off=0, dst=1) -> 1934-01-01 11:00:00 (off=-28800, dst=0) ->  -1136005200 -> 1934-01-01 11:00:00 (off=-28800, dst=0)

See that for years 2013, 1927, 1934, the hour is changed and dst set to 0. But in years 1929 and 1932 the hour is not changed, but dst is.

What's super weird is that in tzinfo there is nothing about that time range - zdump for Los Angeles shows that the closest changes are in 1919 and 1942.

This is on CentOS, kernel 2.6.32-358.11.1.el6.x86_64, glibc-2.12-1.107.el6.x86_64.

Further investigation seems to work as expected (consistently) on MacOSX. So this looks like a bug in mktime() to me, but maybe I'm missing something.

Test test program is below and also available here

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

char* printtm(struct tm tm)
{
  static char buf[100];
  sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d (off=%ld, dst=%d)",
    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
    tm.tm_hour, tm.tm_min, tm.tm_sec,
    tm.tm_gmtoff, tm.tm_isdst);
  return buf;
}

void test(int y, int m, int d, int hh, int mm, int ss, int isdst)
{
  // Prepare tm structs
  struct tm tm, tm2;
  memset(&tm, 0, sizeof(tm));
  memset(&tm2, 0, sizeof(tm));
  tm.tm_year = y - 1900;
  tm.tm_mon = m - 1;
  tm.tm_mday = d;
  tm.tm_hour = hh;
  tm.tm_min = mm;
  tm.tm_sec = ss;
  tm.tm_isdst = isdst;
  // Convert tm -> t -> tm and print
  printf("%s -> ", printtm(tm));
  time_t t = mktime(&tm);
  printf("%s -> ", printtm(tm));
  printf("%12ld -> ", t);
  localtime_r(&t, &tm2);
  printf("%s\n", printtm(tm));
}

int main()
{
  setenv("TZ", ":America/Los_Angeles", 1);
  tzset();

  test(2013,07,01, 12,0,0, 1);
  test(2013,01,01, 12,0,0, 1);
  test(1927,01,01, 12,0,0, 1);
  test(1929,01,01, 12,0,0, 1);
  test(1932,01,01, 12,0,0, 1);
  test(1934,01,01, 12,0,0, 1);
  return 0;
}
Это было полезно?

Решение

In the source to glibc's mktime you can find this:

/* tm.tm_isdst has the wrong value.  Look for a neighboring
   time with the right value, and use its UTC offset.

   Heuristic: probe the adjacent timestamps in both directions,
   looking for the desired isdst.  This should work for all real
   time zone histories in the tz database.  */

/* Distance between probes when looking for a DST boundary.  In
   tzdata2003a, the shortest period of DST is 601200 seconds
   (e.g., America/Recife starting 2000-10-08 01:00), and the
   shortest period of non-DST surrounded by DST is 694800
   seconds (Africa/Tunis starting 1943-04-17 01:00).  Use the
   minimum of these two values, so we don't miss these short
   periods when probing.  */
int stride = 601200;

/* The longest period of DST in tzdata2003a is 536454000 seconds
   (e.g., America/Jujuy starting 1946-10-01 01:00).  The longest
   period of non-DST is much longer, but it makes no real sense
   to search for more than a year of non-DST, so use the DST
   max.  */
int duration_max = 536454000;

/* Search in both directions, so the maximum distance is half
   the duration; add the stride to avoid off-by-1 problems.  */
int delta_bound = duration_max / 2 + stride;

If you do the math, you'll find delta_bound is 268828200 seconds, equal to about 8 and a half years. It is almost exactly the difference between the zdump dates (in 1919 and 1942) and the times of your mysterious changeovers (in 1928 and 1933). Each one is off by exactly 25 hours 30 minutes. I haven't looked deeper to find the cause of that magic number.

Without understanding the whole thing, I think the comment basically means that when you're in the middle portion of that long non-DST stretch from 1919 to 1942, the algorithm tries to find a valid dst=1 timestamp near the one you supplied with the erroneous dst=1, and gives up before it finds one because it makes no real sense. The years just outside that middle portion may not make much sense either, but behave differently as a side effect of that Jujuy-related tuning parameter.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top