Как вы хорошо знаете, в Unix-системах мы измеряем время как количество секунд, прошедших с «эпохи»: 00:00:00 UTC 1 января 1970 года. Немало людей сильно разозлилось из-за этого, да и вообще, общественное мнение сочло это ошибкой.

Во-первых, это определение основано не на чём-то разумном, например, на объективной частоте колебаний атома цезия-133, а на удобной доле времени полного оборота одного большого камня вокруг собственной оси.

Во времени Unix каждый день гарантированно состоит из 86400 секунд и мы притворяемся, что это число равномерно увеличивается. Когда оказывается, что вышеупомянутый камень на самом деле вращался дольше, чем удобно для нас, и нам нужно добавить секунду координации, то мы просто притворяемся, что этого не было, а механизм меток времени не идентифицирует уникальный момент времени.

Ещё один аспект, который продолжает вызывать проблемы, когда мы пытаемся считать секунды, заключается в том, что мы сталкиваемся с проблемами хранения и описания данных, потому что, как оказалось, компьютеры не так уж хорошо справляются с числами. Не говоря уж об "эпохальном сбое".

Как же мы к этому пришли? Всё началось в 1971 году, когда в в первом издании руководства для программиста Unix было дано определение времени Unix как "времени с 00:00:00 1 января 1971 года, измеряемого в шестидесятых долях секунды":


Именно так, изначально эпохой Unix было 1971-01-01T00:00:00. Вы спросите, какого часового пояса? Ну, это точно не был «UTC», потому что он заменит GMT в качестве стандартного времени только в 1972 году. Во-вторых, обратите внимание, что время измерялось в 1/60 секунды, а не в секундах. Зачем же так сделали?

Вспомним, что в то время Unix разрабатывался в США на PDP-11. Эти системы имели часы линейного времени (Line-Time Clock, LTC), использующие частоту питания переменного тока для генерации прерывания для процессора. Тогда это прерывание использовалось для обновления системных часов.

Забавно здесь то, что эта частота прерываний зависит от частоты в сети источника питания. В основной части Азии и Европы эта частота равна 50 Гц, однако в США Westinghouse Electric Corporation (конкурент Томаса Эдисона и General Electric; любопытный факт: позже Westinghouse приобрела CBS, а затем была куплена Viacom) заметила, что использовавшиеся тогда дуговые лампы с угольным электродом меньше мерцают при 60 Гц, а поэтому стандартизировала эту частоту.

(Странный побочный эффект этого различия между США и Европой заключается в том, что в Японии используются обе частоты: на западе, где первые генераторы были куплены у немецкой AEG и установлены в Токио, частота в сети равна 50 Гц; на востоке, в Осаке, были установлены генераторы G.E., и используются 60 Гц. В результате этого в стране теперь работает множество высоковольтных линий постоянного тока для преобразования электричества между двумя регионами!)


Электросеть Японии

Итак, в первом издании UNIX измерял время на этих 60 Гц с хранением в 32-битных integer, таким образом имея возможность учитывать только 2^32 / 60 тактов/с * 60 с/мин * 60 мин/ч * 24 ч/д * 365 д/г = 2,3 лет, как и написано в руководстве. Несколько позже в том же 1971 году измерению времени было дано другое определение, оно учитывалось в секундах и могло описывать до 136 лет. Датой же эпохи Unix был достаточно произвольно выбран 1970 год (вопреки распространённому заблуждению о том, что она обозначает дату рождения Unix):

«В то время у нас не было плёночных накопителей, работала пара файловых систем, и мы постоянно меняли начало отсчёта времени. Поэтому в конце концов мы сказали: давайте выберем что-то одно, что достаточно долго не будет переполняться. Нас вполне устроил 1970 год», — сказал он.

Дэннис Ритчи, Wired

(Истинная дата рождения Unix приходится примерно на 1969 год, когда Кен Томпсон портировал "Space Travel" на PDP-7, поэтому легко понять, почему эпоха Unix кажется обозначением рождения Unix.)

Секунды координации


Итак, теперь у нас есть 32-битный счётчик секунд от эпохи, который равномерно увеличивает значение с частотой 1 Гц и гарантирует нам, что будет насчитывать ровно 86400 секунд в любой 24-часовой период. Однако наш космический булыжник отсчёта замедляется в своём вращении, поэтому время от времени это число нужно изменять.

Для этого Международная служба вращения земли (IERS) отправляет всем Повелителям Времени электронные письма, сообщающие, должна или нет добавляться секунда координации:


Сегодня мы не можем винить время эпохи Unix за то, что оно изначально не учитывало секунды координации, потому что их не существовало до 1972 года. С тех пор (на 2022 год) произошло уже 27 положительных секунд координации, последняя из которых была введена в конце 2016 года. Каждый раз, когда происходит секунда координации, время эпохи Unix просто притворяется, что этого не было, из-за чего две даты соответствуют одной метке времени эпохи (epoch-time.c):

From epoch to time, via gmtime(3) to strftime(3):
1483228798 2016-12-31T23:59:58
1483228799 2016-12-31T23:59:59
//здесь отсутствующая секунда координации
1483228800 2017-01-01T00:00:00

