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 tm
tm_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?
changements de l'heure d'été à 02h00, et non 01h00. – Barmar
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
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. –