Ruby:创建日期范围
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19093487/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Ruby: Create range of dates
提问by Stefan
I'm looking for an elegant way to make a range of datetimes, e.g.:
我正在寻找一种优雅的方式来制作一系列日期时间,例如:
def DateRange(start_time, end_time, period)
...
end
>> results = DateRange(DateTime.new(2013,10,10,12), DateTime.new(2013,10,10,14), :hourly)
>> puts results
2013-10-10:12:00:00
2013-10-10:13:00:00
2013-10-10:14:00:00
The step should be configurable, e.g. hourly, daily, monthly.
该步骤应该是可配置的,例如每小时、每天、每月。
I'd like timesto be inclusive, i.e. include end_time.
我想times具有包容性,即 include end_time。
Additional requirements are:
附加要求是:
- Original timezone should be preserved, i.e. if it's different from the local timezone, it should still be maintained.
- Should use proper advance methods, e.g. Rails
:advance, to handle things like variable number of days in months. - Ideally performance will be good, but that's not a primary requirement.
- 应保留原始时区,即如果它与本地时区不同,则仍应保留。
- 应该使用适当的高级方法,例如 Rails
:advance,来处理诸如以月为单位的可变天数之类的事情。 - 理想情况下,性能会很好,但这不是主要要求。
Is there an elegant solution?
有没有优雅的解决方案?
回答by Dan Nguyen
Using @CaptainPete's base, I modified it to use the ActiveSupport::DateTime#advancecall. The difference comes into effect when the time intervals are non-uniform, such as `:month" and ":year"
使用@CaptainPete 的基础,我修改了它以使用ActiveSupport::DateTime#advance调用。当时间间隔不均匀时,差异才会生效,例如`:month" 和 ":year"
require 'active_support/all'
class RailsDateRange < Range
# step is similar to DateTime#advance argument
def every(step, &block)
c_time = self.begin.to_datetime
finish_time = self.end.to_datetime
foo_compare = self.exclude_end? ? :< : :<=
arr = []
while c_time.send( foo_compare, finish_time) do
arr << c_time
c_time = c_time.advance(step)
end
return arr
end
end
# Convenience method
def RailsDateRange(range)
RailsDateRange.new(range.begin, range.end, range.exclude_end?)
end
My method also returns an Array. For comparison's sake, I altered @CaptainPete's answer to also return an array:
我的方法也返回一个Array. 为了比较起见,我修改了@CaptainPete 的答案以返回一个数组:
By hour
按小时
RailsDateRange((4.years.ago)..Time.now).every(years: 1)
=> [Tue, 13 Oct 2009 11:30:07 -0400,
Wed, 13 Oct 2010 11:30:07 -0400,
Thu, 13 Oct 2011 11:30:07 -0400,
Sat, 13 Oct 2012 11:30:07 -0400,
Sun, 13 Oct 2013 11:30:07 -0400]
DateRange((4.years.ago)..Time.now).every(1.year)
=> [2009-10-13 11:30:07 -0400,
2010-10-13 17:30:07 -0400,
2011-10-13 23:30:07 -0400,
2012-10-13 05:30:07 -0400,
2013-10-13 11:30:07 -0400]
By month
按月
RailsDateRange((5.months.ago)..Time.now).every(months: 1)
=> [Mon, 13 May 2013 11:31:55 -0400,
Thu, 13 Jun 2013 11:31:55 -0400,
Sat, 13 Jul 2013 11:31:55 -0400,
Tue, 13 Aug 2013 11:31:55 -0400,
Fri, 13 Sep 2013 11:31:55 -0400,
Sun, 13 Oct 2013 11:31:55 -0400]
DateRange((5.months.ago)..Time.now).every(1.month)
=> [2013-05-13 11:31:55 -0400,
2013-06-12 11:31:55 -0400,
2013-07-12 11:31:55 -0400,
2013-08-11 11:31:55 -0400,
2013-09-10 11:31:55 -0400,
2013-10-10 11:31:55 -0400]
By year
按年份
RailsDateRange((4.years.ago)..Time.now).every(years: 1)
=> [Tue, 13 Oct 2009 11:30:07 -0400,
Wed, 13 Oct 2010 11:30:07 -0400,
Thu, 13 Oct 2011 11:30:07 -0400,
Sat, 13 Oct 2012 11:30:07 -0400,
Sun, 13 Oct 2013 11:30:07 -0400]
DateRange((4.years.ago)..Time.now).every(1.year)
=> [2009-10-13 11:30:07 -0400,
2010-10-13 17:30:07 -0400,
2011-10-13 23:30:07 -0400,
2012-10-13 05:30:07 -0400,
2013-10-13 11:30:07 -0400]
回答by kwarrick
No rounding errors, a Rangecalls the .succmethod to enumerate the sequence, which is not what you want.
没有舍入错误,aRange调用.succ方法枚举序列,这不是你想要的。
Not a one-liner but, a short helper function will suffice:
不是单行的,但是一个简短的辅助函数就足够了:
def datetime_sequence(start, stop, step)
dates = [start]
while dates.last < (stop - step)
dates << (dates.last + step)
end
return dates
end
datetime_sequence(DateTime.now, DateTime.now + 1.day, 1.hour)
# [Mon, 30 Sep 2013 08:28:38 -0400, Mon, 30 Sep 2013 09:28:38 -0400, ...]
Note, however, this could be wildly inefficient memory-wise for large ranges.
但是请注意,对于大范围,这在内存方面可能非常低效。
Alternatively, you can use seconds since the epoch:
或者,您可以使用自纪元以来的秒数:
start = DateTime.now
stop = DateTime.now + 1.day
(start.to_i..stop.to_i).step(1.hour)
# => #<Enumerator: 1380545483..1380631883:step(3600 seconds)>
You'll have a range of integers, but you can convert back to a DateTimeeasily:
您将拥有一系列整数,但您可以DateTime轻松转换回:
Time.at(i).to_datetime
回答by captainpete
Add duration support to Range#step
添加持续时间支持 Range#step
module RangeWithStepTime
def step(step_size = 1, &block)
return to_enum(:step, step_size) unless block_given?
# Defer to Range for steps other than durations on times
return super unless step_size.kind_of? ActiveSupport::Duration
# Advance through time using steps
time = self.begin
op = exclude_end? ? :< : :<=
while time.send(op, self.end)
yield time
time = step_size.parts.inject(time) { |t, (type, number)| t.advance(type => number) }
end
self
end
end
Range.prepend(RangeWithStepTime)
This approach affords
这种方法提供
- Implicit support for preserving time-zone
- Adds duration support to the already-present
Range#stepmethod (no need for a sub-class, or convenience methods onObject, though that was still fun) - Supports multi-part durations like
1.hour + 3.secondsinstep_size
- 隐式支持保留时区
- 为已经存在的
Range#step方法添加持续时间支持(不需要子类或 上的便利方法Object,尽管这仍然很有趣) - 支持多部分持续时间像
1.hour + 3.seconds在step_size
This adds support for our duration to Range using the existing API. It allows you to use a regular range in the style that we expect to simply "just work".
这增加了对使用现有 API 的 Range 持续时间的支持。它允许您以我们期望简单地“正常工作”的样式使用常规范围。
# Now the question's invocation becomes even
# simpler and more flexible
step = 2.months + 4.days + 22.3.seconds
( Time.now .. 7.months.from_now ).step(step) do |time|
puts "It's #{time} (#{time.to_f})"
end
# It's 2013-10-17 13:25:07 +1100 (1381976707.275407)
# It's 2013-12-21 13:25:29 +1100 (1387592729.575407)
# It's 2014-02-25 13:25:51 +1100 (1393295151.8754072)
# It's 2014-04-29 13:26:14 +1000 (1398741974.1754072)
The previous approach
以前的做法
...was to add an #everyusing a DateRange < Rangeclass + DateRange"constructor" on Object, then convert the times to integers internally, stepping through them in stepseconds. This didn't work for time zones originally. Support for time zones was added but then another issue was found with the fact some step durations are dynamic (eg 1.month).
...是#every使用DateRange < Range类+ DateRange“构造函数”添加一个on Object,然后在内部将时间转换为整数,在step几秒钟内逐步完成它们。这最初不适用于时区。添加了对时区的支持,但随后发现了另一个问题,因为某些步骤持续时间是动态的(例如1.month)。
Reading Rubinius' Rangeimplementationit became clear how someone might add support for ActiveSupport::Duration; so the approach was rewritten. Much thanks to Dan Nguyen for the #advancetip and debugging around this, and to Rubinius' implementation of Range#stepfor being beautifully written :D
阅读Rubinius 的Range实现,很明显有人可能会添加对ActiveSupport::Duration; 的支持。所以这个方法被重写了。非常感谢 Dan Nguyen 提供的#advance提示和调试,以及 Rubinius 的实现,Range#step因为写得很漂亮:D
Update 2015-08-14
更新 2015-08-14
This patch was notmerged into Rails/ActiveSupport. You should stick to
forloops using#advance. If you're gettingcan't iterate from Timeor something like that, then use this patch, or just avoid usingRange.Updated patch to reflect Rails 4+
prependstyle overalias_method_chain.
这个补丁是不能合并到Rails /的ActiveSupport。你应该坚持
for使用循环#advance。如果你得到can't iterate from Time或类似的东西,然后使用这个补丁,或者只是避免使用Range.更新补丁以反映 Rails 4+
prepend风格alias_method_chain。
回答by xxjjnn
If you want to have nvalues evenly spread between two dates you can
如果您想让n值在两个日期之间均匀分布,您可以
n = 8
beginning = Time.now - 1.day
ending = Time.now
diff = ending - beginning
result = []
(n).times.each do | x |
result << (beginning + ((x*diff)/(n-1)).seconds)
end
result
which gives
这使
2015-05-20 16:20:23 +0100
2015-05-20 19:46:05 +0100
2015-05-20 23:11:48 +0100
2015-05-21 02:37:31 +0100
2015-05-21 06:03:14 +0100
2015-05-21 09:28:57 +0100
2015-05-21 12:54:40 +0100
2015-05-21 16:20:23 +0100
回答by Darkside
Another solution is to use uniqmethod. Consider examples:
另一种解决方法是使用uniq方法。考虑示例:
date_range = (Date.parse('2019-01-05')..Date.parse('2019-03-01'))
date_range.uniq { |d| d.month }
# => [Sat, 05 Jan 2019, Fri, 01 Feb 2019]
date_range.uniq { |d| d.cweek }
# => [Sat, 05 Jan 2019, Mon, 07 Jan 2019, Mon, 14 Jan 2019, Mon, 21 Jan 2019, Mon, 28 Jan 2019, Mon, 04 Feb 2019, Mon, 11 Feb 2019, Mon, 18 Feb 2019, Mon, 25 Feb 2019]
Note that this approach respects range min and max
请注意,此方法尊重范围最小值和最大值
回答by Kevin Monk
The Ice Cube - Ruby Date Recurrence Librarywould help here or look at their implementation? It has successfully covered every example in the RFC 5545spec.
The Ice Cube - Ruby Date Recurrence Library会在这里有所帮助还是看看它们的实现?它成功地涵盖了RFC 5545规范中的每个示例。
schedule = IceCube::Schedule.new(2013,10,10,12)
schedule.add_recurrence_rule IceCube::Rule.hourly.until(Time.new(2013,10,10,14))
schedule.all_occurrences
# => [
# [0] 2013-10-10 12:00:00 +0100,
# [1] 2013-10-10 13:00:00 +0100,
# [2] 2013-10-10 14:00:00 +0100
#]
回答by user401093
You can use DateTime.parseon the start and end times to get a lock on the iterations you need to populate the array. For instance;
您可以使用DateTime.parse开始和结束时间来锁定填充数组所需的迭代。例如;
#Seconds
((DateTime.parse(@startdt) - DateTime.now) * 24 * 60 * 60).to_i.abs
#Minutes
((DateTime.parse(@startdt) - DateTime.now) * 24 * 60).to_i.abs
and so on. Once you have these values, you can loop through populating the array on whatever slice of time you want. I agree with @fotanus though, you probably shouldn't need to materialize an array for this, but I don't know what your goal is in doing so so I really can't say.
等等。一旦你有了这些值,你就可以在你想要的任何时间片上循环填充数组。不过我同意@fotanus,你可能不需要为此具体化一个数组,但我不知道你这样做的目标是什么,所以我真的不能说。
回答by steenslag
An hour is 1/24th of a day, so you could do
一小时是一天的 1/24,所以你可以这样做
d1 = DateTime.now
d2 = d1 + 1
d1.step(d2, 1/24r){|d| p d}
1/24ris a Rational, more exact than a Float.
1/24r是一个 Rational,比 Float 更精确。

