Возьмите следующий фрагмент кода:
static void printTime(const struct tm* t, const time_t stamp){
printf("%d-%d-%d, %d:%d:%d (DST %s) (stamp: %zu)\n",
1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, t->tm_isdst ? "Active" : "Inactive", stamp);
}
int main(){
time_t t = 1540633936;
struct tm tStruct;
localtime_r(&t, &tStruct);
printTime(&tStruct, t);
for (unsigned i = 0; i < 14; ++i){
tStruct.tm_sec += 7200;
//tStruct.tm_hour += 2;
tStruct.tm_isdst = -1;
t = mktime(&tStruct);
localtime_r(&t, &tStruct);
printTime(&tStruct, t);
}
return 0;
}
Он показывает два способа увеличения даты. Документация mktime сообщает мне:
The mktime() function modifies the fields of the tm structure as follows: tm_wday and tm_yday are set to values determined from the contents of the other fields; if structure members are outside their valid interval, they will be normalized (so that, for example, 40 October is changed into 9 November); tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time.
Исходя из этого, я ожидаю, что нормализация будет работать таким образом, что добавление 7200 секунд эквивалентно добавлению двух часов. Но вывод отличается:
tStruct.tm_sec += 7200;
Дает:
2018-10-27, 11:52:16 (DST Active) (stamp: 1540633936) 2018-10-27, 13:52:16 (DST Active) (stamp: 1540641136) 2018-10-27, 15:52:16 (DST Active) (stamp: 1540648336) 2018-10-27, 17:52:16 (DST Active) (stamp: 1540655536) 2018-10-27, 19:52:16 (DST Active) (stamp: 1540662736) 2018-10-27, 21:52:16 (DST Active) (stamp: 1540669936) 2018-10-27, 23:52:16 (DST Active) (stamp: 1540677136) 2018-10-28, 1:52:16 (DST Active) (stamp: 1540684336) 2018-10-28, 2:52:16 (DST Inactive) (stamp: 1540691536) 2018-10-28, 3:52:16 (DST Inactive) (stamp: 1540695136) 2018-10-28, 5:52:16 (DST Inactive) (stamp: 1540702336) 2018-10-28, 7:52:16 (DST Inactive) (stamp: 1540709536) 2018-10-28, 9:52:16 (DST Inactive) (stamp: 1540716736) 2018-10-28, 11:52:16 (DST Inactive) (stamp: 1540723936) 2018-10-28, 13:52:16 (DST Inactive) (stamp: 1540731136)
(обратите внимание на неправильные скачки времени сразу после перехода на летнее время)
tStruct.tm_hour += 2;
Дает:
2018-10-27, 11:52:16 (DST Active) (stamp: 1540633936) 2018-10-27, 13:52:16 (DST Active) (stamp: 1540641136) 2018-10-27, 15:52:16 (DST Active) (stamp: 1540648336) 2018-10-27, 17:52:16 (DST Active) (stamp: 1540655536) 2018-10-27, 19:52:16 (DST Active) (stamp: 1540662736) 2018-10-27, 21:52:16 (DST Active) (stamp: 1540669936) 2018-10-27, 23:52:16 (DST Active) (stamp: 1540677136) 2018-10-28, 1:52:16 (DST Active) (stamp: 1540684336) 2018-10-28, 3:52:16 (DST Inactive) (stamp: 1540695136) 2018-10-28, 5:52:16 (DST Inactive) (stamp: 1540702336) 2018-10-28, 7:52:16 (DST Inactive) (stamp: 1540709536) 2018-10-28, 9:52:16 (DST Inactive) (stamp: 1540716736) 2018-10-28, 11:52:16 (DST Inactive) (stamp: 1540723936) 2018-10-28, 13:52:16 (DST Inactive) (stamp: 1540731136) 2018-10-28, 15:52:16 (DST Inactive) (stamp: 1540738336)
Это ожидаемое поведение (по крайней мере, для меня).
Итак, мой вопрос: действительно ли ошибка? Или это где-то задокументированное поведение?
Это также происходит, когда tm_hour нужно изменить с помощью mktime. Возьмем следующий пример:
tStruct.tm_hour += 25;
Дает:
2018-10-27, 11:52:16 (DST Active) (stamp: 1540633936) 2018-10-28, 12:52:16 (DST Inactive) (stamp: 1540727536) 2018-10-29, 13:52:16 (DST Inactive) (stamp: 1540817536) 2018-10-30, 14:52:16 (DST Inactive) (stamp: 1540907536) 2018-10-31, 15:52:16 (DST Inactive) (stamp: 1540997536) 2018-11-1, 16:52:16 (DST Inactive) (stamp: 1541087536) 2018-11-2, 17:52:16 (DST Inactive) (stamp: 1541177536) 2018-11-3, 18:52:16 (DST Inactive) (stamp: 1541267536) 2018-11-4, 19:52:16 (DST Inactive) (stamp: 1541357536) 2018-11-5, 20:52:16 (DST Inactive) (stamp: 1541447536) 2018-11-6, 21:52:16 (DST Inactive) (stamp: 1541537536) 2018-11-7, 22:52:16 (DST Inactive) (stamp: 1541627536) 2018-11-8, 23:52:16 (DST Inactive) (stamp: 1541717536) 2018-11-10, 0:52:16 (DST Inactive) (stamp: 1541807536) 2018-11-11, 1:52:16 (DST Inactive) (stamp: 1541897536)
tStruct.tm_sec += 90000
Дает:
2018-10-27, 11:52:16 (DST Active) (stamp: 1540633936) 2018-10-28, 11:52:16 (DST Inactive) (stamp: 1540723936) 2018-10-29, 12:52:16 (DST Inactive) (stamp: 1540813936) 2018-10-30, 13:52:16 (DST Inactive) (stamp: 1540903936) 2018-10-31, 14:52:16 (DST Inactive) (stamp: 1540993936) 2018-11-1, 15:52:16 (DST Inactive) (stamp: 1541083936) 2018-11-2, 16:52:16 (DST Inactive) (stamp: 1541173936) 2018-11-3, 17:52:16 (DST Inactive) (stamp: 1541263936) 2018-11-4, 18:52:16 (DST Inactive) (stamp: 1541353936) 2018-11-5, 19:52:16 (DST Inactive) (stamp: 1541443936) 2018-11-6, 20:52:16 (DST Inactive) (stamp: 1541533936) 2018-11-7, 21:52:16 (DST Inactive) (stamp: 1541623936) 2018-11-8, 22:52:16 (DST Inactive) (stamp: 1541713936) 2018-11-9, 23:52:16 (DST Inactive) (stamp: 1541803936) 2018-11-11, 0:52:16 (DST Inactive) (stamp: 1541893936)





