Вопрос или проблема
Я недавно заметил следующее (странное) поведение:
user@pc:~$ date
Ср 21. Июнь 12:03:10 CEST 2017
user@pc:~$ date +%Z
CEST
user@pc:~$ export TZ=`date +%Z`
user@pc:~$ date
Ср 21. Июнь 10:03:30 CEST 2017
user@pc:~$ date +%Z
CEST
Таким образом, после установки переменной окружения TZ на текущий часовой пояс системы часы отстают на 2 часа. Это похоже на UTC (CEST – 2 часа это UTC). Если я теперь установлю TZ на другие значения, часы остаются без изменений:
user@pc:~$ export TZ=UTC
user@pc:~$ date
Ср 21. Июнь 10:07:09 UTC 2017
user@pc:~$ export TZ=PDT
user@pc:~$ date
Ср 21. Июнь 10:07:19 PDT 2017
Тем не менее, когда я устанавливаю TZ на CEST-2, все снова работает исправно. Я немного запутался.
user@pc:~$ export TZ=CEST-2
user@pc:~$ date
Ср 21. Июнь 12:28:16 CEST 2017
Я работаю на xUbuntu 16.04, но это поведение воспроизводится на системе OpenSUSE 42.2.
Мне кажется, что часовой пояс “ABC+X” всегда рассматривается как “UTC+X”, когда нет файла /usr/share/zoneinfo/ABC (спасибо DevilaN за комментарий). Строка “ABC” затем просто вставляется в строку даты, которая выводится.
Вопросы:
- Правильна ли предположение, описанное выше?
- Почему
date
выводит сокращение часового пояса, которое не поддерживается (т.е. не доступно в /usr/share/zoneinfo)?
Вывод date +%Z
не предполагается использовать как значение $TZ
, но он выводится на основе $TZ
.
$TZ
принимает спецификацию часового пояса в разных форматах. Для упрощения, это может быть:
TZ=ABC3
/TZ=ABC-3
(илиTZ='<ABC>+3'
/TZ='<ABC>-3:00:00'
), что означает, что даты будут на 3 часа отставать/опережать UTC круглый год, каждый год, навсегда, иdate +%Z
выведетABC
(что-то, имеющее отношение к пользователю; например,BST
означало бы Бангладешское стандартное время для бангладешца, Британское летнее время для британца; тот факт, что они означают что-то разное для разных людей, должен давать понять, что они не могут использоваться как спецификация часового пояса сами по себе).TZ=ABC7XYZ
так же, какTZ=ABC7XYZ6
, означает, что время на 7 часов отстает от UTC (иdate +%Z
возвращаетABC
) зимой и на 6 летом, с переключением между зимним и летним временем в соответствии с правилами, действующими сейчас в Нью-Йорке США, но здесь применяемыми для любого времени в прошлом или будущем.TZ=ABC7XYZ6,<complex-rules>
, где правила переключения между/на летнее время явно указаны.:<implementation-defined>
. POSIX позволяет реализациям определять часовой пояс дополнительными проприетарными способами (хотя большинство использовали описанную ниже схему).
Сейчас эти форматы не работают на практике, потому что правила часовых поясов (UTC смещения и когда происходят изменения между зимним и летним временем, если таковые вообще есть) меняются чуть ли не каждый день в одной стране/регионе или другой. Поэтому данное правило обычно применимо только для заданного временного интервала.
На большинстве систем в наши дни вы используете определения часового пояса, предоставленные IANA, выраженные в бинарных файлах, скомпилированных из исходных данных tzdata с помощью команды zic
, и которые называются Region/Most-populated-place
, таким образом как Europe/London
, America/Chicago
…
Эта форма $TZ
только что была добавлена в 2024 году стандарта POSIX (см. соответствующий запрос), но уже давно используется на системах POSIX.
На практике, когда вы устанавливаете TZ=Europe/Paris
, система будет искать определение часового пояса в чем-то вроде /usr/share/zoneinfo/Europe/Paris
и для данного момента времени даст вам время по стене в этом регионе, а также (для %Z
) метку часового пояса, которая была бы (или, надеюсь, будет) актуальна в это время.
$ TZ=Europe/Paris strace -qqze/open date -d @-1231231122
[...]
openat(AT_FDCWD, "/usr/share/zoneinfo/Europe/Paris", O_RDONLY|O_CLOEXEC) = 3
[...]
Пт 26 Дек 15:21:18 WET 1930
(обозначается W
естным E
вропейским T
емпом, тогда как после Второй мировой войны вы получите CET / CEST).
Даже если вы считаете, что это было бы бессмысленно, вы обнаружите, что /usr/share/zoneinfo
на самом деле также содержит:
CET CST6CDT EET EST
EST5EDT GMT GMT+0 GMT-0
GMT0 HST MET MST
MST7MDT NZ-CHAT PRC PST8PDT
ROC ROK UCT UTC
WET
(и не другие комбинации). Это обосновано IANA на https://data.iana.org/time-zones/theory.html#naming:
Руководства со временем развивались, и названия, следуя старым версиям этих руководств, могут не соответствовать текущей версии. Когда руководства менялись, старые названия продолжают поддерживаться. Изменения в руководствах включают следующее:
- Более старые версии этого пакета использовали другую схему именования. См. файл ‘backward’ для большинства этих старых имен (например, ‘US/Eastern’ вместо ‘America/New_York’). Другие старомодные названия, которые до сих пор поддерживаются, это ‘WET’, ‘CET’, ‘MET’ и ‘EET’ (см. файл ‘europe’).
- Более старые версии этого пакета определяли легаси-названия, которые несовместимы с первой директивой названий местоположений, но которые все еще поддерживаются. Эти легаси-имена в основном определены в файле ‘etcetera’. Кроме того, файл ‘backward’ определяет легаси-имена ‘Etc/GMT0’, ‘Etc/GMT-0’, ‘Etc/GMT+0’, ‘GMT0’, ‘GMT-0’ и ‘GMT+0’, и файл ‘northamerica’ определяет легаси-имена ‘EST5EDT’, ‘CST6CDT’, ‘MST7MDT’ и ‘PST8PDT’.
- Более старые версии этих руководств говорили о том, что обычно должно быть как минимум одно название для каждого официально назначенного двухбуквенного кода ISO 3166-1 для населенной страны или территории. Эта старая директива была отменена, поскольку она не требовалась для правильной обработки временных отметок и увеличивала нагрузку по обслуживанию.
Эти названия теперь устарели. В NEWS
файле версии 2024b вы увидите:
Названия, присутствующие только для совместимости с UNIX System V (выпущенной в 1990-х), были перемещены в ‘backward’. Эти названия, которые в основном дублируют данные о географических названиях для временных отметок после 1970 года, путали дальнейшие применения. Названия, перемещенные в ‘backward’, теперь являются ссылками на географические названия. Это затрагивает поведение для TZ=’EET’ для некоторых временных отметок до 1981 года, для TZ=’CET’ для некоторых временных отметок до 1947 года и для TZ=’WET’ для некоторых временных отметок до 1996 года. Кроме того, TZ=’MET’ теперь ведет себя как TZ=’CET’ и использует сокращение “CET” вместо “MET”. Тем, кто нуждается в предыдущем поведении TZDB, которое не соответствует никаким реальным часам, могут найти старые записи в ‘backzone’. (Проблема была зарегистрирована Джастином Грантом.)
По крайней мере, на Debian использование TZ=MST7MDT
действительно использует файл /usr/share/zoneinfo/MST7MDT
, хотя это также синтаксис для явного указания TZ (второй пункт выше). Использование TZ=:MST7MDT
(файл) против TZ='<MST>7<MDT>6'
(явные смещения) устраняет двусмысленность.
Что касается CET
, то здесь нет двусмысленности, поскольку оно не соответствует никакому из явных правил.
Если вы посмотрите на файл europe
из версии базы данных 2024a, он определяется следующим образом:
Правило C-Eur 1916 только - Апр 30 23:00 1:00 S
Правило C-Eur 1916 только - Окт 1 1:00 0 -
Правило C-Eur 1917 1918 - Апр Пн>=15 2:00s 1:00 S
Правило C-Eur 1917 1918 - Сен Пн>=15 2:00s 0 -
Правило C-Eur 1940 только - Апр 1 2:00s 1:00 S
Правило C-Eur 1942 только - Ноя 2 2:00s 0 -
Правило C-Eur 1943 только - Мар 29 2:00s 1:00 S
Правило C-Eur 1943 только - Окт 4 2:00s 0 -
Правило C-Eur 1944 1945 - Апр Пн>=1 2:00s 1:00 S
# Уитмен указывает на 1944 Окт 7; идите с Шенксом и Поттингером.
Правило C-Eur 1944 только - Окт 2 2:00s 0 -
[...]
Правило C-Eur 1945 только - Сен 16 2:00s 0 -
Правило C-Eur 1977 1980 - Апр Вс>=1 2:00s 1:00 S
Правило C-Eur 1977 только - Сен последняяВс 2:00s 0 -
Правило C-Eur 1978 только - Окт 1 2:00s 0 -
Правило C-Eur 1979 1995 - Сен последняяВс 2:00s 0 -
Правило C-Eur 1981 максимум - Мар последняяВс 2:00s 1:00 S
Правило C-Eur 1996 максимум - Окт последняяВс 2:00s 0 -
# E-Eur отличается от EU только тем, что E-Eur переключается в полночь местного времени.
Правило E-Eur 1977 1980 - Апр Вс>=1 0:00 1:00 S
Правило E-Eur 1977 только - Сен последняяВс 0:00 0 -
Правило E-Eur 1978 только - Окт 1 0:00 0 -
Правило E-Eur 1979 1995 - Сен последняяВс 0:00 0 -
Правило E-Eur 1981 максимум - Мар последняяВс 0:00 1:00 S
Правило E-Eur 1996 максимум - Окт последняяВс 0:00 0 -
[...]
# Зона НАЗВАНИЕ СТДОФФ ПРАВИЛА ФОРМАТ [ДО]
[...]
Зона CET 1:00 C-Eur CE%sT
[...]
И на системах, которые все еще это имеют:
$ TZ=CET date -d 2024-07-01
Пн 1 Июль 00:00:00 CEST 2024
$ TZ=CET date -d 1920-07-01
Чт 1 Июль 00:00:00 CET 1920
$ TZ=Europe/Paris date -d 1920-07-01
Чт 1 Июль 00:00:00 WEST 1920
CET
с 2024b теперь находится в файле backward
, но как:
# Прежние конвенции именования 1993 года
# Связь ЦЕЛЬ ИМЯ-СВЯЗИ #= ЦЕЛЬ1
[...]
Ссылка Europe/Brussels CET
Таким образом, это эквивалентно бельгийскому времени.
Поскольку нет файла CEST
, TZ=CEST
не специфицирован POSIX и в вашем случае, вероятно, трактуется как TZ=CEST0
, так что это просто время UTC с названием CEST
.
Ответ или решение
Ваша проблема с выводом команды date
, связанная с установкой переменной окружения TZ
, может вызывать путаницу из-за особенностей работы с временными зонами в UNIX-подобных системах.
Описание поведения
При выполнении команд, которые вы привели, вы сталкиваетесь с неожиданным поведением. В частности:
user@pc:~$ date
Mi 21. Jun 12:03:10 CEST 2017
user@pc:~$ date +%Z
CEST
user@pc:~$ export TZ=`date +%Z`
user@pc:~$ date
Mi 21. Jun 10:03:30 CEST 2017
Здесь вы сначала получаете правильное время и временную зону (CEST), но после того, как установите переменную TZ
как результат выполнения команды date +%Z
, происходит смещение на два часа назад. Это происходит, потому что значение TZ
, установленное вами, неверно интерпретируется системой.
Причины путаницы
-
Формат переменной TZ: Переменная
TZ
может принимать несколько форматов. Например, вы можете задать ее какTZ=ABC3
илиTZ=ABC-3
, что будет означать, что время будет находиться на три часа раньше/позже UTC. При установкеTZ
какCEST
, система не может найти соответствующую временную зону в каталоге/usr/share/zoneinfo
, и интерпретирует ее иначе. -
Отсутствие соответствующих файлов: У вас нет файла временной зоны
CEST
в/usr/share/zoneinfo
, из-за чегоdate
просто игнорирует расширенную интерпретацию и применяет значение по умолчанию, что приводит к неправильным расчетам. -
Обработка аббревиатур: Когда вы выставляете временную зону как
CEST
, система не находит соответствующий файл и, скорее всего, обрабатывает это значение как смещение относительно UTC (то естьCEST
интерпретируется какUTC+2
, а не какCEST
с учетом перехода на летнее/зимнее время).
Ответы на ваши вопросы
-
Ваше предположение о том, что строка
ABC+X
всегда рассматривается какUTC+X
, когда файл/usr/share/zoneinfo/ABC
отсутствует, является правильным. Таким образом, так как файл дляCEST
отсутствует, значение строится на основанииUTC
и смещения не применяются корректно. -
Удивительный вывод команды
date
с использованием неопределённой абревиатуры времени также объясняется тем, что команда использует значение, взятое из переменнойTZ
, не находя ни каких-либо данных времени, следовательно, программа выводит результат с неправильным смещением. Подобные значения, не имеющие соответствующих файлов вzoneinfo
, считаются нестандартными и не могут быть корректно трактованы.
Рекомендации
Чтобы избежать подобной путаницы в будущем, рекомендуется:
- Использовать временные зоны, которые существуют на вашем компьютере, безопасно проверяя их с помощью команд
ls /usr/share/zoneinfo
илиtimedatectl list-timezones
. - Если вам нужно установить временную зону на
CEST
, вы можете использоватьTZ=Europe/Berlin
(или другую соответствующую временную зону), которая будет правильно учитывать все аспекты временных изменений. - Прежде чем экспортировать переменную
TZ
, убедитесь, что вы установили корректное значение, чтобы избежать неожиданных смещений во времени.
Таким образом, в случае неправильного формата временной зоны или отсутствующих файлов не стоит полагаться на значения, выведенные по умолчанию, так как это может привести к путанице и неправильному отображению времени.