Ошибка в mktime() на CentOS?

Я играл с mktime и заметил странное и непоследовательное поведение.

Я предоставляю ему дату, которая не во время DST (летнее время), но с tm_isdst, установленным на 1, что обычно делает mktime, это изменяет tm_isdst на 0 и соответствующим образом корректирует время, сдвигая на 1 час.

Однако во временном периоде примерно в 1928..1933 годах (другого диапазона не нашел) поведение другое. Поле tm_isdst установлено в 0, но время не меняется. Это приводит к странностям при выполнении вычислений времени и т. д.

У меня есть крошечная тестовая программа, которая для заданной даты ввода печатает: исходную структуру tm, структуру tm после вызова для нее mktime, результат mktime и структуру tm, которая является результатом вызова localtime для результата mktime (должна представлять тот же момент в время как исходное).

Результат:

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)

Обратите внимание, что для 2013, 1927, 1934 годов часы меняются, а летнее время устанавливается равным 0. Но в 1929 и 1932 годах часы не меняются, а летнее время изменяется.

Что очень странно, так это то, что в tzinfo нет ничего об этом временном диапазоне — zdump для Лос-Анджелеса показывает, что самые близкие изменения приходятся на 1919 и 1942 годы.

Это на CentOS, ядро ​​2.6.32-358.11.1.el6.x86_64, glibc-2.12-1.107.el6.x86_64.

Дальнейшее исследование, кажется, работает, как и ожидалось (стабильно) на MacOSX. Так что для меня это выглядит как ошибка в mktime(), но, возможно, я что-то упускаю.

Программа тестового тестирования приведена ниже, а также доступна здесь

#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;
}

person Marcin Zukowski    schedule 06.12.2013    source источник
comment
Было бы полезно знать, какую ОС вы используете и является ли time_t 32- или 64-разрядной (даже если вы все еще безопасно работаете в 32-разрядном диапазоне).   -  person R.. GitHub STOP HELPING ICE    schedule 06.12.2013
comment
Не вините ни библиотеку, ни себя... найдите виноватого специалиста по аппаратному обеспечению. ржу не могу   -  person Fiddling Bits    schedule 06.12.2013
comment
Добавлена ​​информация о платформе: CentOS, ядро ​​2.6.32-358.11.1.el6.x86_64, glibc-2.12-1.107.el6.x86_64.   -  person Marcin Zukowski    schedule 06.12.2013
comment
Хм ... Я также тестировал на MacOSX ... Он ведет себя стабильно ... Так что либо это разница в glibc, либо разница в информации о часовом поясе.   -  person Marcin Zukowski    schedule 06.12.2013


Ответы (1)


В источник mktime glibc вы можете найти это:

/* 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;

Если вы посчитаете, то обнаружите, что delta_bound равно 268828200 секундам, что равно примерно 8 с половиной годам. Это почти в точности разница между датами сброса (в 1919 и 1942 годах) и временем ваших загадочных переходов (в 1928 и 1933 годах). Каждая отстает ровно на 25 часов 30 минут. Я не искал глубже, чтобы найти причину этого магического числа.

Не понимая всего этого, я думаю, что комментарий в основном означает, что когда вы находитесь в средней части этого длинного отрезка времени без перехода на летнее время с 1919 по 1942 год, алгоритм пытается найти допустимую временную метку dst = 1 рядом с той, которую вы предоставили ошибочный dst=1 и сдается до того, как найдет его, потому что it makes no real sense. Годы за пределами этой средней части тоже могут не иметь особого смысла, но ведут себя по-другому как побочный эффект этого параметра настройки, связанного с Жужуй.

person Community    schedule 06.12.2013
comment
Спасибо @Wumpus-q-wumbley! Глядя на код, это действительно имеет большой смысл :) - person Marcin Zukowski; 06.12.2013