【问题标题】:Ruby returns invalid date (wrong DoW) when parsing date stringRuby 在解析日期字符串时返回无效日期(错误的 DoW)
【发布时间】:2016-12-05 23:35:45
【问题描述】:

我正在使用一个 API,它返回一个引用 2016 年第 53 周的日期字符串。

我发现解析它时唯一有效的日历是JULIAN

但是,Date.parse("2016-W53", true, Date::JULIAN) 返回 Mon, 27 Dec 2016,这是错误的日期,因为它应该是 Mon, 26 Dec 2016

为什么会这样?
我怎样才能让它返回正确的日期?

【问题讨论】:

    标签: ruby date time


    【解决方案1】:

    儒略历是完全不同的历法;在儒略历上,12/27 星期一,因为在儒略历上,2016 年 1 月 1 日是星期四,而在公历上是星期五。

    通常,我会说使用Date.strptime('2016-W53','%G-W%V')... 但是这似乎不起作用。根据文档,在计算一年中的周数时,Ruby 使用基于 ISO 8601 周数的年和周数:

    基于 ISO 8601 周的年份和周数:
    YYYY 的第 1 周从星期一开始,包括 YYYY-01-04。
    第一周前一年的日子是在最后一周 前一年。
    %G - 基于周的年份
    %V - 基于周的年份的周数 (01..53)
    %g - 基于周的年份的最后 2 位数字 (00..99)

    如上所述,该系统中一年的第一周是包含 1 月 4 日的那一周;在儒略历中,4 日星期日包含在 1 月 1 日所在的同一周内;但是,在公历中,第 4 个是星期一,因此这标志着第一周“算作” 2016 年的一部分 - 前一周是 2015 年的第 53 周。

    这样做的原因是(大概)不应将一周视为 2016 年的第一周和 2015 年的最后一周;因为如果你想要一段时间,比如 10 周,你最终会计算两次。选择 1 月 4 日作为标记的原因大概是因为如果 1 月 1 日这一周包括 1 月 4 日,那么这 7 天的大部分时间都在新年,所以这一周算作新年的一部分。

    鉴于上述情况,2016 年没有第 53 周,至少根据 ISO 计算周数的规则。您可以实现自己的系统来确定日期的含义,但是您必须询问您正在使用的 API 的提供者他们是如何生成这些数字的,否则您将不知道您是否正确解释了它.

    【讨论】:

      【解决方案2】:

      您不应使用 Julian calendar 解释日期,因为它不能纠正我们现在在西方国家使用的较新的 Gregorian calendar 中说明的周期性“漂移”。

      如果输入有效,Date.parse 应该可以工作,但看起来你的输入无效:

      Date.parse("2016-W53")
      # ArgumentError: invalid date
      Date.parse("2020-W53")
      # <Date: 2020-12-28 ((2459212j,0s,0n),+0s,2299161j)>
      

      我想说,如果标准库中的默认 parse 方法无法理解该值,您应该编写自己的解析器。

      def custom_parse(str)
        regex = /\A(?<year>\d{4})-W(?<week>\d{2})\z/
        if data = regex.match(str)
          year = data[:year].to_i
          week = data[:week].to_i
          Date.commercial(year, week - 1) + 7
        end
      end
      
      custom_parse("2016-W53")
      # <Date: 2017-01-02 ((2457756j,0s,0n),+0s,2299161j)>
      

      【讨论】:

        【解决方案3】:

        关于上周根据Wikipedia

        如果 12 月 31 日是星期一、星期二或星期三,则它是在下一年的第 1 周。如果是星期四,则在刚刚结束的一年的第 53 周;如果在星期五是第 52 周(如果刚刚结束的年份是闰年,则为第 53 周);如果在星期六或星期日,则在刚刚结束的一年中的第 52 周。

        和:

        里面有 12 月 28 日。因此,最早的上周是从 12 月 21 日星期一到 1 月 28 日星期日,最晚的上周是从 12 月 28 日星期一到 1 月 3 日星期日(下一个公历年)。

        因此,如果您想查看一年中有多少周,您可以请求YYYY-12-28 的周数。如果您在 Ruby 中执行此操作,它将如下所示:

        2014.upto(2016) do |i|
          date = Date.new(i, 12, 28)
          puts "#{date} - in week #{date.cweek}"
        end
        
        # 2014-12-28 - in week 52
        # 2015-12-28 - in week 53
        # 2016-12-28 - in week 52
        

        如您所见,2016 年没有 53 周,因此您得到的日期可能是错误的。我建议联系您的 API 提供商,因为我认为推出您自己的方法来处理这种情况并不明智。

        如果您需要推出自己的解决方案,您可以使用如下所示的内容(来自 tompave 示例的正则表达式):

        class MyCustomDateParser
          FORMAT = /\A(?<year>\d{4})-W(?<week>\d{2})\z/
        
          def self.parse!(date)
            raise ArgumentError, "invalid date" unless m = date.match(FORMAT)
            Date.parse(date)
          rescue ArgumentError => e
            fail e unless m[2] == "53"
            Date.parse("#{m[1]}-W52")
          end
        end
        

        【讨论】:

          猜你喜欢
          • 2023-03-16
          • 1970-01-01
          • 2017-08-28
          • 1970-01-01
          • 2014-02-18
          • 2012-07-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多