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

Posted

技术标签:

【中文标题】Alpine Linux 上的 Rails ActiveSupport 时间和时区错误【英文标题】:Rails ActiveSupport time and timezone error on Alpine Linux 【发布时间】:2021-02-11 07:32:58 【问题描述】:

我不知道我是不是在做傻事,所以请耐心等待。

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

重现步骤:

    在 Docker Alpine Linux Ruby 映像中启动 shell:
$ 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 -e 'puts Time.now.inspect'
2020-10-28 20:34:24.4817918 +0000

看起来不错。时间以 UTC 打印。

    除了 UTC,标准 ruby​​ Time 类只能处理当地时间。这取决于本地系统时区,可以使用操作系统配置机制进行配置,或者可以简单地使用 TZ env var 将其传递给 Ruby。让我们尝试使用我的时区“欧洲/柏林”:
$ TZ="Europe/Berlin" ruby -e 'puts Time.now.inspect'
2020-10-28 21:39:22.7037648 +0100

看起来不错。柏林时区是冬季的 UTC+01(标准)或夏季的 UTC+02(DST)。在写这篇文章的时候,我们是冬天,所以+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 不同。

    我们可以使用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 对象。这很好。

    现在我希望我的时区也一样:
$ 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 中的错误或其与时区数据的连接?我做错了吗?

【问题讨论】:

欢迎来到 SO!恭喜你提出了一个写得很棒的问题。 哦,只是为了避免在这个方向上出现任何问题:当然,我已经检查了第 8 步是否正确运行 - 它产生了所需的输出 - 在非 Alpine 图像上。 不仅#current 方法受到影响,ActiveSupport 的所有时区相关方法都受到影响,例如Time.zone.atTime.zone.parse(如 ruby -e 'require "active_support/all"; Time.zone = "Europe/Berlin"; puts Time.zone.at(Time.now.to_i).inspect')。 据我所知 ActiveSupport 使用 TZInfo 作为其信息来源。您可能希望将注意力转移到该库以更接近问题的根源。 看起来是时候在 Active Report repo 上提交错误报告了 【参考方案1】:

原来这是一个影响tzinfo gem(版本< 1.2.8 / < 2.0.3)的问题 - 它是incompatible with timezone-data 2020b(以后?)和Alpine ship with 2020c 的最新版本。文件格式从“fat”更改为“slim”,这与 tzinfo gem 尚不兼容。

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

现在已在发行版的tzinfo gem 中添加了对“slim”格式 zoneinfo 文件的支持:

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

【讨论】:

在 v1.2.8 和 v2.0.3 版本中的 tzinfo 现已添加对“slim”格式 zoneinfo 文件的支持。

以上是关于Alpine Linux 上的 Rails ActiveSupport 时间和时区错误的主要内容,如果未能解决你的问题,请参考以下文章

sh 在Alpine Linux 3.4上的PHP 7.1.7中打破了strftime()%P修饰符

无法在 Alpine 上的多架构构建中安装 bash

gdb调试Alpine Linux上的OpenJDK java失败了“线程接收信号?,未知信号”

Go 编译的二进制文件不会在 Ubuntu 主机上的 alpine docker 容器中运行

linux12企业实战 -- 06alpine Linux简介alpine制作jdk镜像jre镜像 Alpine 命令

alpine linux怎么安装文件