From time to epoch, via strptime(3) to mktime(3):
2016-12-31T23:59:58 is 1483228798
2016-12-31T23:59:59 is 1483228799
2016-12-31T23:59:60 is 1483228800
2017-01-01T00:00:00 is 1483228800 //здесь дублирующаяся метка времени

(И давайте не забывать обо всех отрицательных секундах координации и тот факт, что некоторые системы Unix определяют диапазон tm_sec как [0-61], учитывая мифическую «двойную секунду координации», которой на самом деле никогда не существовало.)

Хорошо в этом то, что всё это соответствует требованиям POSIX:

4.16 Секунды после эпохи

Значение, которое аппроксимирует количество секунд, прошедшее после эпохи.

Соотношение между истинным временем суток и текущим значением секунд после эпохи точно не установлено.

Способ внесения любых изменений в значение секунд после эпохи для согласования с нужным соотношением с текущим временем зависит от реализации. В формате секунд после эпохи каждый день насчитывает ровно 86400 секунд.


Проблема 2038 года


Даже при равномерном отсчёте и игнорировании секунд координации используемый для измерения секунд после эпохи тип данных time_t неизбежно придёт к переполнению. Хорошо, что есть стандарты, которые спасут нас в этой ситуации! Пусть POSIX не решает проблемы, но хорошо в стандартах то, что их много и можно выбрать подходящий, не так ли? Ох, постойте, в чём же дело? Давайте разберёмся.

Плохие новости: стандарты нас не спасут. Например, стандарт C гласит:

Диапазон и степень точности времени, представленного в clock_t и time_t, зависят от реализации.

Что ж, ладно. Имея 32-битный time_t, мы можем рассчитывать начиная с 1970 года примерно на 136 лет. Однако time_t является знаковым 32-битным integer, то есть у нас есть всего 68 лет в обоих направлениях, из-за чего возникает так называемая "Проблема 2038 года".

Она состоит в том, что самая большая дата, которую можно записать как time_t при помощи знакового 32-битного integer — это 2^31 - 1 = 2147483647 эпохи, или 2038-01-19T03:14:07Z:


Это легко исправить, правда? Мы «просто» изменяем тип данных time_t на знаковый 64-битный integer, что даёт нам теоретический максимум даты эпохи 2^63-1 = 9223372036854775807 после 1 января 1970 года. Как любят говорить люди, это значение задаёт дату примерно на 292 миллиарда лет в будущем, или примерно в 22 раз больше приблизительного возраста Вселенной, поэтому официально может считаться Чьей-то Чужой Проблемой.

Но — всегда есть «но», не правда ли? — действительно ли это произойдёт? Почему бы не попробовать и не проверить, как разные системы поведут себя, если мы передадим им для обработки не совсем логичные значения времени?

Оборот mktime(3)


Хотя время представлено в виде time_t, используется и другой популярный формат для записи разбитого на части времени — struct tm, из которого можно получить time_t, вызвав mktime(3). В POSIX зафиксированы следующие элементы struct tm:

int    tm_sec   Seconds [0,60]. 
int    tm_min   Minutes [0,59]. 
int    tm_hour  Hour [0,23]. 
int    tm_mday  Day of month [1,31]. 
int    tm_mon   Month of year [0,11]. 
int    tm_year  Years since 1900. 
int    tm_wday  Day of week [0,6] (Sunday =0). 
int    tm_yday  Day of year [0,365]. 
int    tm_isdst Daylight Savings flag.

Забавно в описанных здесь диапазонах то, что они в лучшем случае являются… рекомендательными. В POSIX конкретно написано:

… исходные значения [...] компонентов не должны быть ограничены диапазонами, описанными в <time.h>.

Так что же произойдёт, если передать mktime(2) тип struct tm со значениями вне этих диапазонов? Рассмотрим следующую программу:

int main() {
        struct tm t;
        time_t epoch;

	/* 2022-12-31 */
        t.tm_year = 122; t.tm_mon = 11; t.tm_mday = 31;

	/* 22:57 */
        t.tm_hour = 22; t.tm_min = 57;

        t.tm_sec = 3785;

        if ((epoch = mktime(&t)) == -1) {
                err(EXIT_FAILURE, "mktime");
        }

        (void)printf("%s", ctime(&epoch));

	return 0;
}
$ cc -Wall -Werror -Wextra t.c
$ ./a.out
Sun Jan  1 00:00:05 2023
$ 

Здесь перед присвоением tm_sec значения наш struct tm задавал дату December 31st, 22:57:00, 2022. Но потом мы присвоили tm_sec значение 3785, то есть 1 час, 3 минуты и 5 секунд. Это приводит к тому, что выполняется инкремент tm_min на 3 минуты, что приводит к увеличению tm_hour на единицу. Затем мы ещё раз выполняем инкремент tm_hour (из оставшихся от tm_sec 3600 секунд), что приводит к оборачиванию этого значения на 00 и необходимости инкремента tm_mday. Теперь tm_mday оборачивается до 01 с инкрементом tm_year, и в результате мы получаем дату 2023-01-01T00:00:05.

