【问题标题】:Ruby TZInfo, and ActiveSupport::TimeZone, fail to identify most timezone abbreviationsRuby TZInfo 和 ActiveSupport::TimeZone 无法识别大多数时区缩写
【发布时间】:2020-09-28 02:29:00
【问题描述】:

在存在 ActiveSupport 和 TZInfo 的 Ruby 中,我试图解析可以包含时区标识符的任意时间字符串。

以偏移量(例如,'-08:00')的形式处理时区是没有问题的。两种风格的长名称似乎都有效(例如,'America/Vancouver''Pacific Time (US & Canada)'),但常见的缩写以及它们的夏令时替代品大部分都失败了——除了少数例外。

查看 Wikipedia 上的 List of time zone abbreviations,有 190 个不同的缩写。当我使用TZInfo::Timezone.get(abbrev)(其中abbrev 是一个包含给定缩写字符串的变量,例如'MST')对它们中的每一个进行测试时,只识别出9 个:CET、EET、EST、GMT、HST、MET、MST 、UTC 和 WET。

例如:

> TZInfo::Timezone.get('MST')
 => #<TZInfo::DataTimezone: MST>
> TZInfo::Timezone.get('PST')
TZInfo::InvalidTimezoneIdentifier

那么:是否有一种方法可以将大部分或全部常见的 3-5 个字符的时区缩写转换为 TZInfo 或 ActiveSupport 时区?

或者我是否必须编写自己的转换助手,以便随着世界各地时区政策的变化而使转换表保持最新?

(我确实承认缩写不是 100% 可靠或权威的——尤其是在少数情况下,相同的缩写指的是具有不同偏移量的多个时区——但我仍然需要做一个“最好的猜测”而不是没有。)

【问题讨论】:

    标签: ruby activesupport tzinfo


    【解决方案1】:

    TZInfo 和 ActiveSupport 不支持通过缩写查找时区。 TZInfo::Timezone.get 可用的那些缩写实际上是 Time Zone Database 中旧时区的标识符。

    不过,您可以使用 TZInfo 构建自己的从缩写到时区标识符的映射。例如:

    # Consider abbreviations used in the current year.
    from = Time.utc(Time.now.utc.year)
    to = Time.utc(from.year + 1)
    
    # Build an array of [abbreviation, identifier] pairs.
    abbrev_identifiers = TZInfo::Timezone.all_data_zones.flat_map do |tz|
      abbrevs = tz.offsets_up_to(to, from).map {|o| o.abbreviation.to_s }.uniq
      abbrevs.map {|a| [a, tz.identifier] }
    end
    
    # Create a Hash using abbreviation as the key and an array of identifiers as the value.
    lookup = abbrev_identifiers.each.with_object(Hash.new {|h,k| h[k] = [] }) {|(a, i), h| h[a] << i }
    

    您现在可以使用lookup 来获取时区标识符(适合与TZInfo::Timezone.get 一起使用)。以 MST 为例:

    p lookup['MST']
    #=> ["America/Boise", "America/Cambridge_Bay", "America/Chihuahua", "America/Creston", "America/Dawson", "America/Dawson_Creek", "America/Denver", "America/Edmonton", "America/Fort_Nelson", "America/Hermosillo", "America/Inuvik", "America/Mazatlan", "America/Ojinaga", "America/Phoenix", "America/Whitehorse", "America/Yellowknife", "MST", "MST7MDT"]
    

    自 1970 年以来,这些时区中的每一个都会在某个时候使用不同的规则(至少在过去几年的时区数据库版本中)。如果您只对在较小窗口内处理时间感兴趣,那么您可以将查找过滤到仅在该窗口内具有不同规则的时区:

    current_year = lookup.map.with_object(Hash.new) do |(a, z), h|
      h[a] = z.uniq {|i| TZInfo::Timezone.get(i).transitions_up_to(to, from) }
    end
    
    p current_year['MST']
    #=> ["America/Boise", "America/Chihuahua", "America/Creston", "America/Dawson"]
    

    根据您的应用程序,您可能需要预先计算和存储查找。遍历时区将在进程的生命周期内将每个时区加载到内存中。

    请注意,时区数据库中使用并由 TZInfo 返回的缩写与您链接到的 List of time zone abbreviations Wikipedia 页面上的缩写不同。在很多情况下,没有标准的缩写,时区数据库和维基百科的作者将使用不同的来源和方法。

    【讨论】:

      【解决方案2】:

      tzinfo describes 它使用的数据来自哪里:

      • 包含时区定义文件的 zoneinfo 目录。这些文件是使用 zic 实用程序从 IANA 时区数据库生成的。大多数类 Unix 系统都包含 zoneinfo 目录。

      • TZInfo::Data 库(tzinfo-data gem)。 TZInfo::Data 包含一组同样从 IANA 时区数据库生成的 Ruby 模块。

      时区定义文件通常位于/usr/share/zoneinfo。在我的机器上,此目录中没有任何内容,例如 PMDT

      tzinfo-data 是here,也没有PMDT

      如果您找到一组合适的时区信息定义文件,其中包含您要查询的区域,tzinfo 理论上应该可以使用它们。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-04-27
        • 2015-10-31
        • 2015-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-16
        相关资源
        最近更新 更多