#ruby #time #alpine #activesupport
#ruby #время #alpine-linux #активная поддержка
Вопрос:
Я не знаю, делаю ли я что-то глупое, так что потерпите меня.
tl; dr Время и часовой пояс активной поддержки Rails, похоже, имеют ошибку в Alpine Linux. Он использует вариант летнего времени (летнее время) моего часового пояса, когда он должен использовать зимнее время.
Шаги для воспроизведения:
- Запустите оболочку в образе Docker Alpine Linux Ruby:
$ docker run -it --rm ruby:2.7.1-alpine sh
Все следующие шаги выполняются внутри запущенного контейнера docker.
- Установка данных часового пояса:
$ apk add --no-cache --update tzdata
fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2020c-r0)
Executing busybox-1.31.1-r9.trigger
OK: 23 MiB in 37 packages
- Вывести стандартное текущее время ruby:
$ ruby -e 'puts Time.now.inspect'
2020-10-28 20:34:24.4817918 0000
Выглядит хорошо. Время печатается в UTC.
- Помимо UTC, стандартный
Time
класс ruby может обрабатывать только местное время. Это зависит от часового пояса локальной системы, который можно настроить с помощью механизмов настройки операционной системы или который можно просто передать Ruby сTZ
помощью переменной env. Давайте попробуем использовать мой часовой пояс «Европа / Берлин»:
$ TZ="Europe/Berlin" ruby -e 'puts Time.now.inspect'
2020-10-28 21:39:22.7037648 0100
Выглядит хорошо. Часовой пояс Берлина — UTC 01 зимой (стандартный) или UTC 02 летом (летнее время). На момент написания этой статьи у нас зимнее время, так 0100
что все в порядке.
- Теперь давайте перейдем к ActiveSupport:
$ gem install activesupport
Fetching tzinfo-1.2.7.gem
Fetching i18n-1.8.5.gem
Fetching activesupport-6.0.3.4.gem
# #### many more lines of output ####
Successfully installed activesupport-6.0.3.4
6 gems installed
- В отличие от стандартного ruby, ActiveSupport поддерживает все возможные часовые пояса, а не только два. Получение текущего времени
Time.current
, так что давайте попробуем это:
$ ruby -e 'require "active_support/all"; puts Time.current.inspect'
2020-10-28 20:43:51.1098842 0000
Это выглядит не отличается от вывода шага 3. Причина этого в том, что Time.current
только поведение отличается Time.now
от того, когда настроен часовой пояс.
- Мы можем настроить часовой пояс для ActiveSupport с
Time.zone=(timezone_identifier)
помощью или сTime.use_zone(timezone_identifier) { "inside this block the timezone is used" }
помощью . Давайте попробуем первый вариант:
$ ruby -e 'require "active_support/all"; Time.zone = "UTC"; puts Time.current.inspect'
Wed, 28 Oct 2020 20:50:55 UTC 00:00
Мы все еще находимся в UTC, но результат выглядит иначе, чем раньше. Из этого мы знаем, что мы получили ActiveSupport::TimeWithZone
объект. Это хорошо.
- Теперь я хочу то же самое для своего часового пояса:
$ ruby -e 'require "active_support/all"; Time.zone = "Europe/Berlin"; puts Time.current.inspect'
Wed, 28 Oct 2020 22:52:21 CEST 02:00
На первый взгляд это выглядит хорошо, но тщательно сравните его с выводом шага 4. В часовом поясе «Европа / Берлин» в настоящее время у нас зимнее время, UTC 01, также называемое «CET» (центральноевропейское время). Но на этот раз в выходных данных временная метка помечена как «CEST» (летнее время Центральной Европы), то есть UTC 02.
И это неправильно.
Где ошибка? Это в Alpine Linux? Это в стандартном Ruby? Но вывод правильный на шаге 4. Или ошибка в ActiveSupport или его связи с данными часового пояса? Я делаю что-то не так?
Комментарии:
1. Добро пожаловать в SO! Поздравляю с фантастически написанным вопросом.
2. О, просто чтобы избежать каких-либо вопросов в этом направлении: конечно, я проверил, что шаг 8 выполняется правильно — он выдает желаемый результат — на не альпийском изображении.
3. И это влияет не только на
#current
метод, но и на все зависящие от часового пояса методы ActiveSupport, напримерTime.zone.at
, илиTime.zone.parse
(например, вruby -e 'require "active_support/all"; Time.zone = "Europe/Berlin"; puts Time.zone.at(Time.now.to_i).inspect'
).4. Насколько я знаю, ActiveSupport использует TZInfo в качестве источника информации. Возможно, вы захотите переключить внимание на эту библиотеку, чтобы приблизиться к корню проблемы.
5. Похоже, пришло время отправить отчет об ошибке в репозитории Active report
Ответ №1:
Оказывается, это проблема, влияющая на tzinfo
gem (версии < 1.2.8
/ < 2.0.3
) — она несовместима с данными о часовом поясе 2020b (и далее?), А последние версии Alpine поставляются с 2020c. Формат файла изменен с «fat» на «slim», что пока несовместимо с tzinfo
gem.
Обновленное решение (2020-11-09)
Поддержка файлов zoneinfo формата «slim» теперь добавлена в tzinfo
gem в выпусках:
- версия v1.2.8
- v2.0.3
Оригинальное решение Ruby
Вы можете использовать tzinfo-data
gem вместо того, чтобы полагаться на system tzinfo:
gem install activesupport tzinfo-data
ruby -e 'require "active_support/all"; Time.zone = "Europe/Berlin"; puts TZInfo::DataSource.get; puts Time.current.inspect'
# Ruby DataSource
# Thu, 29 Oct 2020 16:25:08 CET 01:00
Оригинальное решение Alpine
Вы можете перестроить пакет данных часового пояса в формате ‘fat’ (адаптировано из комментария):
FROM ruby:2.7.2-alpine
# Install tzdata because we need the zic binary
RUN apk add --no-cache tzdata
# Fix incompatibility with slim tzdata from 2020b onwards
RUN wget https://data.iana.org/time-zones/tzdb/tzdata.zi -O /usr/share/zoneinfo/tzdata.zi amp;amp;
/usr/sbin/zic -b fat /usr/share/zoneinfo/tzdata.zi
Затем проверка времени в Europe/Berlin
:
gem install activesupport
ruby -e 'require "active_support/all"; Time.zone = "Europe/Berlin"; puts TZInfo::DataSource.get; puts Time.current.inspect'
# Zoneinfo DataSource: /usr/share/zoneinfo
# Thu, 29 Oct 2020 16:29:19 CET 01:00
Комментарии:
1. Поддержка файлов zoneinfo в формате «slim» теперь добавлена в tzinfo в версиях v1.2.8 и v2.0.3 .