2017-02-17 2 views
0

J'essaie de comprendre comment fonctionne UNIX timelocal et mktime. Supposément ils traitent l'heure d'été lorsque vous passez la bonne valeur dans le champ struct tmtm_isdst.Pourquoi timelocal et mktime ne parviennent pas à gérer correctement l'heure d'été?

Je teste un moment très précis dans le temps. Selon la base de données timezone pour "America/New_York", l'heure d'été a été modifiée le 30 octobre 2005 à 01h00. Voici la sortie de zdump -v America/New_York que vous pouvez confirmer sur votre propre système. Je ne montrant un sous-ensemble des données autour de l'année 2005 (à droite de défilement pour voir gmtoff valeurs):

 
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 

Pour tester cette transition, je vais mettre en place un struct tm pour contenir 01h30 ce jour-là en particulier. Si je passe 0 pour tm_isdst il devrait me donner un gmtoffset de -18000. Si je passe 1 et activez l'heure d'été, alors gmtoffset devrait être -14400.

Voici le code que je utilise pour tester à la fois Darwin/OSX et 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; 
} 

L'exécution de ce sur différents systèmes d'exploitation donne des résultats différents. Sur FreeBSD cette sortie de code (défilement vers la droite pour voir les valeurs de 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] 

sur darwin/Mac OS X exactement le même code produit ce (défiler vers la droite pour voir les valeurs 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] 

A mes yeux il semble que les deux ont mal compris. Le champ tm_isdst semble n'avoir aucun effet sur le champ tm_gmtoff. La sortie tm_hour change lorsque vous utilisez mktime mais le décalage est toujours incorrect.

Si vous modifiez le tm_mday jours auparavant ou jours plus tard, le gmtoffset ne change pas du tout ce qui me trouble.

Est-ce que je fais quelque chose de mal ou ai-je mal interprété le fonctionnement de ces fonctions?

+1

changements de l'heure d'été à 02h00, et non 01h00. – Barmar

+3

Les mois dans 'struct tm' vont de' 0' à '11', donc Octobre est' 9', pas '10'. Vous imprimez les résultats pour le 30 novembre, pas le 30 octobre. – Barmar

+0

Je l'ai résolu. Le champ 'tm-> tm_year' est basé sur 1900. Donc pour 2005 je dois mettre (2005 - 1900) = 105 dans ce champ. Alors ça marche. En ce qui concerne le commentaire de @ barmar, le code s'ajuste déjà pour le compte de 0-11 mois, ce qui n'est pas l'erreur. La fonction 'set_tm' soustrait 1 de la valeur du mois. –

Répondre

-2

Le temps UNIX est très visqueux. Il s'avère que mon erreur a à voir avec le champ tm_year dans le struct tm. Il est supposé représenter le nombre d'années depuis 1900, donc la valeur qui va dans ce champ devrait être 105 et non 2005 (par exemple 2005 - 1900 = 105). Cela donne maintenant la bonne réponse.

Cette définition du struct se trouvent sur this very useful page.

+0

C'était en fait une session de canard en caoutchouc très utile. J'ai posté cette question et ensuite pris les chiens pour une promenade. La réponse m'est venue pendant la marche. :) –

+1

Il n'y a rien "unix" à ce sujet. Le temps Unix est un nombre entier de secondes depuis l'époque dans une unité appelée UTC mais se comporte plus comme UT1. Ce que vous regardez, 'struct tm' ou' temps décomposé ', est complètement spécifié par ISO C et n'a rien à voir avec unix. –

+0

C'est un commentaire inutile. –