Такая нормализация меток времени становится ещё более запутанной, когда мы добавляем отрицательные значения (например, tm_mday = -1 означает предыдущий день tm_mon - 1) или (снова) при участии секунд координации, что может стать причиной головной боли. Лучше избегать такой ситуации.

Забавная date(1)


Давайте рассмотрим даты эпохи в момент или рядом с эпохой. Проигнорируем тот факт, что время эпохи до 1972 года определено не очень чётко, поскольку время эпохи по определению считается в UTC, но (см. выше) UTC стандартизировали только в 1972 году. Ну, мы поступим точно так же, как Unix поступает с секундами координации, и притворимся, что это нас не волнует.

Отобразить произвольную дату при помощи команды date(1) довольно легко. Достаточно просто передать дату в формате CCyymmddHHMM.SS. Если только вы не работаете в Linux, где date(1) GNU хочет использовать, наверное, самый выбешивающий формат (MMDDhhmmCCYY.ss). Зачем вставлять год между минутами и секундами?

Однако использование любого из этих форматов всё равно бесполезно для наших целей, потому что в определённый момент нам нужно будет выйти за пределы годов из четырёх цифр, поэтому давайте укажем непосредственно секунды после эпохи ("-r <seconds>" для date(1) в BSD, "--date @<seconds>" для date(1) в GNU).

Отобразить даты в момент эпохи или рядом с ней довольно просто:

NetBSD / FreeBSD / macOS:
$ date -r 0
Thu Jan  1 00:00:00 UTC 1970
$ date -r -1
Wed Dec 31 23:59:59 UTC 1969
$ date -r 1 
Thu Jan  1 00:00:01 UTC 1970

Linux:
$ date --date @0
Thu Jan  1 12:00:00 AM UTC 1970
$ date --date @-1
Wed Dec 31 11:59:59 PM UTC 1969
$ date --date @1
Thu Jan  1 12:00:01 AM UTC 1970

OmniOS (с использованием даты GNU)
$ date -r 0
January  1, 1970 at 12:00:00 AM UTC
$ date -r -1
December 31, 1969 at 11:59:59 PM UTC
$ date -r 1
January  1, 1970 at 12:00:01 AM UTC

(date(1) GNU, использующая AM/PM вместо 24 часов, раздражает, ну да ладно.)

Давайте посмотрим, что произойдёт, если мы попробуем проверить 32-битный time_t. Как говорилось выше, проблема 2038 года возникнет в 2147483648 / -2147483649 эпохи:

netbsd$ date -r 2147483647
Tue Jan 19 03:14:07 UTC 2038
netbsd$ date -r 2147483648
Tue Jan 19 03:14:08 UTC 2038
netbsd$ date -r -2147483648
Fri Dec 13 20:45:52 UTC 1901
netbsd$ date -r -2147483649
Fri Dec 13 20:45:51 UTC 1901

linux$ date --date @2147483647
Tue Jan 19 03:14:07 AM UTC 2038
linux$ date --date @2147483648
Tue Jan 19 03:14:08 AM UTC 2038
linux$ date --date @-2147483648
Fri Dec 13 08:45:52 PM UTC 1901
linux$ date --date @-2147483649
Fri Dec 13 08:45:51 PM UTC 1901

omnios$ date -r -2147483648
December 13, 1901 at 08:45:52 PM UTC
omnios$ date -r -2147483649
date: failed to parse -r argument: -2147483649
omnios$ date -r 2147483647
January 19, 2038 at 03:14:07 AM UTC
omnios$ date -r 2147483648
date: failed to parse -r argument: 2147483648

О, постойте-ка. Похоже, у OmniOS возникают проблемы с датами эпохи после 2^31. Интересно, что произойдёт, если мы не только отобразим дату, но и установим её? Давайте в цикле установим дату и выведем её:

omnios$ for s in 5 6 7 8; do sudo date -u 011903142038.0$s; date; done
January 19, 2038 at 03:14:05 AM UTC
January 19, 2038 at 03:14:05 AM UTC
January 19, 2038 at 03:14:06 AM UTC
January 19, 2038 at 03:14:06 AM UTC
January 19, 2038 at 03:14:07 AM UTC
December 13, 1901 at 08:45:52 PM UTC //!!!
ld.so.1: date: fatal: /lib/libc.so.1: Value too large for defined data type
Killed
omnios$ date
ld.so.1: date: fatal: /lib/libc.so.1: Value too large for defined data type
Killed
omnios$ ls
ld.so.1: ls: fatal: /lib/libc.so.1: Value too large for defined data type
Killed
omnios$ sudo reboot
sudo: unknown uid 100
sudo: error initializing audit plugin sudoers_audit
omnios$

Замечательно. Обратите внимание, что дата на самом деле оборачивается, но ОС сходит с ума. Но ведь у других систем не должно быть никаких проблем с установкой даты, правильно?

netbsd# date 197001010000; date +%s
Thu Jan  1 00:00:00 UTC 1970
0
netbsd# date 196912312359
date: settimeofday: Invalid argument
netbsd$

