Почему timelocal и mktime не справляются с переходом на летнее время правильно?
Я пытаюсь понять, как работают UNIX timelocal и mktime. Предположительно они обрабатывают летнее время, когда вы передаете правильное значение в struct tm
tm_isdst
поле.
Я тестирую очень специфический момент времени. Согласно базе данных часовых поясов для "America/New_York", летнее время сместилось 30 октября 2005 года в 01:00. Вот вывод из zdump -v America/New_York
который вы можете подтвердить в своей системе. Я показываю только подмножество данных около 2005 года (прокрутите вправо, чтобы увидеть значения gmtoff):
Америка /New_York Sun 3 апреля 06:59:59 2005 UT = вс 3 апреля 01:59:59 2005 EST isdst=0 gmtoff=-18000 Америка / Нью-Йорк, воскресенье, 3 апреля 07:00:00 2005 UT = вс, 3 апреля, 03:00:00 EDT isdst=1 gmtoff=-14400 Америка / Нью-Йорк вс 30 октября 05:59:59 2005 UT = вс 30 октября 01:59:59 2005 EDT isdst=1 gmtoff=-14400 Америка / Нью-Йорк вс 30 октября 06:00:00 2005 UT = вс 30 октября 01:00:00 2005 EST isdst=0 gmtoff=-18000 Америка /New_York Sun 2 апреля 06:59:59 2006 UT = вс 2 апреля 01:59:59 2006 EST isdst=0 gmtoff=-18000 Америка /New_York Sun 2 апреля 07:00:00 2006 UT = вс 2 апреля 03:00:00 2006 EDT isdst=1 gmtoff=-14400
Чтобы проверить этот переход, я настраиваю struct tm
содержать 01:30 в этот конкретный день. Если я передам 0 для tm_isdst
это должно дать мне gmtoffset -18000. Если я пройду 1 и включу летнее время, то значение gmtoffset должно быть -14400.
Вот код, который я использую для тестирования на Darwin/OSX и FreeBSD:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
void print_tm(struct tm* tm) {
printf("tm: sec [%d] min [%d] hour [%d] mday [%d] mon [%d] year [%d] wday [%d] yday [%d] isdst [%d] zone [%s] gmtoff [%ld]\n",
tm->tm_sec,
tm->tm_min,
tm->tm_hour,
tm->tm_mday,
tm->tm_mon + 1,
tm->tm_year,
tm->tm_wday,
tm->tm_yday + 1,
tm->tm_isdst,
tm->tm_zone,
tm->tm_gmtoff);
}
struct tm* set_tm(int sec, int min, int hour, int mday, int mon, int year, int wday, int yday, int isdst, int gmtoff, char* zone) {
struct tm* tm;
tm = malloc(sizeof(struct tm));
memset(tm, 0, sizeof(struct tm));
tm->tm_sec = sec;
tm->tm_min = min;
tm->tm_hour = hour;
tm->tm_mday = mday;
tm->tm_mon = mon - 1;
tm->tm_year = year;
tm->tm_wday = wday;
tm->tm_yday = yday - 1;
tm->tm_isdst = isdst;
tm->tm_zone = zone;
tm->tm_gmtoff = gmtoff;
return tm;
}
void test_timelocal(struct tm* tm, int isdst) {
time_t seconds = -1;
if(!setenv("TZ", "America/New_York", 1)) {
printf("isdst is [%d]\n", isdst);
tm->tm_isdst = isdst;
tzset();
seconds = timelocal(tm);
localtime_r(&seconds, tm);
print_tm(tm);
} else {
printf("setenv failed with [%s]\n", strerror(errno));
}
printf("\n");
}
void test_mktime(struct tm* tm, int isdst) {
time_t seconds = -1;
if(!setenv("TZ", "America/New_York", 1)) {
printf("isdst is [%d]\n", isdst);
tm->tm_isdst = isdst;
tzset();
seconds = mktime(tm);
localtime_r(&seconds, tm);
print_tm(tm);
} else {
printf("setenv failed with [%s]\n", strerror(errno));
}
printf("\n");
}
int main(void) {
struct tm* tm;
printf("Test with timelocal\n");
tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
test_timelocal(tm, 0);
tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
test_timelocal(tm, 1);
tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
test_timelocal(tm, -1);
printf("Test with mktime\n");
tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
test_mktime(tm, 0);
tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
test_mktime(tm, 1);
tm = set_tm(0, 30, 1, 30, 10, 2005, 0, 0, 0, 0, "");
test_mktime(tm, -1);
return 0;
}
Запуск этого в разных ОС дает разные результаты. На FreeBSD этот код выводит (прокрутите вправо, чтобы увидеть значения gmtoffset):
Тест с временной шкалой isdst составляет [0] тм: сек [0] мин [30] час [1] мдень [30] пн [10] год [2005] день недели [1] yday [303] isdst [1] зона [EDT] gmtoff [-14400] isdst равен [1] тм: сек [0] мин [30] час [1] мдень [30] пн [10] год [2005] день недели [1] день [303] isdst [1] зона [ EDT] gmtoff [-14400] isdst равно [-1] tm: сек [0] мин [30] час [1] мдень [30] пн [10] год [2005] день недели [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400] Тест с mktime isdst равен [0] tm: sec [0] min [30] hour [2] mday [30] mon [10] year [2005] wday [1] yday [ 303] isdst [1] зона [EDT] gmtoff [-14400] isdst составляет [1] тм: сек [0] мин [30] час [1] мдень [30] пн [10] год [2005] день недели [1] yday [303] isdst [1] zone [EDT] gmtoff [-14400] isdst is [-1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] день [303] isdst [1] зона [EDT] gmtoff [-14400]
В darwin/OSX точно такой же код производит это (прокрутите вправо, чтобы увидеть значения gmtoffset):
Тест с timelocal isdst is [0] tm: sec [0] min [30] час [1] mday [30] mon [10] год [2005] wday [1] yday [303] isdst [0] зона [EST] gmtoff [-18000] isdst is [1] tm: sec [0] min [30] час [1] mday [30] mon [10] год [2005] wday [1] yday [303] isdst [0] зона [EST] gmtoff [-18000] isdst is [-1] tm: sec [0] min [30] час [1] mday [30] mon [10] год [2005] wday [1] yday [303] isdst [0] зона [EST] gmtoff [-18000] Тест с mktime isdst is [0] tm: sec [0] min [30] час [1] mday [30] mon [10] год [2005] wday [1] yday [303] isdst [0] зона [EST] gmtoff [-18000] isdst is [1] tm: sec [0] min [30] час [0] mday [30] mon [10] год [2005] wday [1] yday [303] isdst [0] зона [EST] gmtoff [-18000] isdst is [-1] tm: sec [0] min [30] час [1] mday [30] mon [10] год [2005] wday [1] yday [303] isdst [0] зона [EST] gmtoff [-18000]
На мой взгляд, похоже, что ОБА из них ошибаются. tm_isdst
поле, кажется, не влияет на tm_gmtoff
поле. tm_hour
изменения выходного сигнала при использовании mktime
но смещение все равно неверно.
Если вы измените tm_mday
днями раньше или днями gmtoffset вообще не меняется, что меня смущает.
Я делаю что-то не так или я неправильно понял, как работают эти функции?
1 ответ
Время UNIX очень крутое. Оказывается, моя ошибка связана с tm_year
поле в struct tm
, Предполагается, что он представляет количество лет с 1900 года, поэтому значение в этом поле должно быть 105, а не 2005 (например, 2005 - 1900 = 105). Это теперь дает правильный ответ.
Это определение структуры можно найти на этой очень полезной странице.