Ошибка времени и часового пояса активной поддержки Rails в Alpine Linux

#ruby #time #alpine #activesupport

#ruby #время #alpine-linux #активная поддержка

Вопрос:

Я не знаю, делаю ли я что-то глупое, так что потерпите меня.

tl; dr Время и часовой пояс активной поддержки Rails, похоже, имеют ошибку в Alpine Linux. Он использует вариант летнего времени (летнее время) моего часового пояса, когда он должен использовать зимнее время.

Шаги для воспроизведения:

  1. Запустите оболочку в образе Docker Alpine Linux Ruby:
 $ docker run -it --rm ruby:2.7.1-alpine sh
  

Все следующие шаги выполняются внутри запущенного контейнера docker.

  1. Установка данных часового пояса:
 $ 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
  
  1. Вывести стандартное текущее время ruby:
 $ ruby -e 'puts Time.now.inspect'
2020-10-28 20:34:24.4817918  0000
  

Выглядит хорошо. Время печатается в UTC.

  1. Помимо 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 что все в порядке.

  1. Теперь давайте перейдем к 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
  
  1. В отличие от стандартного 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 от того, когда настроен часовой пояс.

  1. Мы можем настроить часовой пояс для 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 объект. Это хорошо.

  1. Теперь я хочу то же самое для своего часового пояса:
 $ 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 .