- PVSM.RU - https://www.pvsm.ru -
Как вы хорошо знаете, в Unix-системах мы измеряем время как количество секунд, прошедших с «эпохи»: 00:00:00 UTC 1 января 1970 года. Немало людей сильно разозлилось из-за этого, да и вообще, общественное мнение сочло это ошибкой.
Во-первых, это определение основано не на чём-то разумном, например, на объективной частоте колебаний атома цезия-133, а на удобной доле времени полного оборота одного большого камня вокруг собственной оси.
Во времени Unix каждый день гарантированно состоит из 86400 секунд и мы притворяемся, что это число равномерно увеличивается. Когда оказывается, что вышеупомянутый камень на самом вращался дольше, чем удобно для нас, и нам нужно добавить секунду координации, то мы просто притворяемся, что этого не было, а механизм меток времени не идентифицирует уникальный момент времени.
Ещё один аспект, который продолжает вызывать проблемы, когда мы пытаемся считать секунды, заключается в том, что мы сталкиваемся с проблемами хранения и описания данных, потому что, как оказалось, компьютеры не так уж хорошо справляются с числами. Не говоря уж об "эпохальном сбое [1]".
Как же мы к этому пришли? Всё началось в 1971 году, когда в в первом издании руководства для программиста Unix [2] было дано определение времени Unix как "времени с 00:00:00 1 января 1971 года, измеряемого в шестидесятых долях секунды":
Именно так, изначально эпохой Unix было 1971-01-01T00:00:00
. Вы спросите, какого часового пояса? Ну, это точно не был «UTC», потому что он заменит GMT в качестве стандартного времени только в 1972 году. Во-вторых, обратите внимание, что время измерялось в 1/60 секунды, а не в секундах. Зачем же так сделали?
Вспомним, что в то время Unix разрабатывался в США на PDP-11. Эти системы имели часы линейного времени (Line-Time Clock, LTC) [3], использующие частоту питания переменного тока для генерации прерывания для процессора. Тогда это прерывание использовалось для обновления системных часов [4].
Забавно здесь то, что эта частота прерываний зависит от частоты в сети [5] источника питания. В основной части Азии и Европы эта частота равна 50 Гц, однако в США Westinghouse Electric Corporation (конкурент Томаса Эдисона и General Electric [6]; любопытный факт: позже Westinghouse приобрела CBS, а затем была куплена Viacom) заметила, что использовавшиеся тогда дуговые лампы с угольным электродом меньше мерцают [7] при 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 [8]
(Истинная дата рождения Unix приходится примерно на 1969 год, когда Кен Томпсон портировал "Space Travel [9]" на PDP-7, поэтому легко понять, почему эпоха Unix кажется обозначением рождения Unix.)
Итак, теперь у нас есть 32-битный счётчик секунд от эпохи, который равномерно увеличивает значение с частотой 1 Гц и гарантирует нам, что будет насчитывать ровно 86400 секунд в любой 24-часовой период. Однако наш космический булыжник отсчёта замедляется в своём вращении, поэтому время от времени это число нужно изменять.
Для этого Международная служба вращения земли (IERS) отправляет всем Повелителям Времени электронные письма, сообщающие, должна или нет добавляться секунда координации:
Сегодня мы не можем винить время эпохи Unix за то, что оно изначально не учитывало секунды координации, потому что их не существовало до 1972 года. С тех пор (на 2022 год) произошло уже 27 положительных секунд координации, последняя из которых была введена в конце 2016 года. Каждый раз, когда происходит секунда координации, время эпохи Unix просто притворяется, что этого не было, из-за чего две даты соответствуют одной метке времени эпохи (epoch-time.c [10]):
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]
, учитывая мифическую «двойную секунду координации», которой на самом деле никогда не существовало [11].)
Хорошо в этом то, что всё это соответствует требованиям POSIX [12]:
4.16 Секунды после эпохи
Значение, которое аппроксимирует количество секунд, прошедшее после эпохи.
Соотношение между истинным временем суток и текущим значением секунд после эпохи точно не установлено.
Способ внесения любых изменений в значение секунд после эпохи для согласования с нужным соотношением с текущим временем зависит от реализации. В формате секунд после эпохи каждый день насчитывает ровно 86400 секунд.
Даже при равномерном отсчёте и игнорировании секунд координации используемый для измерения секунд после эпохи тип данных time_t
неизбежно придёт к переполнению. Хорошо, что есть стандарты, которые спасут нас в этой ситуации! Пусть POSIX не решает проблемы, но хорошо в стандартах то, что их много и можно выбрать подходящий, не так ли? Ох, постойте, в чём же дело? Давайте разберёмся.
Плохие новости: стандарты нас не спасут. Например, стандарт C гласит [13]:
Диапазон и степень точности времени, представленного в clock_t и time_t, зависят от реализации.
Что ж, ладно. Имея 32-битный time_t
, мы можем рассчитывать начиная с 1970 года примерно на 136 лет. Однако time_t
является знаковым 32-битным integer, то есть у нас есть всего 68 лет в обоих направлениях, из-за чего возникает так называемая "Проблема 2038 года [14]".
Она состоит в том, что самая большая дата, которую можно записать как 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 раз больше приблизительного возраста Вселенной, поэтому официально может считаться Чьей-то Чужой Проблемой.
Но — всегда есть «но», не правда ли? — действительно ли это произойдёт? Почему бы не попробовать и не проверить, как разные системы поведут себя, если мы передадим им для обработки не совсем логичные значения времени?
Хотя время представлено в виде time_t
, используется и другой популярный формат для записи разбитого на части времени — struct tm
, из которого можно получить time_t
, вызвав mktime(3)
. В POSIX [15] зафиксированы следующие элементы 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 конкретно написано [16]:
… исходные значения [...] компонентов не должны быть ограничены диапазонами, описанными в <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
) или (снова) при участии секунд координации, что может стать причиной головной боли. Лучше избегать такой ситуации.
Давайте рассмотрим даты эпохи в момент или рядом с эпохой. Проигнорируем тот факт, что время эпохи до 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
из-за гарантий [17], даваемых его "CLOCK_MONOTONIC
", который, согласно POSIX [18], "представляет количество времени после неуказанной точки в прошлом":
Все варианты 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 годе? Так как я не смог с этим разобраться, то отправил баг-репорт [19].
Если мы попытаемся установить дату, то поначалу заметим, что при использовании date(1)
NetBSD невозможно установить дату выше 9999 года (ещё один баг-репорт [20]), но забавно, что на самом деле это не важно, поскольку мы всё равно не можем добраться выше 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 [21] (код [22]):
/*
* Установка завышенного значения времени
* приводит к недопустимому поведению системы.
*/
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
Причина этого ограничения, найденная в исходном коде [23], заключается в том, что оно может использовать 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 позволяет установить для часов, я выяснил, что в двух последних релизах это значение различается, но оба релиза имели неприятную проблему [24], приводящую к спонтанной перезагрузке системы:
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; она является одной из всего шести зарегистрированных на данный момент систем (остальные [25] — это AIX, EulerOS (коммерческий дистрибутив Linux, созданный Huawei), HP-UX, Xinuos (ранее UnixWare, создан старыми AT&T Unix System Laboratories + Novell, SCO,
Caldera и UnXis) и z/OS). Однако фреймворк Core Foundation [26] компании Apple не использует эпоху Unix как основу своего времени. В качестве его даты отсчёта [27] используется 2001-01-01T00:00:00 GMT:
Все варианты CLOCK_MONOTONIC гарантируют, что время, возвращаемое Core
Foundation, измеряет время в секундах. В качестве базового типа данных используется CFTimeInterval, измеряющий разность в секундах между двумя моментами времени. Фиксированные моменты времени, или даты определяются типом данных CFAbsoluteTime, измеряющим интервал времени между конкретной датой и абсолютной датой отсчёта Jan 1 2001 00:00:00 GMT.
То есть если вы захотите преобразовать эти метки времени в эпоху Unix, нужно будет прибавить 978307200
.
Автор:
PatientZero
Источник [29]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/unix-2/380014
Ссылки в тексте:
[1] эпохальном сбое: https://xkcd.ru/376/
[2] в первом издании руководства для программиста Unix: https://www.bell-labs.com/usr/dmr/www/pdfs/man22.pdf
[3] часы линейного времени (Line-Time Clock, LTC): https://www.learningpdp11.com/post/using-the-pdp-11-line-clock
[4] обновления системных часов: https://accu.org/journals/overload/23/130/schmidt_2185/
[5] частоты в сети: https://en.wikipedia.org/wiki/Utility_frequency
[6] конкурент Томаса Эдисона и General Electric: https://en.wikipedia.org/wiki/War_of_the_currents
[7] использовавшиеся тогда дуговые лампы с угольным электродом меньше мерцают: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=628099
[8] Wired: https://www.wired.com/2001/09/unix-tick-tocks-to-a-billion/
[9] Space Travel: https://en.wikipedia.org/wiki/Space_Travel_(video_game)
[10] epoch-time.c: https://www.netmeister.org/blog/epoch-time.c
[11] на самом деле никогда не существовало: https://groups.google.com/g/comp.dcom.telecom/c/Qq0lwZYG_fI?hl=en#02dd6ed5bb9ed84e
[12] требованиям POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_16
[13] стандарт C гласит: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf
[14] Проблема 2038 года: https://en.wikipedia.org/wiki/Year_2038_problem
[15] POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html
[16] написано: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mktime.html
[17] гарантий: https://man7.org/linux/man-pages/man2/clock_settime.2.html
[18] согласно POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
[19] отправил баг-репорт: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=267266
[20] ещё один баг-репорт: https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=57069
[21] KUBSAN: https://man.netbsd.org/kernel_sanitizers.7
[22] код: http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/kern/kern_time.c.diff?r1=1.205&r2=1.206
[23] исходном коде: https://elixir.bootlin.com/linux/v6.0.3/source/include/linux/time64.h#L36
[24] неприятную проблему: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=267157
[25] остальные: https://en.wikipedia.org/wiki/Single_UNIX_Specification#Currently_registered_UNIX_systems
[26] Core Foundation: https://developer.apple.com/documentation/corefoundation
[27] даты отсчёта: https://developer.apple.com/documentation/corefoundation/time_utilities:
[28] часовыми поясами и летним временем: https://youtu.be/3N2aH1vUacQ?t=575
[29] Источник: https://habr.com/ru/post/695688/?utm_source=habrahabr&utm_medium=rss&utm_campaign=695688
Нажмите здесь для печати.