linux$ sudo date -s @0
date: cannot set date: Invalid argument
Thu Jan  1 12:00:00 AM UTC 1970
linux$ uptime; sudo date -s @7080
 03:34:27 up  1:57,  1 user,  load average: 0.04
Thu Jan  1 01:58:00 AM UTC 1970

Вот так, в NetBSD мы не можем установить дату до эпохи, а в Linux (с версии ядра 4.3) мы не можем установить дату до текущего аптайма. settimeofday(2) вернёт EINVAL из-за гарантий, даваемых его "CLOCK_MONOTONIC", который, согласно POSIX, "представляет количество времени после неуказанной точки в прошлом":

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

С учётом всего этого, похоже, OmniOS вычисляет аптайм на основе времени запуска относительно системной даты, а, например, NetBSD и Linux хранят отдельные счётчики:

omnios$ sudo date -u 010100001970.00
January  1, 1970 at 12:00:00 AM GMT
omnios$ uptime
00:01:46    up -49 min(s),  1 user,  load average: 0.00, 0.00, 0.02
omnios$ sudo date -u 011803142038.00
January 18, 2038 at 03:14:00 AM GMT
omnios$ uptime
03:14:11    up 5567 day(s), 7 min(s),  1 user,  load average: 0.24, 0.16, 0.07

Попытка установить дату до эпохи тоже приводит к разным результатам. В NetBSD и Linux это сделать просто не удастся, а в OmniOS будет установлен месяц, день, час, минута и секунда, однако год будет ограничен значением 1970:

omnios$ sudo date -u 020100001969.00
February  1, 1970 at 12:00:00 AM GMT
omnios$ date
February  1, 1970 at 12:00:01 AM UTC
omnios$ sudo date -u 121308451901.52
December 13, 1970 at 08:45:52 AM GMT
omnios$ date
December 13, 1970 at 08:45:54 AM UTC

Но какую максимальную дату мы можем установить в системах, использующих 64-битный time_t? Как говорилось выше, можно ожидать, что доступно время до 2^63 - 1 = 9223372036854775807 эпохи. Давайте сначала отобразим дату, а затем попытаемся её установить:

netbsd$ date -r 9223372036854775807
date: 9223372036854775807: localtime: Value too large to be stored in data type
netbsd$ date -r 67768036191676799
Wed Dec 31 23:59:59 UTC 2147485547
netbsd$ date -r 67768036191676800
date: 67768036191676800: localtime: Value too large to be stored in data type

linux$ date --date @9223372036854775807
date: time ‘9223372036854775807’ is out of range
linux$ date --date @67768036191676799
Wed Dec 31 11:59:59 PM UTC 2147485547
linux$ date --date @67768036191676800
date: time ‘67768036191676800’ is out of range
linux$

freebsd$ date -r 9223372036854775807
date: invalid time
freebsd$ date -r 67768036191676799
date: invalid time
freebsd$ date -r 67767976233532799
Tue Dec 31 23:59:59 UTC 2147483647
freebsd$ date -r 67767976233532800
date: invalid time

Это интересно. Несмотря на то, что нам обещали 64-битный time_t, мы не можем установить время на 9223372036854775807. Похоже, максимальное значение равно 67768036191676799 в 2147485547 году. Хотя поначалу это значение кажется произвольным, можно заметить, что 2147485547 — это 2^31 - 1, и внезапно всё обретает смысл: даже несмотря на то, что time_t является 64-битным, tm_year структуры struct tm всё равно остаётся 32-битным, а потому максимальным значением, которое он может задать, является последняя секунда 2147485547 года.

А как дела в FreeBSD? Почему в ней время 67768036191676799 эпохи является недопустимым, а 67767976233532799 эпохи соответствует тому, что на других платформах является 67768036191676799? Если провести вычисления, то можно заметить, что разница между этими двумя моментами времени эпохи равна 1900 годам, то есть, очевидно, FreeBSD основывает свой tm_year не на 1900 годе (как заявляется struct tm в <time.h>), а на 0 годе? Так как я не смог с этим разобраться, то отправил баг-репорт.

Если мы попытаемся установить дату, то поначалу заметим, что при использовании date(1) NetBSD невозможно установить дату выше 9999 года (ещё один баг-репорт), но забавно, что на самом деле это не важно, поскольку мы всё равно не можем добраться выше 4147 года:

netbsd# date 414708200732.17
date: settimeofday: Invalid argument
netbsd# date 414708200732.16; date +%s
Sun Aug 20 07:32:16 UTC 4147
68719476736

Так получилось потому, что в NetBSD есть жёстко прописанное ограничение в 2^36 = 68719476736 для значения tv_sec, которое принимается при установке времени, потому что бОльшие значения вызывают недовольство KUBSAN (код):

        /*
         * Установка завышенного значения времени
         * приводит к недопустимому поведению системы.
         */
        if (ts->tv_sec < 0 || ts->tv_sec > (1LL << 36))
                return EINVAL;

В Linux используется другой практический максимум даты, установленный на 2232 год:

