-
Notifications
You must be signed in to change notification settings - Fork 0
/
time-counting.html
1132 lines (996 loc) · 90.7 KB
/
time-counting.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>О времени и о счёте времени</title>
</head>
<body>
<p>На написание этой статьи меня подтолкнуло появление аналогичной статьи,
ограничившейся только перечислением распространённых представлений без
уточнения их свойств и применимости. Поскольку такой подход реально
чреват тяжёлыми проблемами в случае выхода за узкие рамки применимости
одного представления, я решил описать проблему в полном объёме.
Практическая сторона вопроса ограничена рассмотрением двух наиболее
распространённых классов систем — Unix (вместе с Linux) и Microsoft
Windows. Стиль написания — "трактат".</p>
<p>Начнём немного издалека; я постараюсь максимально сократить "матан" и
философию. Кто хочет сразу практических фактов — пропустите
первую четверть текста.</p>
<h2><b><center>Что есть время?</center></b></h2>
<p>Время нам нужно, чтобы рассчитывать события и затраты. Время в нашем
понимании — счёт равных периодов. Русское слово "время" по происхождению
значит "колесо" или "колесница" и родственно "вращению". Возможен ли
идеально периодический процесс? Да, возможен, пока мы его не начинаем
измерять. Любое измерение искажает процесс, пусть даже на 10 в минус
пятидесятой степени. Атомные часы, о которых речь пойдёт далее
отличаются в выгодную сторону точностью — то есть тем, что внешние
влияния (включая измерительное) искажают ход часов значительно меньше
других часов.</p>
<p>Самое общее представление о времени содержится, видимо, в теориях
Эйнштейна. Если события связаны причинно-следственной связью, то
причина случилась раньше. Насколько раньше — тут уже не устанавливается,
и тем более не даётся никаких определённых единиц времени или абсолютных
значений вроде "точки 0". В то же время, можно создать процессы, которые
идут максимально равномерно в локальном времени их системы.</p>
<h2><b><center>Как мы считаем время?</center></b></h2>
<p>Людям в обычной жизни не нужен точный подсчёт времени в фиксированных
единицах. Людям нужен подсчёт тех явлений и событий, которые реально
влияют на все жизненные процессы:</p>
<ul>
<li> годовой природный цикл увеличения и уменьшения доставляемого Солнцем
тепла и зависимых от них сезонных колебаний осадков (что на экваторе
важнее, чем почти постоянный поток Солнца)</li>
<li> суточный цикл светимости</li>
<li> месячный цикл Луны (потерял значимость первым, поэтому в наиболее
распространённом григорианском календаре "месяц" никак не связан с
лунным циклом)</li>
</ul>
<p>Возможно, в Долине Гейзеров местные жители считают также циклами выброса
горячей воды из недр. Но для человечества в целом важны только названные
три цикла. Остальные единицы времени — производные от них. Здесь я сразу
намекаю на тему "что такое секунда?", раскрытую ниже.</p>
<h2><b><center>Как мы определяем время событий в прошлом?</center></b></h2>
<p>У предков не было атомных часов, радиосвязи и телевидения. Ещё 100 лет
назад, до введения часовых поясов, каждый город настраивал свои часы по
местному астрономическому времени (солнце в зените => полдень). Точные
часы, пригодные для измерения долготы с приемлемой для мореплавания
точностью, появились только в середине XVIII века (хронометр Гаррисона),
хотя уже столетие до этого точность часов позволяла вырулить на нужную
сторону нужного океана:) Чем дальше, тем менее точные данные
нам доступны и тем хуже понимание. В древнем Риме тоже считали
"часами", но от восхода до заката было 12 равных дневных часов,
а от заката до восхода — 12 ночных (соответственно летом
дневные часы были длиннее ночных, зимой — наоборот).
Большинство дат до введения отсчёта "от сотворения мира" (в
каждой стране своего) писались в стиле "на восьмом году правления Гороха
77-го", и получить смещение от другой даты можно только зная, в каком
году от коронации Ягупоппа 19-го произошла коронация Гороха 77-го, и так
по всей цепочке. Греки классического периода считали время по
олимпиадам. Шатания стиля календарей усугубляют ситуацию: на Руси год
начинался 1-го марта, потом Алексей Тишайший перенёс начало на 1-е
сентября, потом Пётр I — на 1 января. Не зная точного начала для
конкретного периода времени, тривиально ошибиться на год при пересчёте.
Шумерские хроники утверждают, что их цари правили 7200 лет и более.
Любой здравомыслящий человек отбросит от такой даты два 60-ричных
"нуля". Римский счёт от основания Рима был, по сравнению с этим,
эталоном точности несмотря на весьма условную опорную дату.</p>
<p>Наш основной счёт лет — григорианский. Тем не менее, для большой
части населения Земли вместо 2015 года — 1393-й (хиджры), 5776 (от
сотворения мира)... мы не перечислили все варианты. И, опять-таки,
моменты нового года не совпадают.</p>
<p>Одна из сложнейших загадок — хронология древнего Египта. Даже отбросив
все проблемы чтения древних источников, выходящие за пределы темы данной
статьи, и сосредоточившись на астрономических наблюдениях, получаем три
наиболее вероятные хронологии. Большинство исследователей сейчас считают
наиболее вероятной "короткую" хронологию. Но любая находка сначала
сравнивается с возможными предшественниками и наследниками и только
после этого получает возможную абсолютную дату. Тот, кто априорно
назначит "юлианский день" событию из истории Египта до греческого
завоевания, будет как минимум поспешен в выводах.</p>
<p>Про палеонтологию даже не хочу и начинать. Желающие да погуглят.</p>
<h2><b><center>Не думай о секундах свысока</center></b></h2>
<p>В сутках 24 часа, в часе 60 минут, в минуте 60 секунд — это то, как
большинство из нас воспринимает счёт времени. Астрономы знают, что Земля
вращается неравномерно и вокруг своей оси, и вокруг Солнца, так что
секунда одних солнечных суток не равна секунде других. Более того, Земля
замедляет своё вращение — по некоторым данным, 400 миллионов лет назад
суточный цикл составлял 9 наших часов.</p>
<p>Тем не менее, на ближайшие несколько тысяч лет мы можем сохранять
стабильность подхода 24*60*60 в бытовых расчётах. За пределами этого
периода — уже вряд ли. Однажды мне попалось на глаза предложение считать
время сверхбольшим числом (256 бит) очень малых долей, предлагавший это
думал, что сможет этим представлением получить шкалу на все времена от
Большого взрыва. Представить-то он сможет, но для одного атома... уже
для соседнего эта шкала не подойдёт. А тем более для каких-то
практических целей.</p>
<p>Действующее с 1967 года определение секунды по атомным часам, измеряющим
частоту специальной установки, привело к введению в 1972 году
международного счёта времени Coordinated Universal Time (UTC;
перестановка букв вызвана взаимовлиянием с французским выражением). В
UTC, все секунды — атомные и равны между собой. Платой за это стала
необходимость корректировки длительности года введением так называемых
"високосных секунд" или "вставных секунд" (leap second), которых с 1972
до 2009 года набралось уже 24 и которые вводятся
как последняя секунда июня или декабря; в такой
знаменательный момент часы, работающие по UTC, должны
показывать 23:59:60. Кроме того, периодически ITU грозится забрать
секунду или ввести две вставные секунды подряд; пока такого не
случалось. Есть ещё другие виды счёта:</p><ul>
<li> GMT — Greenwich Mean Time — почти то же что UTC, но
не имеет вставных секунд и длина секунды
определяется скоростью вращения Земли в этот период. Далее будет чуть
подробнее о его использовании (в компьютерном счёте).</li>
<li> UT1 — согласно вращению Земли; размер секунды нефиксирован (но при
этом есть и вставные секунды!), поддерживается отклонение от UTC не
более чем на 0.9 секунды.</li>
<li> UT1R, UT2, UT2R достигают того же результата, что UT1, с несколько
иными расчётами; не будем их вспоминать, кроме того, что их отклонение
от UT1 гарантированно не больше секунды.</li>
<li> UTC-SLS растягивает или сжимает последние 1000 секунд в день
корректировки по UTC.</li>
<li> TAI (International Atomic Time) считает время просто в атомных
секундах с полуночи 01.01.1900 (некоторые его пересчитывают в года без
вставных секунд, в этом случае оно спешит в 2009 году по сравнению с
UTC на 34 секунды). Полночь 01.01.2009 в нём — 3439756834, что равно
(27*366+82*365)*86400+34.</li>
<li> ещё более астрономически специфические... хватит с нас.</li>
</ul>
<p>Unix-системы определяют свой счёт времени "epoch" или "unixtime" как
количество секунд с полуночи 1 января 1970 года по Гринвичу (GMT). В
Posix сказано, что это время считается в UTC. Но глава, посвящённая
расчёту времени, устанавливает формулы, которые ничего не знают про
вставные секунды; например, полночь 1 января 2009 г. по Гринвичу
происходит ровно в (10*366+29*365)*86400 секунд, поэтому
Posix-совместимые системы в своём счёте не знают вставных секунд.
Windows тоже не учитывает вставные секунды, хотя интерфейсы уровня Win32
реализуют время, разложенное в структуру.</p>
<p>Как работать в условиях этого зоопарка, рассмотрим ниже.</p>
<h2><b><center>Какое, милые, у нас тысячелетье на дворе?</center></b></h2>
<p>Откуда мы знаем, "который час"? В большинстве случаев мы пользуемся
механическими или электронными "держателями времени", попросту говоря —
часами. Но откуда часы знают это время? Есть несколько вариантов:</p>
<p>1. Попросту ниоткуда. Это неинтересный для нас вариант, хотя в некоторых
случаях почти единственно реальный (в целом классе встроенных систем,
которым точное время не нужно, а достаточно более-менее стабильного
источника внутреннего хода). Простой пример — независимый уличный
светофор: снаружи он получает команды "обычная работа", "жёлтый
мигающий", "выключен", остальное задаётся внутренними генераторами.
Даже при наличии высокоточных внутренних таймеров необязательна связь
со внешним временем — автомобильные инжекторы работают без него.</p>
<p>2. Встроенный надёжный источник. Попросту говоря, те же атомные часы,
плюс бригада квалифицированных специалистов для их обслуживания. Я
сомневаюсь, однако, что хоть один читатель данной статьи купит такие часы
хотя бы на работу — дорого и бессмысленно (последний раз, когда я
смотрел, цены начинались от 200000$ стартово и в год).</p>
<p>Все остальные источники времени, особенно доступные в дешёвых системах
типа PC-clone, не обладают достаточной устойчивостью, чтобы давать
точное время. Если часы на Вашем домашнем компьютере уходят менее чем на
секунду за сутки — Вам <u>очень</u> повезло с производителем.</p>
<p>3. Синхронизацией с надёжным источником, непосредственно (в быту не
встречается) или через промежуточных "достаточно надёжных" посредников.
Уже простейшие солнечные часы относятся к этому классу — Солнце является
первичным источником информации о времени. Мы подводим настенные часы
или часы в мобильном телефоне, услышав сигналы точного времени по радио.
У наших дедов и бабушек это было важной привычкой (опоздать на работу
часто означало получить приговор), уже у отцов и матерей нет такого,
если нет признаков сильного ухода времени. В компьютерном мире, однако,
синхронизация настраивается периодически по некоторому внешнему
источнику. Этот метод — регулярная внешняя синхронизация — относится к
99.999...% практически значимых систем, и далее речь пойдёт только о
нём.</p>
<p>Сеть общедоступных бесплатных серверов времени, позволяющих установить
часы с точностью выше секундной (то есть с погрешностью менее секунды),
работает на протоколе NTP. В Unix мире, настройка локального времени по
NTP является первым, что должно по правилам хорошего тона делаться для
свежеустановленной системы без проблем доступа в Internet (пусть даже не
напрямую). Не берусь решать про Windows, но не вижу причины для другой
политики, кроме случаев безразличия к результату; любой анализ событий
на основании логов должен начинаться со сверки часов источников.</p>
<p>Как именно синхронизировать? Можно получать точное время другой
стороны, а затем ставить его локально. Но это означает рывки времени,
что крайне неудобно для таймеров. Более практичным видится другой
вариант, который и применён в NTP:
<p>1. Выбирается некоторый источник времени среди описанных в конфигурации
на основании данных от него (stratum) и устойчивости его данных (по
собственным измерениям).
<p>2. Вычисляется разница между временем другой стороны и локальным
временем на основании ответа другой стороны и оценки задержки ответа.
<p>3. По возможности, вводится корректировка <u>темпа</u> локального времени.
(В ряде случаев допускаются изменения рывками. Чаще допускаются
рывки вперёд, чем назад.)
Корректировка определяет долю изменения темпа времени и длительность
этого изменения. В современных Unix-системах для этого есть специальные
функции (системные вызовы ядра):<ul>
<li>Linux: <a href="http://linux.die.net/man/2/adjtimex">adjtimex</a></li>
<li>BSD: <a href="http://www.daemon-systems.org/man/ntp_adjtime.2.html">
ntp_adjtime</a>
</li>
<li>adjtime — более ранний, устанавливает только величину корректировки,
темп корректировки определяет по ней само ядро.</li></ul>
<p>Много сложностей в реализации такого изменения вызвано тем, что
корректировка темпа должна быть максимально незаметной для всех
участников, включая тех, что измеряет микросекунды. Но это выходит за
пределы данной статьи. Главное — что метод с введением корректировки,
несмотря на его относительно большую сложность по сравнению с прямой
установкой конкретного момента, предпочтительнее именно плавностью
корректировки. Лучше лишний раз переспросить.</p>
<p>Аналогом в Windows представляется функция SetSystemTimeAdjustment.
Впрочем, она выглядит неконсистентной как с задачей, так и с
родственными функциями. Установка корректировки на каждое таймерное
прерывание не учитывает возможность смены темпа прерываний и требует
постоянного управления со стороны контролирующего сервиса (если он не
остановит вовремя, корректировка останется неостановленной и может
увести часы значительно дальше). Родственные функции используют не
линейное представление, а разложенное в структуру и потому требующее
сложных вычислений. Упростить работу с ними можно через FILETIME, см.
ниже.</p>
<h2><b><center>How many watches is on your clock?</center></b></h2>
<p>Итак, что мы имеем внутри типичной ОС типичного компьютера:</p>
<p>1. Счёт внешнего времени (wall time, называя меткой английской
идиомой). В Unix он считается в UTC-без-вставных-секунд в секундах и
долях секунды (далее будем называть это "unixtime") и независим от
местного часового пояса — про часовой пояс знает уже userland, причём
для каждого процесса может быть своя настройка (переменной TZ в
окружении). В Windows (потомки NT) подход идентичен с точностью
до представления (масштаб и смещение), см. ниже.</p>
<p>2. Счёт монотонного времени. До первого сна, оно, как правило,
равно аптайму системы. В спящей системе на десктопе или лаптопе (не
Android с компанией) монотонное время может стоять вместе с аптаймом
или раздельно — это не важно для обсуждаемой темы; если предусмотрен
таймер, который выводит из тотального сна (обязательно на современных
смартфонах и планшетниках) — считается и во время сна. Монотонное
время не зависит от коррекций (как плавных, так и рывком) внешнего
времени, но корректировка постоянного темпа хода часов влияет на него
(возможно, ограниченно по абсолютному значению). Темп и представление
монотонного времени берутся максимально близким к внешнему
времени.</p>
<p>Эти два счёта делаются аппаратными таймерами и их обработчиками в ОС,
подробнее об этом см. ниже.</p>
<p>3. Счёт времени, затраченного процессом, нитью, группой процессов. Из-за
существенной специфики этого измерения для каждой ОС, ограничимся
упоминанием этого счёта.</p>
<p>Представление времени может быть линейным (единственное возможное для
монотонного времени, потому что для него нет привязки к календарю) или
структурным (в виде записи из отдельных полей — год, месяц, день...)</p>
<p>Системные функции получения текущего времени в Unix возвращают
линейное время (unixtime):</p>
<p>
<br>
- clock_gettime(CLOCK_REALTIME) — заполняет struct timespec:
<pre><code>
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* and nanoseconds */
};
</code></pre>
<br>
- gettimeofday() — заполняет struct timeval — то же, но точность чуть
меньше:
<pre><code>
struct timeval {
time_t tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
</code></pre>
<br>
- time() — возвращает секунды значением типа time_t (с отброшенными
долями секунды).</p>
<p>Самая современная функция из них — clock_gettime. Несколько реликтовых
интерфейсов вроде ftime() пропущены.</p>
<p>time_t определёно как int64_t на большинстве 64-разрядных архитектур и
int32_t большинстве 32-разрядных. 32-битного счёта хватит до 2038 года
(когда будут проблемы с знаковостью представления), 2106 года (если
считать беззнаковым). 64-битного счёта хватит надолго в будущее (сотни
миллиардов лет), что явно избыточно:)</p>
<p>Далее секундную часть можно через localtime() преобразовать к
структурному виду в локальном времени, или через gmtime() — к структурному
виду в GMT. Преобразование в локальное время зависит от установки
таймзоны; её можно переопределять через переменную окружения TZ, а если
она не установлена — в большинстве систем читается /etc/localtime.
Временная зона (она же часовой пояс — далее будем использовать эти
термины как синонимы) определяет правила конверсии unixtime
в локальное время с учётом всех правил в настоящем, будущем
и прошлом, принятого часового пояса, декретного времени,
летнего времени и так далее.</p>
<p>Обратный перевод может быть выполнен из правильно заполненной
структуры (struct tm) с помощью mktime() для локального времени и
(не во всех системах) timegm() для GMT. (Впрочем, timegm()
элементарно эмулируется в ~30 строках кода.) Однако для обратного
перевода из локального времени становится принципиальным указание
признака летнего времени для момента перевода стрелок. В случае
Киева, 03:xx:yy (для любой секунды часа) 27 октября 2013 г.
повторилось дважды с интервалом в час, в первый раз по летнему
времени, во второй — по зимнему. Не указав правильное значение для
tm_isdst, невозможно получить точное значение времени. Для
однозначного структурного времени (любое кроме перевода назад),
можно установить tm_isdst равным -1, в этом случае mktime() сама
определит нужное значение.</p>
<p>Java тип java.util.Date и вызов java.lang.System.currentTimeMillis()
используют временную шкалу, полностью аналогичную unixtime, но в
миллисекундах (long — т.е. 64 бита). Документация, однако, допускает
возможность счёта со вставными секундами (не верю).</p>
<p>Windows тип FILETIME, используемый для времени файлов и для счёта в
ядре (где, по слухам, называется просто LARGE_INTEGER), представляет
время в 100-наносекундных интервалах с точки отсчёта, за которую принята
условная полночь (GMT) 1 января 1601 года по григорианскому календарю.
Размер данного — 64 бита (в виде двух слов по 32 бита), максимальная
представляемая дата — в районе 60056-го года н.э. Дискретность
такого представления вполне разумна (100 наносекунд обратно к 10
МГц и вызвано, скорее всего, ориентацией на 14.3Мгц таймеры PC),
однако надо отметить, что на 1601 год ещё не было
достаточно точного измерения вращения Земли, поэтому
наши представления об этом очень условны и считать от него бессмысленно.
Правильно было бы сказать, например, "время в 100-наносекундных
интервалах от 1 января 1970 года плюс константа" (в 1970 уже были
атомные часы и точные измерения времени), или от любого другого
современного года (если не 1970, так 1900). Но этот факт принципиален
только для понимания, что это время непригодно для астрономии; практика
кодирования обходится использованием констант. (Вероятно, в выборе 1601
года виноват стандарт ANSI Date, считающий дни от начала указанного
года?) И, опять-таки, это время не учитывает вставных секунд — поэтому
оно линейно пропорционально unixtime:
<pre><code>FILETIME = (unixtime + 11644473600) * 10000000</code></pre>
смещение 11644473600 секунд == 134774 суток по 86400 секунд, или 89
високосных лет плюс 280 невисокосных (с 1601 по 1970 год не включая
последний) таких же суток, то есть
(89*366+280*365)*86400 == 11644473600.</p>
<p>Если время в таком счёте разделить на 2**48 (сдвинуть на 48 бит
вправо), получится время в периодах, приблизительно равных 11 месяцам;
можно это брать для приближенного счёта в годах. Началу 2016 года
соответствует 0x01D1 == 465 таких периодов. В случае unixtime,
гигасекунда соответствует 32 годам — немного больше, чем 1 поколению
людей, и близко к 1/3 столетия (то есть можно оценить темп как 3
гигасекунды на столетие).</p>
<p>Для Windows (конкретно — Win32 на NT с потомками), внутренний счёт
работает в представлении FILETIME и определён следующий набор вызовов
получения текущего времени:
<dl>
<dt>GetSystemTimeAsFileTime</dt>
<dd>самый прямой вариант — только извлечь данные, без конверсии.</dd>
<dt>GetSystemTime<dt>
<dd>извлечение времени с конверсией в структурный формат в GMT, в
структуру SYSTEMTIME.</dd>
<dt>GetLocalTime</dt>
<dd>извлечение времени с конверсией в структурный формат в локальной
зоне, в структуру SYSTEMTIME.</dd>
</dl>
Однако документация утверждает обратное — якобы первичным является локальное
время, а SetTimeZoneInformation() устанавливает корректировку system time
по сравнению с local time. Из-за вероятности скачков локального времени
в моменты введения летнего или зимнего времени, этот подход некорректен
и видится вызванным сугубо наследием MS-DOS.</p>
<p>Полный вид структуры SYSTEMTIME:</p>
<pre><code>
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
</code></pre>
<p>Структура SYSTEMTIME не имеет поля признака DST (daylight saving time —
летнее время), поэтому однозначный перевод такого времени в линейное
(например, FILETIME) в периоды перевода назад получить невозможно. С
этой точки зрения, использование промежуточных функций, работающих с
локальным временем в виде SYSTEMTIME, при наличии возможности прямого
вызова, например, GetSystemTimeAsFileTime() или её более раннего
эквивалента NtQuerySystemTime, выглядит нелепостью. Поле wDayOfWeek при
конверсии в линейное время не используется.</p>
<p>С третьей стороны, наличие одного только признака DST не помогает в
случаях более сложного процесса (например, смены часового пояса
территории). Более общей была бы возможность задавать или признак DST
при местном поясе, или явное смещение от GMT. Это становится жизненно
важным для внешнего хранения.</p>
<p>Кроме внешнего времени, большинство современных Unix-систем поддерживают
отдачу монотонного времени вызовом clock_gettime(CLOCK_MONOTONIC). В
большинстве реализаций это время равно аптайму системы. FreeBSD, кроме
того, явно предоставляет CLOCK_UPTIME с тем же результирующим значением.
По Posix, это время должно быть в SI секундах (атомных); на практике
используются локальные часы, для которых в лучшем случае применена
постоянная корректировка темпа согласно измерениям NTP. Но это в любом
случае лучше, чем отсутствие такого счёта. Подробность такого значения в
Unix — до наносекунд. Более старый times() даёт секундную точность.
Аналоги в Windows — GetTickCount64(), GetTickCount() — возвращают
аптайм в миллисекундах и подвержены корректировке темпа по
SetSystemTimeAdjustment(). Но даже при такой подвержденности,
монотонный счёт выгоднее в тех случаях, когда важно, что он именно
монотонный и не подвержен рывкам вперёд или тем более назад. Ещё для
монотонного счёта может быть важно, останавливается он во время сна
(Suspend, Hibernate) системы — эффекты этого упоминаются в соседних
главах.</p>
<h2><b><center>Что делать со вставными секундами?</center></b></h2>
<p>Как показала практика, на подавляющее большинство систем проблема
неравномерности хода часов и меры по компенсации этой неравномерности не
имеют никакого значения, пока отклонение темпа не превышает некоторую
границу — даже 10% часто не замечается. Для огромного их количества
вообще нет постоянной корректировки (такой, как NTP), и
локальное отклонение может достигать минут и часов, что не мешает их
успешному функционированию. Тем не менее, такие не все. Не приводя в
пример такие особые объекты, как атомные станции (которым всё равно
требуется более серьёзная реализация, чем описано здесь), можно
вспомнить большое количество систем реального времени, в которых
одновременно важны и точный равномерный ход времени, и соответствие
внешнему гражданскому времени. Как можно их удовлетворить?</p>
<p>Вариант 1: счёт идёт по опорному сигналу от надёжного источника с
точным счётом времени. Наиболее типичные варианты — TAI, GPS. TAI
раздаёт время в атомных секундах с полуночи 01.01.1900, о нём чуть
подробнее ниже. GPS раздаёт время в атомных секундах с полуночи 6-го
января 1980 года по Гринвичу (а также и структурное UTC). Если я
не ошибся в расчётах, это меньше TAI на константу
(2524521600+6*86400+19) == 2525040019, где 2524521600 — от 1900 до
1980 года без вставных секунд, 19 — количество вставных секунд UTC
от 1900 до 1980 года.</p>
<p>Применённая в GNU libc библиотека Olson time library позволяет такой
счёт при использовании зоны с префиксом "right/":</p>
<pre><code>
$ env TZ=right/UTC ./t
1230767999 -> 2008-12-31T23:59:36
1230768000 -> 2008-12-31T23:59:37
1230768022 -> 2008-12-31T23:59:59
1230768023 -> 2008-12-31T23:59:60
1230768024 -> 2009-01-01T00:00:00
$ env TZ=right/Europe/Moscow ./t
1230767999 -> 2009-01-01T02:59:36
1230768000 -> 2009-01-01T02:59:37
1230768022 -> 2009-01-01T02:59:59
1230768023 -> 2009-01-01T02:59:60
1230768024 -> 2009-01-01T03:00:00
</code></pre>
Для сравнения, чему соответствуют те же значения unixtime в обычных
зонах:</p>
<pre><code>
$ env TZ=UTC ./t
1230767999 -> 2008-12-31T23:59:59
1230768000 -> 2009-01-01T00:00:00
1230768022 -> 2009-01-01T00:00:22
1230768023 -> 2009-01-01T00:00:23
1230768024 -> 2009-01-01T00:00:24
$ env TZ=Europe/Moscow ./t
1230767999 -> 2009-01-01T02:59:59
1230768000 -> 2009-01-01T03:00:00
1230768022 -> 2009-01-01T03:00:22
1230768023 -> 2009-01-01T03:00:23
1230768024 -> 2009-01-01T03:00:24
</code></pre>
<p>Такая система оказывается завязана на то, что unixtime не
соответствует требованиям Posix и все пересчёты должны гарантированно
выполняться через функции libc. Иногда это ограничение становится
слишком жёстким.</p>
<p>Для получения актуального внешнего представления требуется своевременное
реконфигурирование базы вставных секунд. Таблицу вставных секунд можно
получать автоматизированно с <a href="ftp://time.nist.gov/pub/">
time.nist.gov</a>. В таблице описываются моменты введения нового счёта
по NTP шкале и смещение с этого момента. Последние записи на сейчас
в этой таблице:
<pre><code>
3345062400 33 # 1 Jan 2006
3439756800 34 # 1 Jan 2009
3550089600 35 # 1 Jul 2012
3644697600 36 # 1 Jul 2015
</code></pre>
</p>
<p>(Когда будут следующие корректировки — неизвестно; в ITU-R есть
проект отмены корректировочных секунд до набора смещения в 1 час. Пока
что достаточно слежения за новостями раз в три месяца.)</p>
<p>Вариант 2: локальные часы ничего не знают про вставные секунды. По
наступлении вставной секунды обнаруживается разница между временем по
внешнему источнику и локальному, которая корректируется плавно.</p>
<p>Это наиболее практически приемлемый вариант для Unix— и Windows-систем,
не знающих про вставные секунды или не получивших данных про них,
для которых возникающая при такой реализации разница в ходе часов
соседних систем величиной до секунды не настолько существенна, чтобы
мешать функционированию (для более чем 99.99% из них).</p>
<p>NTP раздаёт время по шкале, основанной, как и TAI, на 1900-м годе, но
игнорирующей вставные секунды. Дополнительно в день вставки/удаления
передаётся специальный флаг оповещения об этом. В результате, система
обладает достаточной возможностью реагировать корректировкой локального
хода, при этом не храня актуальную таблицу вставных секунд. В момент
добавления локальной секунды счёт времени по NTP останавливается на
секунду, при удалении — перепрыгивает на секунду вперёд. (Практически,
большинство локальных серверов не получают признак корректировки секунды
и производят правку уже позже, так что её распространение от корневых
серверов может занимать несколько часов.)</p>
<p>Вариант 3: локальные часы ничего не знают про вставные секунды. При
добавлении секунды, локальные часы притормаживают или совсем
останавливаются. При удалении секунды (ещё ни разу не было), локальные
часы делают скачок вперёд на секунду. Проблема этого режима — сбои
таймеров, особенно если часы делают прыжок на секунду назад (самый
ужасный вариант).</p>
<p>Вариант 4: локальные часы ничего не знают про вставные секунды. Когда
становится известно про корректировку, в последние 1000 секунд перед ней
все синхронно корректируют темп локальных часов. Этот вариант известен
как UTC-SLS и удобен синхронностью производимых действий.</p>
<h2><b><center>Без таймзоны — никуда</center></b></h2>
<p>Как уже было показано, представление времени только в виде
день-час-минута-секунда неоднозначно даже в пределах одной системы с
неизменными настройками. Но тем более оно неоднозначно между разными
системами, потенциально находящимися в разных часовых поясах.
Естественно, признак летнего времени между ними не спасёт:) Поэтому,
есть два варианта, как корректно вести пригодное для всех
(переносимое) время:</p>
<p>1. Унифицировать в общую шкалу (unixtime, FILETIME, TAI), устраняя
местную особенность.</p>
<p>2. Передавать и хранить <b>и</b> локальное время <b>и</b> его
смещение от канона (UTC, GMT). В этом случае не нужно передавать признак
летнего времени или другие детали происхождения этого смещения — для
унификации и сравнения значений достаточно вычесть указанное
смещение.</p>
<p>В каких случаях становится принципиально важным указывать время в
переносимом виде? Как минимум это:
<ol>
<li> Базы данных.</li>
<li> Все виды сетевого клиент-серверного взаимодействия.</li>
<li> Сетевые файловые системы.</li>
<li> Внешние носители данных.</li>
</ol>
это всё тавтологические переописания простого понятия "может быть
использовано не только на текущей машине до любого ближайшего сдвига
времени". Но раскрытие их нужно для того, чтобы не дать пропустить
существенный случай. Например, СУБД может предоставлять типы данных
"дата/время" и "дата/время с часовым поясом". Нет проблем с первым из
них, пока мы работаем в пределах одного пояса и принудительно
унифицировали часовой пояс на всех системах. Но уже подключение к
серверу в Минске клиентской системы из Хабаровска приведёт к
неразберихе во временах.</p>
<p>Стандарт ISO8601, используя текстовое представление даты в UTC счёте,
устанавливает обязательное наличие указания часового пояса в виде
смещения от GMT, например:</p>
<pre>
2009-11-07T12:34:56.789+0300
2009-11-06T23:34:56.789-10:00
2009-11-07T11:34:56.789B
2009-11-07T09:34:56.789Z
(один и тот же момент времени):
</pre>
<p>ASN.1 GeneralizedTime всего лишь рекомендует указывать часовой пояс —
тот же момент времени в нём будет записан как</p>
<pre>
20091107123456.789+0300
20091107093456.789Z
</pre>
<p>У большинства современных СУБД существует тип данных "дата/время+пояс"
или "дата+пояс". Если нет противопоказаний, следует использовать его
вместо аналогичных типов без указания часового пояса.</p>
<p>Примером для Windows является CIM_DATETIME, представляющий время в виде
строки фиксированного размера и формата (25 байт). То же самое время в
нём может быть записано одним из следующих вариантов:</p>
<pre>
20091107123456.789000+180
20091107093456.789000-000
20091106233456.789000-600
</pre>
<p>Как видим, он ближе всего к GeneralizedTime, но представляет часовой
пояс достаточно непривычно — в минутах (вместо традиционных форматов в
часах и минутах). Что ж, Microsoft всегда отличалась оригинальностью, но
сам факт записи смещения в этом формате — уже колоссальный плюс.</p>
<p>Что нужно, чтобы записать дату в виде локального времени со смещением,
если смещение неизвестно? Ни localtime() в Unix, ни GetLocalTime() в
Windows не даёт таких данных (некоторые системы, как FreeBSD, добавляют
поле tm_gmtoff в struct tm, но это непереносимо). Для вычисления
смещения преобразуем сначала это время в линейное (time_t или FILETIME),
а уже его — в UTC (GMT). (Если линейное уже известно,
один шаг можно сократить;)) Имея две структуры, представляющие один и
тот же момент времени в разных форматах, можно сделать относительно
тривиальные вычисления:</p>
<pre><code>
static inline int
day_difference(struct tm *tm_gmt, struct tm *tm_local)
{
// Предусловие: две структуры представляют одну и ту же дату. Иначе
// мы не имеем права сравнивать таким простым путём.
if (tm_gmt->tm_mday == 1 && tm_local->tm_mday >= 28)
return -1;
if (tm_gmt->tm_mday >= 28 && tm_local->tm_mday == 1)
return 1;
return tm_local->tm_mday - tm_gmt->tm_mday;
}
// И ещё предусловие: все смещения часовых поясов кратны минутам.
static inline int
tzoffset_minutes(struct tm *tm_gmt, struct tm *tm_local)
{
return day_difference(tm_gmt, tm_local) * 1440 +
(tm_local->tm_hour - tm_gmt->tm_hour) * 60 +
(tm_local->tm_min - tm_gmt->tm_min);
}
</code></pre>
<p>для Windows SYSTEMTIME подход идентичен с точностью до переименования
полей. Можно также воспользоваться GetTimeZoneInformation(), при
условии, что между его вызовом и вызовом GetLocalTime() зона не
менялась. Но в общем случае использование данных <b>текущей</b> зоны
непригодно для определения смещения от GMT в прошлом или будущем, с
учётом всей истории указанного места.</p>
<p>Самые странные принятые на Земле смещения кратны 15 минутам (Непал — 5
часов 45 минут), более частые но ещё странные — получасу (десяток стран и
территорий по миру и центральный пояс Австралии). Остальные используют
смещения, кратные часу. Тем не менее, RFC3339 упоминает существование в
прошлом часовых поясов со смещением, не кратным минутам (хм?)</p>
<h2><b><center>Время, выбитое на ферромагнитной поверхности</center></b></h2>
<p>Одна достаточно интересная и путаная область представлений времени —
представления в свойствах файлов.</p>
<p>Устоявшийся для Unix-систем метод поддерживает три времени, называемые
atime, mtime и ctime. Все три исчисляются в unixtime (в переносимом
случае — с точностью до секунд). Различие между ними следующее:<ul>
<li> atime модифицируется по факту чтения из файла.</li>
<li> mtime модифицируется по факту записи в файл.</li>
<li> ctime модифицируется при создании файла, изменении параметров inode
или явной установке atime & mtime через соответсвующие вызовы
(utime(), utimes(), futimens()...) Точные правила модификации могут
зависеть от системы, хотя общий характер сохраняется.
</li>
</ul></p>
<p>Точность более секунды существенно важна для Unix из-за ориентации на
make как средство исполнения действий (далеко не только компиляцию, как
некоторые могут подумать — через make принято реорганизовывать конфиги,
строить зависимости других действий). Для устойчивой работы make нужно,
чтобы квант времени файла был заведомо меньше длительности любого
целевого действия. Поэтому, BSD системы в UFS добавили наносекунды к
этим временам (для реального включения может потребоваться включить
vfs.timestamp_precision). В Linux интерфейс ядра поддерживает
наносекунды, но не все FS это умеют (ext2, ext3 — нет). FreeBSD UFS2
добавило birth time, которое не меняется при utime() или другим
изменениям данных inode. В целом, Unix-системы достаточно однородно
работают с этими временами (секундная точность гарантирована
везде). В Posix.1-2008 стандартизован интерфейс для получения этих
времён с наносекундной точностью, хотя во многих системах он уже
был давно.</p>
<p>NTFS однородно использует FILETIME для четырёх времён (чтения,
записи, создания, модификации в MFT(?)), то есть имеем точность в идеале
до 100нс и однородно организованную. С FAT значительно сложнее — оно
"радует" разнообразием подходов. Время последнего доступа пишется с
точностью до дня, последней модификации — с точностью до 2 секунд, а
создания — до сотых долей секунды (на NT?). Смысл в подобной градации мне
установить не удалось (было бы более понятным самым точным хранить время
последней модификации, далее — доступа, и самым грубым — создания). Это
время структурное и может быть в локальной временной зоне (до NT) или в
GMT (в NT и потомках). В общем, временам на FAT можно верить только
после применения сложного корректировочного алгоритма. Это
грустно, с учётом того, что FAT во всех вариантах (включая exFAT)
активно продвигается как универсальный межсистемный формат для
переноса файлов.</p>
<p>Отдельным вопросом для Unix-подобных систем является работа с
atime. Это очень "дорогой" в сопровождении атрибут, если происходит
массовое чтение файловой системы с редкими записями; далеко не всегда
есть смысл обновлять atime миллионов файлов просто оттого, что кто-то
на них посмотрел. Флаги noatime для файловых систем есть почти во всех
современных ОС. Но в Linux по умолчанию сейчас действует "relaxed
atime", по которому у файла, в который не пишут, atime меняется не
чаще раза в сутки (это время фиксировано). В среднем это таки разумный
вариант.</p>
<h2><b><center>О внутреннем счёте времени</center></b></h2>
<p>Как уже говорилось, точность любых локальных средств скорее всего
недостаточна для самостоятельного счёта времени в течение длительных
периодов времени (часы, сутки и более). Тем не менее, обычные
локальные средства чаще всего достаточны, чтобы измерять время с
необходимой точностью при условии регулярной корректировки по внешним
источникам.</p>
<p>Все аппаратные средства локального счёта времени соответствуют
следующему относительно простому набору условий:<ol>
<li>Они позволяют узнавать время с хорошей точностью (на PC — не менее
чем до микросекунды) без остановки счёта.</li>
<li>Они генерируют прерывания, "оживляющие" работу системы (шедулинг
задач, сброс буферов, пересчёт статистики, etc.)</li>
</ol>
Однако, вне этих рамок особенности реализации существенно
различаются.</p>
<p>Какой конструктив локального таймера был бы идеальным для задачи счёта
времени в ОС разделения времени? В рамках современных подходов к
схемотехнике, это циклический счётчик, инкрементируемый постоянной
опорной частотой, и возможность его считывать, но этого недостаточно.<br>
Базовые требования к нему:</p>
<p>1. Достаточно большая длительность цикла. Этот цикл должен быть не
менее 2 максимально возможных длительностей сложной работы с аппаратной
частью, при которой заблокированы прерывания. Значения менее секунды
находятся за гранью допустимого, оценочный минимум — 2 секунды.</p>
<p>2. Постоянный ход независимо от процедур чтения данных. (Из этого
следует, что переустановка для выбора времени следующего прерывания
недопустима.)</p>
<p>3. Достаточная подробность показаний (опорная частота не менее
1МГц).</p>
<p>4. Возможность генерации прерываний с постоянной частотой или близко к
заданным моментам.</p>
<p>Из существующих сейчас таймеров платформы PC всем этим условиям
соответствует только таймер ACPI-fast (он же ACPI-safe, он же PIIX) в
сочетании с некоторым источником генерации прерываний (например i8254
или LAPIC), и HPET не со всеми стилями применения. Остальные источники
или смешивают средства счёта и генерации прерываний, или теряют точность
при переустановке.</p>
<p>Для таймера в первом IBM PC был выбран распространённый кварц
(в каждом американском телевизоре;), генерирующий частоту равной 4
частотам цветовой поднесущей NTSC; точное его определение даёт
315/22 МГц = 14318181.818... Гц, кварц стандартизован с
округлением до целого числа 14318182 Гц. На вход микросхемы i8254 был
подан этот сигнал, разделённый на 12, что даёт 1193181,8... Гц.
Отсюда "заветное" число 1193182, известное во всех руководствах по
IBM PC. Для ACPI-fast, делитель равен 4 и частота таймера равна
~3579545.5 Гц. i8254 имеет только 16-разрядный счётчик, поэтому
переход через ноль и генерация прерывания происходит не реже ~18.2
раза (1193181.81/65536) в секунду, можно программировать на более
частые прерывания. Счётчик времени BIOS хранит количество таких
прерываний (за полные 65536 тиков) от границы суток (4 байта на
хранение значения до чуть более полутора миллионов). При желании
можно считать текущее значение счётчиков в таймерах и получить
время с точностью до микросекунды, но это требует тщательного
анализа фактора перехода через 0.</p>
<p>ACPI-fast реализован как циклический счётчик размером 24 бита
(тогда он совершает полный цикл за ~4.7 секунды) или 32 бита (20
минут соответственно). Переустанавливать его на ходу нельзя, что
является выдающимся плюсом.
Отрицательная черта — сложность процедуры чтения из-за отсутствия
регистра-защёлки для чтения: нужно получить три значения подряд и
убедиться в их монотонности.</p>
<p>Самая современная разработка Intel — HPET (high performance event
timers) — тщательно разработана в плане управления раутингом прерываний,
но опять-таки страдает потерей точности при необходимости
перенастройки для периодического режима. Однако, если настраивать таймер
в непериодическом режиме, этой проблемы нет. До ~2007 года HPET
обычно "кормили" неразделённой исходной частотой телевизионного
кварца 14.318181...МГц, после — чаще видно 25 МГц (напрямую с
основного кварца, с которого берутся опорные частоты для генерации
тактовых сигналов шин и процессоров); требования Intel — не менее
10МГц. Intel хочет устранить старые таймеры (кроме HPET), но это
вряд ли будет возможно до того, как вообще перестанет работать
MS-DOS и старые стили BIOS.</p>
<p>Какая требуется точность значения времени для пользовательских
запросов? Понятно, что если уже известно достаточно точное значение, то
экономить на его представлении обычно смысла мало. Но разница между
стоимостью операции, которая обращается к аппаратному таймеру, и
операцией, которая отдаёт готовое значение после последнего события,
которое обновило это значение (срабатывание шедулера, прерывание
аппаратного таймера, etc.) может быть разительной.</p>
<p>Самый гранулированный подход из известных показывает FreeBSD —
начиная с 7-й версии были разделены операции точного снятия и
использования уже готового значения — например, CLOCK_REALTIME_PRECISE,
которая вызывает перечитывание аппаратного таймера;
CLOCK_REALTIME_FAST, которая полагается на последнее обновление; и
наконец CLOCK_SECOND, которая не требует долей секунды и точности более
секунды. Для подавляющего большинства применений достаточно
"fast" вариантов.
Unix-like системы (начиная с Linux) стараются сейчас организовать чтение
времени вообще без перехода в ядро; для этого используется механизм
модификации ссылок на динамически линкуемые функции и
отображение памяти аппаратных таймеров только для чтения.
В Windows, судя по доступным данным, GetSystemTime() и аналоги
используют значение из разделяемой памяти, не требуя его обновления,
но там хранится только количество сработавших прерываний; следствие —
точность более ~10мс (при 100Гц прерываниях таймера) от этих средств
недоступна, для этого нужно использовать другие функции
(QueryPerformanceCounter, но синхронизацию с внешним временем надо
вычислять самому). (Уточнить на современные версии.)
</p>
<h2><b><center>По каким часам спим?</center></b></h2>
<p>Интересным и существенным вопросом является выбор счёта времени для
внутренних программных таймеров.</p>
<p>До начала-середины 2000-х, единственным вариантом, о котором
массово думал использующий народ и который существовал во всех
интерфейсах, было внешнее время (unixtime, wall clock,
CLOCK_REALTIME). Многие интерфейсы и сейчас имеют это единственным
вариантом. Реализации с монотонным временем начались только вместе с
timer_create() из состава бывшего POSIX realtime API (связь с realtime
тут только та, что последнее обострило нужду в подобных средствах до
уровня полной неизбежности). FreeBSD имеет
pthread_condattr_setclock(), которая задаёт, какое время понимается в
аргументах — pthread_cond_timedwait() — внешнее или монотонное.</p>
<p>Ядро Linux уже заметно давно перешло на показ таймстампов при
сообщениях в monotonic time (не считая периодов сна, совпадает с
uptime), например:</p>
<code><pre>
[ 0.000000] e820: BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009cbff] usable
[ 3.693135] hda-intel 0000:01:00.1: Handle VGA-switcheroo audio client
[299334.802152] type=1400 audit(1454391963.188:89): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="/usr/sbin/cupsd" pid=28046 comm="apparmor_parser"
</pre></code>
<p>До этого использовалось "wall clock".</p>
<p>Тем не менее, проблема того, что логика "я хочу спать тут 1 час" (а
не до конкретного момента времени по какой-то из шкал) не
содержит никакого прямого указания, это абсолютное, относительное
время или какое-то другое — остаётся в полный рост. Как можно решать
эту проблему? Мне видится, например, вариант организовать для
kqueue/epoll/etc. заказ ожидания "разница между абсолютным и
относительным временем вышла за указанные границы" с заданием минимума
и максимума допустимой разницы.</p>
<p>(В новых интерфейсах — типа timerfd в Linux — это решено, при их
создании можно указывать тип времени, по которому ожидание, и ждать их
сразу два. Кривовато, но, по идее, должно сработать.)
<h2><b><center>Календарь недалёкого прошлого</center></b></h2>
<p>Связному счёту прошлого времени в Европе мы обязаны Дионисию
Малому, который проработал и закрепил известный нам счёт
"от рождества Христова" (промахнувшись на 7 лет), и Жозефу Жюсту
Скалигеру, который ещё более чем через тысячу лет обновил результаты и
подвёл научную основу. Кроме этого, Скалигер ввёл счёт в так
называемых "юлианских днях". Счёт этот достаточно своеобразен —
граница дня приходится не на полночь; точка 0 — <b>полдень</b> 1
января 4713 г. до н.э. по юлианскому календарю. Все исторически
зафиксированные события попадают на положительную сторону оси
такого отсчёта (самое раннее что нам относительно достоверно
известно — хронология древнего Шумера — начинается с середины 4-го
тысячелетия до н.э. и имеет погрешность в годы и десятки лет;
установленные с точностью до дня даты начинаются значительно
позже), поэтому у такого счёта почти нет проблемы с понятием нуля
и методом понимания отрицательных значений (сколько лет между 1-м
годом до н.э. и 1-м годом н.э.?)</p>
<p>При Скалигере граница дня определялась по меридиану Александрии.
Современное определение уточняет, что опорный часовой пояс для такого
счёта с точностью до секунды равен Гринвичу (сейчас рекомендуется
Terrestrial Time). Независимо от выбора точной шкалы для счёта юлианских
дней, они связана именно с Солнцем, поэтому особенности счёта UTC
(вставные секунды) не влияют на них.
</p>
<p>Соответствия между Julian Day и привычными нам линейными временами:
<pre><code>
unixtime = (JD-2440587.5)*86400
windows_FILETIME = (JD-2305812.5)*86400*10000000
</code></pre>
(смещения взяты из википедии, не проверял). При переводе надо помнить,
что неточность реальных событий в юлианских днях вряд ли меньше целых
минут, а то и часов. В то же время это уже неплохая основа для
астрономических шкал. Не следует, однако, забывать, что пересчитывая в
прошлое и сравнивая даты, надо всегда уточнять стиль календаря. Различие
между григорианским и юлианским счётом — минимально ошибочное среди
возможных; неправильно определив принятое начало года, можно
промахнуться на год. Это уже область заботы историка и филолога.
</p>
<p>Существует около десятка модификаций такой шкалы с другими опорными
моментами; большинство из них тоже называются "юлианскими днями", но с
разнообразными префиксами (модифицированный, сокращённый, etc.) Для нас
это различие непринципиально. Выше упоминалась ANSI date из того
же ряда.</p>
<h2><b><center>Кто виноват и что делать?</center></b></h2>
<p>Как видно из изложенного, если бы не грубая реальность, мы бы не
имели вообще никаких проблем со временем.;) Так как устранить законы
природы или даже сделать идеально стабильное движение Земли мы не в
состоянии, предлагаю считать первый вечный вопрос решённым и перейти ко
второму.</p>
<p>Рамочные принципы работы (то есть не "как делать", а "до чего не
доходить") прикладных приложений со временем можно описать следующим
образом:
<ol>
<li>Не делать ложных допущений.</li>
<li>Не допускать незаметной потери точности.</li>
</ol>
</p>
<p>Все описанные "подводные камни" так или иначе ложатся на
какие-то принципы из этого списка. Например, незнание вставных
секунд — ложные допущения. Отсутствие признака DST в структуре
SYSTEMTIME — незаметная потеря точности, проявляющаяся в
маргинальных случаях. Неиспользование часового пояса в базе данных
- и первое, и второе. Предположение, что все даты в прошлом
записаны по какому-то конкретному календарю (юлианскому,
григорианскому...) — ложные допущения. И так далее. К сожалению,
мест и ситуаций, где можно нарушить эти простые правила не заметив
нарушения, больше, чем кажется.</p>
<p>Конструктивный метод определения подходящего представления времени
и правил работы со временем можно определить следующим образом:</p>
<p>I. Выберите необходимый тип задачи:<ul>
<li>Счёт реального времени, точнее секунды.</li>
<li>Счёт реального времени для обычных задач, точнее секунды.</li>
<li>Счёт реального времени для обычных задач, секундная точность.</li>
<li>Счёт реального времени для обычных задач, часовая или суточная
точность.</li>
<li>Счёт времени независимо от точки отсчёта, произвольная
точность.</li>
<li>Счёт прошлого времени, точность не ниже суточной.</li>
<li>Счёт прошлого времени на основании опорных точек без точной привязки