"export TZ=date +%Z" приводит к путаному выводу команды "date"

Вопрос или проблема

Я недавно заметил следующее (странное) поведение:

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” затем просто вставляется в строку даты, которая выводится.

Вопросы:

  1. Правильна ли предположение, описанное выше?
  2. Почему 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, установленное вами, неверно интерпретируется системой.

Причины путаницы

  1. Формат переменной TZ: Переменная TZ может принимать несколько форматов. Например, вы можете задать ее как TZ=ABC3 или TZ=ABC-3, что будет означать, что время будет находиться на три часа раньше/позже UTC. При установке TZ как CEST, система не может найти соответствующую временную зону в каталоге /usr/share/zoneinfo, и интерпретирует ее иначе.

  2. Отсутствие соответствующих файлов: У вас нет файла временной зоны CEST в /usr/share/zoneinfo, из-за чего date просто игнорирует расширенную интерпретацию и применяет значение по умолчанию, что приводит к неправильным расчетам.

  3. Обработка аббревиатур: Когда вы выставляете временную зону как CEST, система не находит соответствующий файл и, скорее всего, обрабатывает это значение как смещение относительно UTC (то есть CEST интерпретируется как UTC+2, а не как CEST с учетом перехода на летнее/зимнее время).

Ответы на ваши вопросы

  1. Ваше предположение о том, что строка ABC+X всегда рассматривается как UTC+X, когда файл /usr/share/zoneinfo/ABC отсутствует, является правильным. Таким образом, так как файл для CEST отсутствует, значение строится на основании UTC и смещения не применяются корректно.

  2. Удивительный вывод команды date с использованием неопределённой абревиатуры времени также объясняется тем, что команда использует значение, взятое из переменной TZ, не находя ни каких-либо данных времени, следовательно, программа выводит результат с неправильным смещением. Подобные значения, не имеющие соответствующих файлов в zoneinfo, считаются нестандартными и не могут быть корректно трактованы.

Рекомендации

Чтобы избежать подобной путаницы в будущем, рекомендуется:

  • Использовать временные зоны, которые существуют на вашем компьютере, безопасно проверяя их с помощью команд ls /usr/share/zoneinfo или timedatectl list-timezones.
  • Если вам нужно установить временную зону на CEST, вы можете использовать TZ=Europe/Berlin (или другую соответствующую временную зону), которая будет правильно учитывать все аспекты временных изменений.
  • Прежде чем экспортировать переменную TZ, убедитесь, что вы установили корректное значение, чтобы избежать неожиданных смещений во времени.

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

Оцените материал
Добавить комментарий

Капча загружается...