Будет ли `gmtime()` сообщать секунды как 60, когда в секунду прыжка?

У меня есть сервер, работающий в TZ=UTC, и у меня есть такой код:

time_t t = time(NULL);
struct tm tm;
gmtime_r(&t, &tm);

Вопрос: будет ли tm.tm_sec == 60, когда сервер находится в пределах секунды?

Например, если бы я находился в следующем интервале времени:

1998-12-31T23:59:60.00 - 915 148 800.00
1998-12-31T23:59:60.25 - 915 148 800.25
1998-12-31T23:59:60.50 - 915 148 800.50
1998-12-31T23:59:60.75 - 915 148 800.75
1999-01-01T00:00:00.00 - 915 148 800.00

будет gmtime() возвращать tm == 1998-12-31T23:59:60 для time_t = 915148800 и, однажды из секунды прыжка, верните tm == 1999-01-01T00:00:00 для того же time_t?

Ответ 1

Короткий ответ: нет, практически говоря gmtime_r никогда не будет заполнять tm_sec 60. Это несчастливо, но неизбежно.

Основная проблема заключается в том, что time_t есть, по стандарту Posix, количество секунд с 1970-01-01 UTC, не предполагая никаких секунд прыжка.

В течение последней секунды прыжка прогрессия была такой:

1483228799    2016-12-31 23:59:59
1483228800    2017-01-01 00:00:00

Да, там должен был быть прыжок второй, 23:59:60. Но между 1483228799 и 1483228800 нет значения time_t.

Я знаю два способа для варианта gmtime вернуть время, заканчивающееся на :60:

  • Вы можете запускать часы ОС на чем-то, кроме UTC, обычно TAI или TAI-10, и использовать так называемые "правильные" часовые пояса для преобразования в UTC (или локальное время) для отображения. См. эту веб-страницу для обсуждения этого вопроса.

  • Вы можете использовать clock_gettime() и определить новое значение clkid, возможно CLOCK_UTC, которое обходится вокруг проблемы time_t, используя при необходимости ненулевое значение struct timespec. Например, способ получить значение времени между 1483228799 и 1483228800 - установить tv_sec на 1483228799 и tv_nsec на 1000000000. Подробнее см. эту веб-страницу.

Путь № 1 работает очень хорошо, но никто не использует его, потому что никто не хочет запускать свои часы ядра ни на что другое, кроме UTC, которое оно должно быть. (У вас возникают проблемы с такими моментами, как временные метки файловой системы, и такие программы, как tar, которые вставляют эти метки времени.)

Way # 2 - прекрасная идея, IMO, но, насколько мне известно, она никогда не была реализована в выпущенной ОС. (Как это бывает, у меня есть рабочая реализация для Linux, но я еще не выпустил свою работу.) Чтобы путь № 2 работал, вам нужен новый gmtime вариант, возможно gmtime_ts_r, который принимает struct timespec вместо time_t.


Добавление: я просто перечитал название вопроса. Вы спросили: "Будет ли gmtime() сообщать 60 секунд, когда сервер находится на скачке второго?" Мы могли бы ответить на это, сказав "да, но", с отказом от права, поскольку, поскольку большинство серверов не могут представлять время в течение прыжковой секунды правильно, они никогда не будут "on" секундомером.


Добавление 2: Я забыл упомянуть, что схема №1 работает лучше для локальных времен - то есть, когда вы вызываете один из вариантов localtime, чем для UTC и gmtime. Очевидно, что для преобразований, выполняемых localtime, зависит настройка переменной среды TZ, но не так ясно, что TZ оказывает какое-либо влияние на gmtime. Я заметил, что некоторые реализации gmtime находятся под влиянием TZ и поэтому могут совершать прыжки секунд в соответствии с "правильными" зонами, а некоторые не могут. В частности, gmtime в GNU glibc, похоже, обращает внимание на вторую секундную информацию в "правой" зоне, если TZ указывает один, тогда как gmtime в IANA tzcode distribution не работает.

Ответ 2

Вопрос: будет ли tm.tm_sec == 60, когда сервер находится в пределах секунды?

Нет. В типичной системе UNIX time_t подсчитывает количество секунд без скачка с эпохи (1970-01-01 00:00:00 GMT). Таким образом, преобразование a time_t в struct tm всегда даст временную структуру со значением tm_sec между 0 и 59.

Игнорирование секунд прыжка в расчете time_t позволяет конвертировать a time_t в человеко-читаемую дату/время без полного знания всех секунд прыжка до этого времени. Это также позволяет однозначно преобразовать значения time_t в будущем; включая секунды прыжка, сделало бы это невозможным, поскольку присутствие секунды прыжка не известно более чем через 6 месяцев в будущем.

Существует несколько способов, с помощью которых UNIX и UNIX-подобные системы обрабатывают прыжки секунд. Как правило, либо:

  • Одно значение time_t повторяется для второй секунды. (Это результат строгой интерпретации стандартов, но приведет к сбою многих приложений, так как кажется, что время ушло в прошлое.)

  • Системное время выполняется немного медленнее в течение некоторого времени, окружая второй прыжок, чтобы "размазать" секунду прыжка в более широком периоде. (Это решение было принято многими крупными облачными платформами, включая Google и Amazon. Он избегает любых локальных несогласованностей часов, за счет того, что оставшиеся системы до половины секунды не синхронизируются с UTC в течение продолжительности.)

  • Системное время установлено на TAI. Так как это не включает в себя секунды прыжка, не требуется никакая секундная обработка. (Это редко, так как это приведет к тому, что система несколько секунд не синхронизируется с системами UTC, которые составляют большую часть мира. Но это может быть жизнеспособным вариантом для систем, которые практически не имеют контакта с внешним миром, и, следовательно, не имеют возможности узнать о предстоящих секундах прыжка.)

  • Система полностью не осознает секунды прыжка, но ее клиент NTP исправит часы после того, как второй прыжок покинет системные часы на одну секунду с правильного времени. (Это то, что делает Windows.)

