我必须使用 Ruby 在两个日期之间按顺序获取所有月份的名称。例如,我想得到:
['jan','feb','mars']
当我在以下之间进行区分时:
1st january and March 15th
我也希望它在日期差异大于一年时工作。
任何的想法?
我必须使用 Ruby 在两个日期之间按顺序获取所有月份的名称。例如,我想得到:
['jan','feb','mars']
当我在以下之间进行区分时:
1st january and March 15th
我也希望它在日期差异大于一年时工作。
任何的想法?
我会去:
d1 = Date.parse('jan 1 2011')
d2 = Date.parse('dec 31 2012')
(d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ Date.strptime(m, '%Y%m').mon ] }
=> ["Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"]
或者:
(d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ m[/\d\d$/ ].to_i ] }
这可能会快一点。
问题是年份边界。您必须跟踪年份和月份,而不仅仅是月份,否则在uniq
用于删除日期时,您将删除所有重复的月份索引。我选择了YYYYMM
格式,以获得正确的粒度。
require 'benchmark'
require 'date'
d1 = Date.parse('jan 1 2011')
d2 = Date.parse('dec 31 2012')
n = 100
Benchmark.bm(8) do |x|
x.report('strptime') { n.times { (d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ Date.strptime(m, '%Y%m').mon ] } } }
x.report('regex') { n.times { (d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ m[/\\d\\d$/ ].to_i ] } } }
end
user system total real
strptime 3.060000 0.020000 3.080000 ( 3.076614)
regex 2.820000 0.010000 2.830000 ( 2.829366)
编辑:
让我们让它变得更有趣。
我有一些代码气味一直困扰着我。我不喜欢使用Date.strftime
and Date.strptime
,所以我再次运行了这个问题:这里有两个运行得更快的解决方案,以及基准测试:
require 'benchmark'
require 'date'
def regex_months_between(d1, d2)
d1, d2 = [d1, d2].map{ |d| Date.parse(d) }.minmax
(d1..d2).map{ |m| m.strftime('%Y%m') }.uniq.map{ |m| Date::ABBR_MONTHNAMES[ m[/\d\d$/ ].to_i ] }
end
def months_between1(d1, d2)
d1, d2 = [d1, d2].map{ |d| Date.parse(d) }.minmax
months = (d2.mon - d1.mon) + (d2.year - d1.year) * 12
month_names = []
months.times{ |m|
month_names << Date::ABBR_MONTHNAMES[(d1 >> m).mon]
}
month_names << Date::ABBR_MONTHNAMES[d2.mon]
month_names
end
def months_between2(d1, d2)
d1, d2 = [d1, d2].map{ |d| Date.parse(d) }.minmax
months = (d2.mon - d1.mon) + (d2.year - d1.year) * 12
(d1.mon ... (d1.mon + months)).each_with_object(Date::ABBR_MONTHNAMES[d1.mon, 1]) { |month_offset, month_names_array|
month_names_array << Date::ABBR_MONTHNAMES[(d1 >> month_offset).mon]
}
end
puts regex_months_between('jan 1 2011', 'dec 31 2012').join(', ')
puts months_between1('jan 1 2011', 'dec 31 2012').join(', ')
puts months_between2('jan 1 2011', 'dec 31 2012').join(', ')
n = 100
Benchmark.bm(3) do |b|
b.report('rmb') { n.times { regex_months_between('jan 1 2011', 'dec 31 2012') } }
b.report('mb1') { n.times { months_between1('jan 1 2011', 'dec 31 2012') } }
b.report('mb2') { n.times { months_between2('jan 1 2011', 'dec 31 2012') } }
end
输出看起来像:
Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
user system total real
rmb 2.810000 0.010000 2.820000 ( 2.820732)
mb1 0.060000 0.000000 0.060000 ( 0.057763)
mb2 0.060000 0.000000 0.060000 ( 0.057112)
有趣的。” rmb
“现在已经远远落后了。将其从测试中拉出来并将循环提高 100 倍:
n = 10_000
Benchmark.bm(3) do |b|
b.report('mb1') { n.times { months_between1('jan 1 2011', 'dec 31 2012') } }
b.report('mb2') { n.times { months_between2('jan 1 2011', 'dec 31 2012') } }
end
这使:
user system total real
mb1 5.570000 0.060000 5.630000 ( 5.615789)
mb2 5.570000 0.040000 5.610000 ( 5.611323)
这基本上是两种获取月份的新方法之间的联系。作为肛门,我会去,mb2
因为如果我这样做数百万次会快一点,但你的里程可能会有所不同。
因此,假设您有两个日期,d1
并且d2
:
> d1 = 2.years.ago
=> Sun, 19 Sep 2010 15:51:18 CDT -05:00
> d2 = 1.month.ago
=> Sun, 19 Aug 2012 15:51:25 CDT -05:00
(在这种情况下,它们是 ActiveSupport::TimeWithZone 对象,但这与 DateTime 或 what-have-you 一样有效)
制作一个数组来包含您的输出:
m = []
然后在增加月份的同时用月份缩写(使用.strftime
)填充它:
while d1 <= d2.at_end_of_month
m << d1.strftime('%b')
d1 = d1 + 1.month
end
它就是:
> m
=> ["Sep", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug"]
我建议使用数组。
这样的事情应该这样做:
months = ['jan', 'feb', 'mar', etc...]
然后,您可以简单地从下个月迭代到上个月来创建您的列表。或者像这样使用对象的索引:
months[months.index('jan')..months.index('mar')]
这是我为解决此问题而写的一种方法。这是为处理哈希数据而设计的,例如:{Sun, 01 Jan 2012=>58, Wed, 01 Feb 2012=>0, Thu, 01 Mar 2012=>0} 但可以很容易地修改为数组数据。
见:https ://github.com/StephenOTT/add_missing_dates_ruby
但关键的代码是:
def addMissingMonths (datesHash)
count = 0
result = {}
datesHash.keys.each do |x|
if x != datesHash.keys.last
(x+1.month).upto(datesHash.keys[count+1]-1.month) do |a|
result[a.at_beginning_of_month] = 0
end
end
count += 1
end
return result.merge!(datesHash)
end
要看的关键内容是:(x+1.month).upto(datesHash.keys[count+1]-1.month)
(start_date..end_date).map{|m| m.beginning_of_month}.uniq.map{|m| Date::ABBR_MONTHNAMES[m.month]}
使用 Natural Language Date Parser = the Chronic Gem将日期转换为 Ruby 日期。然后区分这两个日期。