linux$ sudo date -s @8277292036
date: cannot set date: Invalid argument
Wed Apr 18 11:47:16 PM UTC 2232
linux$ sudo date -s '@8277292035'
Wed Apr 18 11:47:15 PM UTC 2232
linux$ sleep 10; date +%s
8277292045
linux$ sudo date -s @$(date +%s)
date: cannot set date: Invalid argument
Wed Apr 18 11:47:25 PM UTC 2232

Причина этого ограничения, найденная в исходном коде, заключается в том, что оно может использовать 30-летний аптайм до оборачивания счётчика:

#define NSEC_PER_SEC    1000000000L
#define KTIME_MAX       ((s64)~((u64)1 << 63))
#define KTIME_SEC_MAX   (KTIME_MAX / NSEC_PER_SEC)

/*
 * Ограничения settimeofday():
 *
 * Чтобы предотвратить установку времени близко к точке оборачивания,
 * устанавливаемое время ограничено так, чтобы можно было использовать разумный аптайм.
 * Достаточно будет аптайма в 30 лет,
 * то есть точкой отсечки является 2232. На этом этапе эта отсечка
 * является просто небольшой частью более серьёзной проблемы *
 */
#define TIME_UPTIME_SEC_MAX    (30LL * 365 * 24 *3600)
#define TIME_SETTOD_SEC_MAX    (KTIME_SEC_MAX - TIME_UPTIME_SEC_MAX)

Это означает, что в Linux знаковое 64-битное максимальное значение времени (2^63 - 1 = 9223372036854775807) не обозначает секунды после эпохи, а подсчитывает наносекунды после эпохи, поэтому теоретическая максимальная дата Linux (KTIME_SEC_MAX) снижается до всего лишь 9223372036 секунд эпохи, или даты 2262-04-23T11:47:16, что очень далеко от «22 приблизительных возрастов Вселенной», и ближе к тому, чтобы стать реальной проблемой.

Проверяя, какое значение FreeBSD позволяет установить для часов, я выяснил, что в двух последних релизах это значение различается, но оба релиза имели неприятную проблему, приводящую к спонтанной перезагрузке системы:

freebsd# date -u -f "%s" 49282253052249598
Fri Dec 31 23:59:58 UTC 1561694399
49282253052249598
freebsd# sleep 1; date; date +%s
Fri Dec 31 23:59:59 UTC 1561694399
49282253052249599
freebsd# sleep 1; date; date +%s
Sat Jan  1 00:00:00 UTC 1561694400
49282253052249600
[ system reboots ]

Примечание: изначально я могу установить дате значение больше 49282253052249598, но спустя примерно три секунды система перезагружается. Если задать дату на одну секунду меньше, то есть 49282253052249597, то система не перезагружается, даже когда системное время уходит дальше следующего значения. Разве компьютеры — это не чудесно?

О, и ещё кое-что...


Всем нам нравится, что macOS — это UNIX; она является одной из всего шести зарегистрированных на данный момент систем (остальные — это AIX, EulerOS (коммерческий дистрибутив Linux, созданный Huawei), HP-UX, Xinuos (ранее UnixWare, создан старыми AT&T Unix System Laboratories + Novell, SCO,
Caldera и UnXis) и z/OS). Однако фреймворк Core Foundation компании Apple не использует эпоху Unix как основу своего времени. В качестве его даты отсчёта используется 2001-01-01T00:00:00 GMT:

Все варианты CLOCK_MONOTONIC гарантируют, что время, возвращаемое Core
Foundation, измеряет время в секундах. В качестве базового типа данных используется CFTimeInterval, измеряющий разность в секундах между двумя моментами времени. Фиксированные моменты времени, или даты определяются типом данных CFAbsoluteTime, измеряющим интервал времени между конкретной датой и абсолютной датой отсчёта Jan 1 2001 00:00:00 GMT.

То есть если вы захотите преобразовать эти метки времени в эпоху Unix, нужно будет прибавить 978307200.



Как я и сказал, время — иллюзия, а время Unix — иллюзия вдвойне. И хотя вас может и не беспокоить проблема 2038 года (наверно, только если вы не пользуетесь OmniOS), возможно, мне удалось показать, что внутри может таиться множество других сюрпризов. А ведь мы ещё не касались безумия, творящегося с часовыми поясами и летним временем

