我必须创建一个包含24个day
的月份列表,并正确处理没有29、30或31天的月份。
我目前要做的是:
def dates_list(first_month, assigned_day)
(0...24).map do |period|
begin
(first_month + period.months).change(day: assigned_day)
rescue ArgumentError
(first_month + period.months).end_of_month
end
end
end
在某些情况下,我需要从ArgumentError
营救下来:
Date.parse('10-Feb-2019').change(day: 30)
# => ArgumentError: invalid date
我正在寻找可能已经存在于红宝石或铁轨中的 safe 和 elegant 解决方案。像这样:
Date.parse('10-Feb-2019').safe_change(day: 30) # => 28-Feb-2019
所以我可以写:
def dates_list(first_month, assigned_day)
(0...24).map do |period|
(first_month + period.months).safe_change(day: assigned_day)
end
end
存在吗,或者我需要猴子打Date
?
非常欢迎变通方法(例如已经创建此列表的方法)。
答案 0 :(得分:2)
UPDATE
关于如何处理负数和0天的讨论,使我意识到此功能试图猜测用户的意图。而且还可以硬编码要生成多少个月以及要按月生成。
这让我开始思考这种方法在做什么?它会生成一个固定大小的前进月份列表,并以固定方式对其进行修改,然后猜测用户的需求。如果您的功能说明包含“ 和”,则可能需要多个功能。我们将生成日期列表与修改列表分开。我们用参数替换硬编码部分。我们不用猜测用户想要什么,而是让他们告诉我们。
def date_generator(from, by:, how_many:)
(0...how_many).map do |period|
date = from + period.send(by)
yield date
end
end
用户可以非常明确地了解他们想要更改的内容。用户和读者都不会感到惊讶。
p date_generator(Date.parse('2019-02-01'), by: :month, how_many: 24) { |month|
month.change(day: month.end_of_month.day)
}
我们可以通过将其变成Enumerator来更进一步。然后,您可以拥有任意数量的对象,并使用普通的Enumerable methods对它们进行任何操作。.
INFINITY = 1.0/0.0
def date_iterator(from, by:)
Enumerator.new do |block|
(0..INFINITY).each do |period|
date = from + period.send(by)
block << date
end
end
end
p date_iterator(Date.parse('2019-02-01'), by: :month)
.take(24).map { |date|
date.change(day: date.end_of_month.day)
}
现在,您可以生成任何日期列表,可以按任意字段,任意长度,进行任何更改进行迭代。对于读者来说,发生的事情不是隐藏在方法中,而是非常明显的。而且,如果您有特殊的普通情况,可以将其包装在方法中。
最后一步是使其成为Date
方法。
class Date
INFINITY = 1.0/0.0
def iterator(by:)
Enumerator.new do |block|
(0..INFINITY).each do |period|
date = self + period.send(by)
block << date
end
end
end
end
Date.parse('2019-02-01')
.iterator(by: :month)
.take(24).map { |date|
date.change(day: date.end_of_month.day)
}
如果您有一个特殊的普通案例,则可以为其编写一个特殊案例函数,为其指定一个描述性名称,并记录其特殊行为。
def next_two_years_of_months(date, day:)
if day <= 0
raise ArgumentError, "The day must be positive"
end
date.iterator(by: :month)
.take(24)
.map { |next_date|
next_date.change(day: [day, next_date.end_of_month.day].min)
}
end
上级答案
我的第一个重构就是删除冗余代码。
require 'date'
def dates_list(first_month, assigned_day)
(0...24).map do |period|
next_month = first_month + period.months
begin
next_month.change(day: assigned_day)
rescue ArgumentError
next_month.end_of_month
end
end
end
在这一点上,imo,功能很好。很明显发生了什么事。但是您可以更进一步。
def dates_list(first_month, assigned_day)
(0...24).map do |period|
next_month = first_month + period.months
day = [assigned_day, next_month.end_of_month.day].min
next_month.change(day: day)
end
end
我认为这要好得多。它使决策更加明确,并且不会覆盖其他可能的参数错误。
如果您发现自己经常这样做,则可以将其添加为Date
方法。
class Date
def change_day(day)
change(day: [day, end_of_month.day].min)
end
end
我对change_day
和safe_change
都不太感兴趣。并没有真正说“会用到这一天,或者如果它超出了本月的最后一天”,我不确定如何表达。