Получатель DST-переключателя для метки времени UNIX по местному времени текущего дня в полночь - PullRequest
2 голосов
/ 06 апреля 2011

(Язык / API: Стандартная библиотека C 89 и / или POSIX)

Возможно, это тривиальный вопрос, но у меня такое ощущение, что я что-то упустил.

Мне нужно реализовать эту функцию:

time_t get_local_midnight_timestamp(time_t ts);

То есть мы получаем произвольную метку времени (например, из прошлого года) и возвращаем ее с округлением до полуночи того же дня.

Проблема заключается в том, что функция должна знать о переключателях DST и изменениях правил DST (например, об отмене и / или расширении DST).

Функция также должна быть ориентирована на будущее и справляться со странными изменениями TZ (такими как смещение часового пояса на 30 минут вперед и т. Д.).

(Причина, по которой мне нужно все это, для реализации, искать в более старых статистических данных.)

Насколько я понимаю, наивный подход с обнулением struct tm полей временине работает - именно из-за летнего времени (похоже, что в день смены летнего времени есть две локальные метки времени time_t в полночь).

Пожалуйста, укажите мне правильное направление ...

Я сомневаюсь, чтоэто может быть сделано со стандартом C 89, так что POSIX-специфичные решения являются приемлемыми.Если не POSIX, то что-то специфичное для Debian будет делать ...

Обновление: Также: что-то подсказывает мне, что я должен также учитывать дополнительные секунды.Может быть, я должен попытаться напрямую использовать Tz базу данных ... (что довольно печально - так много / воспринимается / накладных расходов для такой маленькой задачи.) ... Или нет - кажется, что libc должен использовать этотак что, возможно, я просто делаю это неправильно ...

Обновление 2: Вот почему я думаю, что наивное решение не работает:

#include <stdio.h>
#include <time.h>

int main()
{
  struct tm date_tm;
  time_t date_start = 1301173200; /* Sunday 27 March 2011 0:00:00 AM MSK */
  time_t midnight = 0;
  char buf1[256];
  char buf2[256];
  int i = 0;

  for (i = 0; i < 4 * 60 * 60; i += 60 * 60)
  {
    time_t date = date_start + i;
    localtime_r(&date, &date_tm);

    strftime(buf1, 256, "%c %Z", &date_tm);

    date_tm.tm_sec = 0;
    date_tm.tm_min = 0;
    date_tm.tm_hour = 0;

    midnight = mktime(&date_tm);
    strftime(buf2, 256, "%c %Z", &date_tm);

    printf("%d : %s -> %d : %s\n", (int)date, buf1, (int)midnight, buf2);
  }
}

Вывод(местное время было MSD в тот момент, когда я запускаю это):

$ gcc time.c && ./a.out
1301173200 : Sun Mar 27 00:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301176800 : Sun Mar 27 01:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301180400 : Sun Mar 27 03:00:00 2011 MSD -> 1301169600 : Sat Mar 26 23:00:00 2011 MSK
1301184000 : Sun Mar 27 04:00:00 2011 MSD -> 1301169600 : Sat Mar 26 23:00:00 2011 MSK

Как видите, две ночи.

1 Ответ

5 голосов
/ 06 апреля 2011

Я запустил ваш код с переменной окружения TZ, установленной на "Европа / Москва", и смог воспроизвести ваш вывод. Вот что я думаю:

На первых двух строчках все нормально. Тогда мы «прыгнем вперед» и в 2 часа ночи становится 3 часа ночи. Давайте используем gdb, чтобы разбить вход на mktime и посмотрим, каков его аргумент каждый раз:

hour mday mon year wday yday isdst gmtoff tm_zone
   0   27   2  111    0   85     0  10800     MSK
   0   27   2  111    0   85     0  10800     MSK
   0   27   2  111    0   85     1  14400     MSD
   0   27   2  111    0   85     1  14400     MSD

Так что же случилось? Ваш код каждый раз устанавливает значение часа на 0, но это проблема после переключения DST, потому что невозможное произошло: теперь оно «до» переключения DST с точки зрения времени суток, но isdst теперь установлен и gmtoff был увеличен на один час. Взломав время, вы «создали» время полуночи, но с включенным DST, что в принципе недопустимо.

Теперь вы можете задаться вопросом, как мы можем выбраться из этого беспорядка? Не отчаивайся! Когда вы настраиваете поле tm_hour вручную, просто признайте, что вы больше не знаете, что такое DST-статус, установив tm_isdst в -1. Это специальное значение, которое задокументировано в man localtime , означает, что статус DST "недоступен". Так что компьютер все поймет, и все должно работать нормально.

Вот мой патч для вашего кода:

date_tm.tm_hour = 0;
+ date_tm.tm_isdst = -1; /* we no longer know if it's DST or not */

Теперь я получаю этот вывод, надеюсь, вы хотите:

$ TZ='Europe/Moscow' ./a.out
1301173200 : Sun Mar 27 00:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301176800 : Sun Mar 27 01:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301180400 : Sun Mar 27 03:00:00 2011 MSD -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301184000 : Sun Mar 27 04:00:00 2011 MSD -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
...