Ответ 3

На этот вопрос нет простого ответа. Если на секунду потребуется секунда секунды, вам потребуется 1) что-то в ОС, чтобы узнать, что есть вторая секунда, и 2) для библиотеки C, которую вы используете, чтобы также знать о секунде прыжка, и что-то с этим делать.

Огромного количества ОС и библиотек нет.

Лучшее, что я нашел, это современные версии ядра Linux, объединенные с gpsd и ntpd, используя GPS-приемник в качестве ссылки на время. GPS рекламирует секунды прыжка в своем системном потоке данных, а gpsd, ntpd и ядро ​​Linux могут поддерживать CLOCK_TAI, пока происходит скачок, а системные часы тоже правильные. Я не знаю, делает ли glibc разумную вещь со вторым прыжком.

В других UNIX-пространствах ваш пробег будет отличаться. Радикально.

Windows - это зона бедствия *******. Например, класс DateTime в С# не знает об исторических секундах скачка. Системные часы скачут 1 секунду в следующий раз, когда будет получено обновление сетевого времени.

Ответ 4

POSIX определяет взаимосвязь между значениями time_t "Секунды с эпохи" и сломанным временем (struct tm) точно таким образом, что не допускает скачков или TAI, поэтому по существу (до некоторой двусмысленности о том, что должно происходить около секунд прыжка), значения POSIX time_t - это UT1, а не UTC, и результаты gmtime отражают это. На самом деле нет способа адаптировать или изменить это, совместимое с существующими спецификациями и существующим программным обеспечением на их основе.

Правильный путь вперед почти наверняка представляет собой сочетание того, что Google сделал с прыжок второго размытия и стандартизованная формула для преобразования назад и вперед между "размытым временем UTC" и "фактическим временем UTC" (и, следовательно, TAI) в 24-часовом окне вокруг секунды прыжка и API для выполнения этих преобразований.

Ответ 5

Я прочитал это на www.cplusplus.com о gmtime: "Использует значение, указанное таймером, для заполнения структуры tm со значениями, которые представляют соответствующее время, выраженное как время UTC (т.е. время в часовой пояс GMT )".

Итак, есть противоречие. UTC имеет секунды абсолютно постоянной длины и поэтому требует секунд прыжка, в то время как GMT имеет дни ровно 86 400 секунд очень незначительной длины. gmtime() не может одновременно работать в UTC и GMT.

Когда нам говорят, что gmtime() возвращает "UTC, не принимая никаких секунд прыжка", я бы предположил, что это означает GMT. Это означало бы, что не будет записано никаких секунд скачка, и это будет означать, что время медленно расходится с UTC, пока разница не составит 0,9 секунды, а второй по времени добавляется в UTC, но не в GMT. Это легко обрабатывать для разработчиков, но не совсем точно.

Один из вариантов состоит в том, чтобы иметь постоянные секунды, пока вы не приблизитесь к прыжковой секунде, а затем отрегулируйте, может быть, 1000 секунд вокруг этого прыжка в длину. Он также прост в обращении, на 100% точно в большинстве случаев, и 0,1% ошибка в длине секунды, иногда на 1000 секунд.

И второй вариант состоит в том, чтобы иметь постоянные секунды, иметь прыжок секунд, а затем забыть их. Таким образом, gmtime() вернет ту же секунду два раза подряд, перейдя от x секунд от 0 наносекунд до x секунд 999999999 наносекунд, затем снова с x секунд 0 наносекунд до x секунд 999999999 наносекунд, затем до x + 1 секунды. Это вызовет проблемы.

Конечно, будет полезно использовать еще часы, которые вернут точный UTC, включая секунды прыжка, с точно точными секундами. Чтобы перевести "секунды с эпохи" на год, месяц, день, часы, минуты, секунды, требуется знание всех секунд прыжка с эпохи (или до эпохи, если вы обрабатываете времена до этого). И часы, которые возвратят гарантированный точный GMT без каких-либо прыжков секунд и секунд, которые почти, но не совсем постоянны.

Ответ 6

Другим углом к ​​их проблеме является наличие библиотеки, которая "знает" о скачкообразных секундах. В большинстве библиотек нет, и поэтому ответы, которые вы получаете от таких функций, как gmtime, являются, строго говоря, неточными во время прыжка. Также расчеты разницы во времени часто приводят к неточным результатам, охватывающим второй шаг. Например, значение time_t, предоставленное вам в то же время UTC вчера, точно на 86400 секунд меньше сегодняшнего значения, даже если на самом деле был прыжок второй.

Астрономическое сообщество это решило. Вот библиотека SOFA, в которой есть правильные временные процедуры внутри. См. их руководство (PDF), раздел о временных масштабах. Если вы сделали часть своего программного обеспечения и постоянно обновлялись (требуется новая версия для каждой новой секунды), у вас есть точные расчеты времени, конверсии и отображение.