В зависимости от вашего точного часового пояса (и юрисдикции) ранним утром 2018-10-28 часы переводятся на 1 час назад, потому что Летнее время заканчивается. Из ваших примеров кажется, что это происходит в 3:00 в вашем часовом поясе / юрисдикции.
В первом случае (добавление 7200 секунд к 2018-10-28, 1:52:16) значение tm_sec выходит за пределы нормального диапазона (0-59), поэтому mktime может определить, что вы добавили 2 часа, и поскольку он знает, что он пересекает границу летнего времени, он соответствующим образом корректирует время. Это приводит к 2018-10-28, 2:52:16, то есть через 2 часа после 2018-10-28, 1:52:16.
Для следующего приращения в первом случае (добавление 7200 секунд к 2018-10-28, 2:52:16) то же самое происходит снова (поскольку вы снова пересекаете границу летнего времени - вы сбросили tm_isdst на -1 после все). Это приводит к 2018-10-28, 3:52:16, то есть через 2 часа после 2018-10-28, 2:52:16.
Во втором случае (добавление 2 часов к 2018-10-28, 1:52:16) значение tm_hour все еще находится в нормальном диапазоне (0-23), поэтому mktime не может определить, что вы добавили 2 часа, и он просто рассматривает это как местное время. Это приводит к 2018-10-28, 3:52:16, то есть через 3 часа после 2018-10-28, 1:52:16.
Чтобы избежать подобных проблем:
tm_isdst на -1 без необходимости (и вы понимаете, что произойдет)Нет, не то. Я изменил вопрос, чтобы показать это, но вкратце: добавление 25 часов не имеет такого же поведения, как добавление 9000 секунд, но в обоих случаях ясно, что я изменил соответствующее поле.
@Cheiron: если вы добавляете 25 часов к 2018-10-28, 1:52:16, mktime может определить, что вы добавили часы, но нельзя быть уверенным, что это добавление пересекло границу летнего времени (вы могли бы добавить 3 к 23 вместо 25 к 1), поэтому он не рискует и не корректирует время.
Итак, поведение на самом деле другое, и добавление часов будет лучше относиться к изменениям летнего времени, верно?
@Cheiron: это зависит от того, что вы имеете в виду под словом «лучше». Я бы пошел за рекомендациями в конце своего ответа.
Но мне действительно нужно выяснить, сколько времени в секундах было потрачено на то, чтобы переместиться, например, на один день вперед по местному времени. Поэтому ваши рекомендации мне не помогут. Мой код основан на stackoverflow.com/questions/310363/….
@Cheiron: в вашем вопросе предлагается объяснить поведение, на которое я отвечу. Обращаясь к вашему предыдущему комментарию (который на самом деле является другим вопросом), в котором вы объясняете, чего вы действительно хотите достичь: mktime не предназначен для этого. Он сделает все возможное, чтобы попытаться угадать, что вы намеревались сделать. Но когда дело доходит до DST, здесь слишком много двусмысленности, и не всегда получается "правильно". Кроме того, разные реализации mktime будут предполагать по-разному. Таким образом, вы не можете рассчитывать на то, что поведение одной платформы будет одинаковым на другой.
Примечание.
"%zu"вprintf("(stamp: %zu)\n", stamp);не определен как спецификатор печати дляtime_t. Нет никаких определений. Разумной альтернативой является приведение к широкому типу, напримерintmax_t, или, возможно, даже к широкому типу FP:printf("(stamp: %jd)\n", (intmax_t) stamp);.