【发布时间】: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。
为什么会这样?
我怎样才能让它返回正确的日期?
【问题讨论】:
我正在使用一个 API,它返回一个引用 2016 年第 53 周的日期字符串。
我发现解析它时唯一有效的日历是JULIAN。
但是,Date.parse("2016-W53", true, Date::JULIAN) 返回 Mon, 27 Dec 2016,这是错误的日期,因为它应该是 Mon, 26 Dec 2016。
为什么会这样?
我怎样才能让它返回正确的日期?
【问题讨论】:
儒略历是完全不同的历法;在儒略历上,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 的提供者他们是如何生成这些数字的,否则您将不知道您是否正确解释了它.
【讨论】:
您不应使用 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)>
【讨论】:
关于上周根据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
【讨论】: