我正在寻找一种优雅的方式来制作一系列日期时间,例如:
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
该步骤应该是可配置的,例如每小时,每天,每月。
我希望times
具有包容性,即包含end_time
。
其他要求是:
:advance
,用于处理几个月内可变天数等事情。有优雅的解决方案吗?
答案 0 :(得分:24)
使用@ CaptainPete的基础,我修改它以使用ActiveSupport::DateTime#advance
调用。当时间间隔不均匀时,例如“:月”和“:年”
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
我的方法也返回Array
。为了比较,我改变了@ CaptainPete的答案也返回了一个数组:
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]
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]
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]
答案 1 :(得分:14)
没有舍入错误,Range
调用.succ
方法来枚举序列,这不是您想要的。
不是单行,但短辅助功能就足够了:
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, ...]
但是,请注意,对于大范围而言,这可能会非常低效。
或者,您可以使用自纪元以来的秒数:
start = DateTime.now
stop = DateTime.now + 1.day
(start.to_i..stop.to_i).step(1.hour)
# => #<Enumerator: 1380545483..1380631883:step(3600 seconds)>
您将拥有一系列整数,但您可以轻松转换回DateTime
:
Time.at(i).to_datetime
答案 2 :(得分:14)
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)
Range#step
方法添加持续时间支持(不需要子类或Object
上的便捷方法,但这仍然很有趣)1.hour + 3.seconds
step_size
等多部分持续时间
这增加了使用现有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)
...是在#every
上使用DateRange < Range
类+ DateRange
“构造函数”添加Object
,然后在内部将时间转换为整数,逐步执行在step
秒内。这最初不适用于时区。增加了对时区的支持,但随后发现另一个问题,即一些步骤持续时间是动态的(例如1.month
)。
阅读Rubinius' Range
implementation很明显有人可能会为ActiveSupport::Duration
添加支持;所以这个方法被改写了。非常感谢Dan Nguyen提出的#advance
提示和调试,以及Rubinius对Range#step
的实现,因为写得很漂亮:D
此补丁was not merged into Rails/ActiveSupport。您应该使用for
坚持#advance
循环。如果您收到can't iterate from Time
或类似的内容,请使用此修补程序,或者只是避免使用Range
。
更新了补丁,以反映prepend
上的Rails 4 + alias_method_chain
样式。
答案 3 :(得分:3)
如果您希望在两个日期之间均匀分布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
给出了
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
答案 4 :(得分:2)
您可以在开始和结束时间使用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
等等。获得这些值后,您可以循环遍历所需的任何时间片段。我同意@fotanus,你可能不需要为此实现数组,但我不知道你的目标是什么,所以我真的不能说。
答案 5 :(得分:2)
另一种解决方案是使用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]
请注意,此方法遵守最小和最大范围
答案 6 :(得分:1)
一个小时是一天的1/24,所以您可以这样做
d1 = DateTime.now
d2 = d1 + 1
d1.step(d2, 1/24r){|d| p d}
1/24r
是有理数,比浮点数更精确。