Rails Alpine 上的 ActiveSupport 时间和时区错误 Linux

Rails ActiveSupport time and timezone error on Alpine Linux

我不知道我是不是在做傻事,所以请多多包涵。

tl;dr Rails ActiveSupport 时间和时区似乎在 Alpine Linux 上有错误。当它应该使用冬季时间时,它使用我的时区的 DST 变体(夏季时间)。

重现步骤:

  1. 在 Docker Alpine Linux Ruby 图像中启动 shell:
$ 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,标准rubyTimeclass只能处理当地时间。这取决于本地系统时区,可以使用操作系统配置机制进行配置,或者可以使用 TZ env var 简单地传递给 Ruby。让我们尝试使用我的时区“Europe/Berlin”:
$ 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. 我们可以使用 Time.zone=(timezone_identifier)Time.use_zone(timezone_identifier) { "inside this block the timezone is used" } 为 ActiveSupport 配置时区。让我们尝试第一个变体:
$ 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 步的输出。在“Europe/Berlin”时区,我们目前有冬令时,UTC+01,也称为“CET”(中央欧洲时间)。但是这次在输出中时间戳被标记为“CEST”(中欧夏令时),即 UTC+02。

这是错误的。

哪里出错了?是在高山Linux吗?是标准的Ruby吗?但是在第 4 步中输出是正确的。或者是 ActiveSupport 中的错误或其与时区数据的连接?我做错了什么吗?

原来这是一个影响 tzinfo gem(版本 < 1.2.8 / < 2.0.3)的问题 - 它是 incompatible with timezone-data 2020b (onwards?), and recent versions of Alpine ship with 2020c。文件格式从 'fat' 更改为 'slim',这与 tzinfo gem 不兼容。

更新解决方案 (2020-11-09)

对“slim”格式 zoneinfo 文件的支持现已添加到 tzinfo gem 版本中:

  • v1.2.8
  • v2.0.3

原始Ruby解决方案

您可以使用 tzinfo-data gem 而不是依赖系统 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'格式的时区数据包(adapted from comment):

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 && \
    /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