Я пытаюсь понять, как работает timelocal и mktime UNIX. Предположительно, они обрабатывают летнее время, когда вы передаете правильное значение в поле struct tm
tm_isdst
.Почему timelocal и mktime не справляются с летней экономией правильно?
Я тестирую очень конкретный момент времени. Согласно базе данных часовых поясов для «America/New_York», переход на летнее время сместился 30 октября 2005 года в 01:00. Вот результат от zdump -v America/New_York
, который вы можете подтвердить в своей собственной системе. Я только показывает подмножество данных вокруг год 2005 (справа прокрутки, чтобы увидеть gmtoff значения):
America/New_York Sun Apr 3 06:59:59 2005 UT = Sun Apr 3 01:59:59 2005 EST isdst=0 gmtoff=-18000 America/New_York Sun Apr 3 07:00:00 2005 UT = Sun Apr 3 03:00:00 2005 EDT isdst=1 gmtoff=-14400 America/New_York Sun Oct 30 05:59:59 2005 UT = Sun Oct 30 01:59:59 2005 EDT isdst=1 gmtoff=-14400 America/New_York Sun Oct 30 06:00:00 2005 UT = Sun Oct 30 01:00:00 2005 EST isdst=0 gmtoff=-18000 America/New_York Sun Apr 2 06:59:59 2006 UT = Sun Apr 2 01:59:59 2006 EST isdst=0 gmtoff=-18000 America/New_York Sun Apr 2 07:00:00 2006 UT = Sun Apr 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):
Test with timelocal isdst is [0] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [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] 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] yday [303] isdst [1] zone [EDT] gmtoff [-14400] Test with mktime isdst is [0] tm: sec [0] min [30] hour [2] mday [30] mon [10] year [2005] wday [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] 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] yday [303] isdst [1] zone [EDT] gmtoff [-14400]
В Darwin/OSX точно такой же код производит это (прокрутку вправо, чтобы увидеть GMTOffset значения):
Test with timelocal isdst is [0] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst is [1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst is [-1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] Test with mktime isdst is [0] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst is [1] tm: sec [0] min [30] hour [0] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000] isdst is [-1] tm: sec [0] min [30] hour [1] mday [30] mon [10] year [2005] wday [1] yday [303] isdst [0] zone [EST] gmtoff [-18000]
На мой взгляд похоже, что ОБА их получают НЕПРАВИЛЬНО. Поле tm_isdst
, по-видимому, не влияет на поле tm_gmtoff
. Выход tm_hour
изменяется при использовании mktime
, но смещение все еще не так.
Если вы изменили tm_mday
на дни раньше или через несколько дней, то gmtoffset не изменится, что меня смущает.
Я делаю что-то неправильно или неправильно истолковал, как эти функции работают?
изменений DST в 02:00, а не 01:00. – Barmar
Месяцы в 'struct tm' переходят от' 0' к '11', поэтому октябрь -' 9', а не '10'. Вы печатаете результаты за 30 ноября, а не 30 октября. – Barmar
Я решил это. Поле 'tm-> tm_year' основано с 1900 года. Поэтому в 2005 году мне нужно поставить (2005 - 1900) = 105 в этом поле. Тогда это работает. Что касается комментария @ barmar, код корректируется для подсчета 0-11 месяцев, так что это не ошибка. Функция 'set_tm' вычитает 1 из значения месяца. –