2017-02-17 38 views
0

Я пытаюсь понять, как работает timelocal и mktime UNIX. Предположительно, они обрабатывают летнее время, когда вы передаете правильное значение в поле struct tmtm_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 не изменится, что меня смущает.

Я делаю что-то неправильно или неправильно истолковал, как эти функции работают?

+1

изменений DST в 02:00, а не 01:00. – Barmar

+3

Месяцы в 'struct tm' переходят от' 0' к '11', поэтому октябрь -' 9', а не '10'. Вы печатаете результаты за 30 ноября, а не 30 октября. – Barmar

+0

Я решил это. Поле 'tm-> tm_year' основано с 1900 года. Поэтому в 2005 году мне нужно поставить (2005 - 1900) = 105 в этом поле. Тогда это работает. Что касается комментария @ barmar, код корректируется для подсчета 0-11 месяцев, так что это не ошибка. Функция 'set_tm' вычитает 1 из значения месяца. –

ответ

-2

UNIX время очень вяло. Оказывается, моя ошибка связана с полем tm_year в struct tm. Предполагается, что это количество лет с 1900 года, поэтому значение, которое идет в этом поле, должно быть 105, а не 2005 (например, 2005 - 1900 = 105). Теперь это дает правильный ответ.

Это определение структуры можно найти на this very useful page.

+0

Это действительно очень полезная сессия резиновой утки. Я отправил этот вопрос, а затем взял собак на прогулку. Ответ пришел ко мне на прогулку. :) –

+1

В этом нет ничего «unix». Время Unix - это целые секунды, начиная с эпохи в единице, которая называется UTC, но ведет себя как UT1. То, что вы смотрите, 'struct tm' или« время разбивки », полностью определено ISO C и не имеет никакого отношения к unix. –

+0

Это бесполезный комментарий. –