Комментарии (45)


  1. M_AJ
    28.10.2022 08:29
    +12

    В результате этого в стране теперь работает множество высоковольтных систем передачи прямого электротока

    Качество перевода конечно огонь. Даже переводчик Гугла уже таких ошибок не делает.


    1. PatientZero Автор
      28.10.2022 13:42

      Спасибо, исправил.


    1. vdudouyt
      29.10.2022 12:30

      на западе, где первые генераторы были куплены у немецкой AEG и установлены в Токио, частота в сети равна 50 Гц; на востоке, в Осаке, были установлены генераторы G.E., и используются 60 Гц.

      Не знал, что Осака теперь находится к востоку от Токио :)


  1. Kremleb0t
    28.10.2022 09:09

    2016-12-31T23:59:60 is 1483228800
    2017-01-01T00:00:00 is 1483228800 //здесь дублирующаяся метка времени

    Что-то я не понял. Я не очень хорошо знаю си, но судя по исходнику https://www.netmeister.org/blog/epoch-time.c автор статьи преобразует строку "2016-12-31T23:59:60" в структуру tm а затем из структуры tm формирует unix timestamp. А чего он ожидал вообще? 2016-12-31T23:59:60 - такого времени не бывает. Автор сам подставил в аргумент функции mktime() дичь а потом жалуется на дублирующую метку времени. По хорошему, функция mktime() вообще должна отказываться работать когда в её аргумент подставляют невалидное значение.

    Сама по себе корректировка времени - это костыль в несовершенных правилах счёта времени. Т.е. правила которые придумали люди для счёта времени расходятся с физическими явлениями (скоростью вращения Земли). Проблема не в unix-времени а в том, что сами правила счёта времени несовершенны.


    1. M_AJ
      28.10.2022 09:44
      +6

      такого времени не бывает.

      Как раз бывает. Автор же там пишет про секунды коррекции, когда чтобы не сбить календарь нужно добавить в сутки дополнительную секунду. "Проблема" в том, что Unix писали не астрономы, и не учитывали такие вещи. Проблема в кавычках, потому что для обычных людей это не важно, а если тебе нужно хранить какие-нибудь астрономические данные, чувствительные к таким вещам, то просто не используй дефолтное Unix-время. Так что это просто любопытная техническая особенность.


      1. Kremleb0t
        28.10.2022 10:38
        +4

        Проблема" в том, что Unix писали не астрономы, и не учитывали такие вещи

        Не считаю нужным учитывать все прихоти астрономов. Сегодня у них +1 секунда в году, а завтра они число секунд дробное придумают. Это всёравно что играть в шахматы с игроком, который находу придумывает свои правила.

        Операционная система нужна чтобы решать конкретные практические задачи и она их решает. А то что кто-то захотел прибавить или убавить секунду в году - меня это не волнует, и я прекрасно понимаю разработчиков ОС которые за 50 лет так и не стали учитывать эту секунду.

        Статья звучит как камень в разработчиков ОС, автор критикует метод расчёта unix timestamp, но не предлагает решения этой проблемы.


        1. Iv38
          28.10.2022 12:05
          +6

          Из-за секунды коррекции получается неправильное число секунд между двумя датами, а это может быть важно не только астрономам.


        1. transcengopher
          28.10.2022 13:11
          +2

          Операционная система нужна чтобы решать конкретные практические задачи и она их решает.

          я прекрасно понимаю разработчиков ОС которые за 50 лет так и не стали учитывать эту секунду.

          Машиночитаемым вариантом передачи метки момента времени является передача Epoch Timestamp в виде числа — либо секунд, либо миллисекунд, я очень часто видел такие форматы на практике. Если в такую "консервативную" систему (не учитывающую коррекционные секунды) передавать метки даты в вышеупомянутом формате откуда-нибудь извне (где коррекционные секунды учитываются), то одна и та же метка после трансляции в человеко-читаемое время будет отображать как минимум разное количество секунд или минут на разных машинах.
          А если вспомнить про ещё один иногда применяющийся паттерн, когда Epoch Timestamp передают и распознают как "время начала дня", и, получая Timestamp, извлекают из него только год, месяц, и день, то при передаче "метки начала дня" в такую систему, она после отбрасывания всех компонентов времени вообще может начать показывать другую дату.
          Вы всё ещё считаете, что такие системы "решают конкретные практические задачи", и делают это правильно?


          1. Kremleb0t
            28.10.2022 14:16

            Формат unix timestamp во многих местах очень удобен. Например, чтобы обозначить время +1 сутки достаточно к текущему времени прибавить 86400. И для сравнения/вычисления дат не нужны никакие объекты типа DateTime.


            1. storoj
              28.10.2022 14:41
              +7

              Так а что если как раз не хватит той одной секунды, чтобы попасть в следующий день?


          1. rrrad
            28.10.2022 18:09
            +3

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


          1. inkelyad
            29.10.2022 13:41

            А если вспомнить про ещё один иногда применяющийся паттерн, когда Epoch Timestamp передают и распознают как "время начала дня",

            Бить по рукам. Потому что оно создает проблемы даже без лишних секунд - а просто из за разных и/или ошибочных таймзон и их баз. Если тебе нужна именно дата - передавай дату.


        1. Lissov
          30.10.2022 12:34

          Не считаю нужным учитывать все прихоти астрономов. Сегодня у них +1 секунда в году, а завтра они число секунд дробное придумают. 

          Ну да, когда-то у них было 365 в каждом году, а у некоторых до сих пор 354. Это очень удобно для их практических задач определения фаз луны, да и разница времени между месяцами очень просто рассчитывается. А что времена года уезжают - это уже другая задача :)

          То есть у нас есть два подхода:

          1. Хранить год/месяц/день…/мс, точно и удобно отображать, но нетривиально рассчитать разницу между двумя моментами и нужна сложная структура данных (усложняются часы).

          2. Хранить секунды или миллисекунды от заданного момента. Очень просто хранить и считать компьютеру, но нетривиально отображать.


    1. TheProgger
      28.10.2022 09:47
      +7

      Вообще говоря время 2016-12-31T23:59:60 Бывает

      Сама по себе корректировка времени - это костыль в несовершенных правилах счёта времени. Т.е. правила которые придумали люди для счёта времени расходятся с физическими явлениями (скоростью вращения Земли)

      А какие можно придумать правила, если скорость вращения Земли непостоянна и в общем случае непредсказуема?


  1. unC0Rr
    28.10.2022 09:39
    +11

    Не правила несовершенны, а скорость вращения Земли несовершенна. То быстрее крутится, то медленнее. Да ещё и количество оборотов вокруг собственной оси за один оборот вокруг солнца нецелое.


  1. Naf2000
    28.10.2022 09:39
    +6

    на западе, ... в Токио ... ; на востоке, в Осаке

    так вот Токио восточнее Осаки


  1. Fr0sT-Brutal
    28.10.2022 10:15

    Просто оставлю здесь классическую байку про ракетные движки, железную дорогу и лошадиную задницу :) https://romx.livejournal.com/166141.html

    Очень уж история происхождения unix эпохи напомнила


  1. alexhott
    28.10.2022 10:54
    +3

    А почему проблема именно для UNIX? В других системах есть корректировка на секунды в соответствии с расчетами службы времени?
    В изначальной статье мягкое с теплым смешали, а тут еще корявый перевод добавился.


    1. screwer
      28.10.2022 11:23
      +4

      В других системах время изначально 64 битное. И не в секундах, и в не наносекундах, а в 100-наносекундных интервалах. И не с 1900/1970/1971/2001 а с 1600 года. И в прошлое разумный отступ, и интервал способен учитывать размерности менее секунд, и в будущее огромный запас. И это все работает сразу, с самого первого релиза.


      1. vassabi
        28.10.2022 11:36
        +3

        ну, теперь-то легко не повторять ошибок прошлого :)


        1. screwer
          28.10.2022 12:46
          +1

          "Ошибку прошлого" совершили в 1970, повторили в 1986-1996 (GNU, Linux, и т.д.)

          А "не совершили" в 1988-1993, используя как основу систему из второй половины 1970х (к сожалению не знаю, как там время устроено).

          Как мы видим, временые периоды почти одинаковы.


          1. vassabi
            28.10.2022 12:56
            -1

            ну, самая большая лигаси ИМХО - это попытка постоянно представлять время в виде одного числа одной и той же размерности.

            Гораздо лучше ему бы было в виде массива байт: чтобы биты первого байта задавали длину остальных (см. например как UNICODE), а остальные уже хранили число (наносекунд, секунд или 1/100 наносекунд и т.д.)


            1. rrrad
              28.10.2022 18:15
              +4

              Такое допустимо для передачи значений, но любая обработка такого значения превратится в ад с массой проверкой и кучей особых случаев. Обрабатывать гораздо проще значения с фиксированным форматом.


      1. slonopotamus
        28.10.2022 20:47
        +2

        И это все работает сразу, с самого первого релиза

        Ну в софте от микрософта тоже есть всякие приколы с датами: https://learn.microsoft.com/en-us/office/troubleshoot/excel/wrongly-assumes-1900-is-leap-year


  1. gsaw
    28.10.2022 11:33
    +2

    Мне вот непонятно, разве нельзя предсказать на годы вперед где окажется земля в тот или иной момент времени? Можно же было бы тогда расчитать все восходы и закаты на годы вперед. Тогда не пришлось бы делать коррекцию постфактум, а просто перещелкивать дни по табличным значениям. Или тут есть какая то доля непредсказуемости из-за влияния других планет?


    1. TheMrWhite
      28.10.2022 11:36
      +8

      задача трёх тел


      1. gsaw
        28.10.2022 11:41
        +1

        Ну это понятно, просто на каком отрезке времени доля хаоса становится больше секунды, миллисекунды? Ну то есть к примеру если на отрезок лет 50 можно рассчитать, даже с учётом хаоса, то потом можно посчитать по новой, лет чрез 50, 20, 10.


        1. vassabi
          28.10.2022 11:45
          +4

          если вам для "ну будет минутой больше или меньше - и так сойдет", то можно наверно и на 100 лет вперед рассчитать


        1. domix32
          28.10.2022 17:41
          +3

          Для такого необходимо знать все гравитационные силы воздействующие на тело, а учитывая, что огромная часть массы скрыта пересчет думается происходит постфактум, когда предсказанная и фактическая разница измерений случается.


      1. 0xd34df00d
        28.10.2022 22:25
        +7

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


    1. vassabi
      28.10.2022 11:43
      +1

      1) а как вы думаете - с какой точностью делают измерения положения и скорости Земли на орбите? А также положения и скорости других небесных тел, влияющих на Землю ? (полмиллиметра на миллион тонн массы или меньше? или больше ?)

      2) какие у вас критерии - что влияет на траекторию Земли, а что - не влияет ? (пояс астероидов и Меркурий - влияют ? или только Луна и Солнце ?)

      3) и, как вы думаете - как быстро накапливается расхождение фактического положения от предрассчитанного со временем ? (на микроградусы в год или больше ? или меньше ?)


      1. gsaw
        28.10.2022 11:44
        +2

        не знаю, потому и спрашиваю.


        1. vassabi
          28.10.2022 11:50
          +3

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

          А если вы - астроном и вам нужно прицелиться на нужную звезду с поверхности Земли, то там увы одна секунда ухода в несколько лет - это именно то, что "не могут рассчитать заранее" (а иначе - просто рассчитывали бы и в ус не дули)


          1. victor_1212
            28.10.2022 16:06

            > а как вы думаете - с какой точностью делают измерения положения и скорости Земли на орбите

            если Вас интересует годичный параллакс, то он незначителен max порядка долей угловой секунды, т.е в большинстве приложений можно считать что звезды на бесконечности, в первом приближении имеет значение только вращение земли

            > А если вы - астроном и вам нужно прицелиться на нужную звезду с поверхности Земли

            чтобы точно прицелиться на нужную звезду зная ее угловые координаты желательно иметь стабильную монтировку, и соответствующие средства управления (включая привода, угловые датчики, контроль механических деформаций и пр. ) в общем случае точность такого прицеливания на 2-3 порядка хуже точности знания координат звезд по каталогу, что тем не менее достаточно для попадания в поле зрения, но для точного наведения обычно используется относительный метод с распознаванием звездного узора


    1. saege5b
      28.10.2022 13:18
      +2

      Сначала нужно составить объёмную точную гравиметрическую карту Земли - распределения масс в объёме; а потом повторить этот фокус для Луны и Солнца.

      Для горизонта лет в пару хватит, а там можно и окружение притягивать.

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


    1. engine9
      28.10.2022 14:13
      +2

      Примерно рассчитывали на десятилетия вперёд еще до нашей эры. Даже делали механические компьютеры для рассчета фаз Луны, затмений, положения планет на небе и т.п.


    1. Apoheliy
      29.10.2022 00:49

      По мне всё проще: Лень и Зачем?!

      Зачем на годы вперёд всё подсчитывать? Если вы знаете (например), что коррекция будет через 173 года ... то что?

      Ок, слишком далеко. Пусть коррекция будет через 2 года ... и ... что? Броситесь переписывать использование функций времени с учётом коррекции? Добавлять/убавлять? А если функции уже будут с учётом коррекции (ОЧЕНЬ вероятное событие)?

      Если в одном календаре (например, григорианский) коррекция есть, а в другом (юлианский, иранский, еврейский, ...) коррекции нет, ... то ... что?


  1. rezedent12
    28.10.2022 13:02
    +1

    Может не надо дёргать время UNIX вслед за астрономическим? Пусть идёт себе равномерно. А для конвертаций в воспринимаемый человеком вид, использовать журнал с количеством секунд в каждом году.


    1. saege5b
      28.10.2022 13:19
      +2

      Надо как-то этот журнал по всем системам рассовывать постфактум.


      1. 13werwolf13
        28.10.2022 13:27
        +2

        в эпоху повсеместного распространения интернета и операционок которые обновляются не спрося у пользователя разрешения это не большая проблема. но в целом я согласен что идея фиговая и очень костыльная. да и реализация породит 100500 несовместимых форматов этого журнала и кучу конвертеров с багами и артефактами.


      1. vassabi
        28.10.2022 13:43
        +1

        ну вот таймзоны - уже и так распихиваются.


      1. mbalabin
        28.10.2022 14:33
        +5

        Вращение Земли ускоряется и замедляется непредсказуемо. Если вы хотите согласовывать значение аппаратных часов с астрономическим временем, то у вас нет другого варианта кроме как рассовывать журнал постфактум. Заранее узнать об изменении скорости вращения невозможно - часы придётся тем или иным способом "подводить". Кроме того, постоянно меняются часовые пояса и правила перехода с летнего времени на зимнее. Эту информацию тоже приходится как-то доводить до конечных систем. Так что решение не двигать UNIX-время очень разумное. Кстати, точно так же не двигается GPS-время и TAI-время.


  1. roqin
    28.10.2022 13:46
    +1

    время измерялось в 1/60 секунды, а не в секундах

    Забавно здесь то, что эта частота прерываний зависит от частоты в сети источника питания.

    А то я уж про терции подумал ????


  1. Apoheliy
    28.10.2022 14:16
    +3

    Вот что происходит, когда люди начинают частности натягивать на "вообще".

    Смотрим на POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/functions/time.html

    там сказано про секунды от "эпохи". Указание на 01.01.1970 есть? Нет! Т.е. условная линукса может считать от любой даты!!! Весело?

    Смотрим на C++:

    https://en.cppreference.com/w/c/chrono/time

    формат представления не специфицирован (хотя обычно совпадает с POSIX) - т.е. это могут быть хоть 1/60 секунды, хоть минуты. Весело?

    По факту возвращаемое время может быть в любых интервальных единицах от любой базовой даты.

    Учите МатЧасть, читайте стандарт.


    1. vassabi
      28.10.2022 15:21

      извините, а это не вы автор проигрывания музыки и показывания веселых картинок, когда программа на С++ натыкается на UB ?