Localtime, mktime: вторая нормализация и поведение DST

Возьмите следующий фрагмент кода:

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)

Примечание. "%zu" в printf("(stamp: %zu)\n", stamp); не определен как спецификатор печати для time_t. Нет никаких определений. Разумной альтернативой является приведение к широкому типу, например intmax_t, или, возможно, даже к широкому типу FP: printf("(stamp: %jd)\n", (intmax_t) stamp);.

chux - Reinstate Monica 03.09.2018 17:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
175
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В зависимости от вашего точного часового пояса (и юрисдикции) ранним утром 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 без необходимости (и вы понимаете, что произойдет)
  • максимально работать с отметками времени в формате UTC и конвертировать только в местное время при отображении.

Нет, не то. Я изменил вопрос, чтобы показать это, но вкратце: добавление 25 часов не имеет такого же поведения, как добавление 9000 секунд, но в обоих случаях ясно, что я изменил соответствующее поле.

Cheiron 03.09.2018 13:49

@Cheiron: если вы добавляете 25 часов к 2018-10-28, 1:52:16, mktime может определить, что вы добавили часы, но нельзя быть уверенным, что это добавление пересекло границу летнего времени (вы могли бы добавить 3 к 23 вместо 25 к 1), поэтому он не рискует и не корректирует время.

Sander De Dycker 03.09.2018 13:57

Итак, поведение на самом деле другое, и добавление часов будет лучше относиться к изменениям летнего времени, верно?

Cheiron 03.09.2018 13:59

@Cheiron: это зависит от того, что вы имеете в виду под словом «лучше». Я бы пошел за рекомендациями в конце своего ответа.

Sander De Dycker 03.09.2018 14:02

Но мне действительно нужно выяснить, сколько времени в секундах было потрачено на то, чтобы переместиться, например, на один день вперед по местному времени. Поэтому ваши рекомендации мне не помогут. Мой код основан на stackoverflow.com/questions/310363/….

Cheiron 03.09.2018 14:04

@Cheiron: в вашем вопросе предлагается объяснить поведение, на которое я отвечу. Обращаясь к вашему предыдущему комментарию (который на самом деле является другим вопросом), в котором вы объясняете, чего вы действительно хотите достичь: mktime не предназначен для этого. Он сделает все возможное, чтобы попытаться угадать, что вы намеревались сделать. Но когда дело доходит до DST, здесь слишком много двусмысленности, и не всегда получается "правильно". Кроме того, разные реализации mktime будут предполагать по-разному. Таким образом, вы не можете рассчитывать на то, что поведение одной платформы будет одинаковым на другой.

Sander De Dycker 03.09.2018 14:35

Другие вопросы по теме