获取两个日期之间的月份名称

时间:2012-09-19 20:24:04

标签: ruby-on-rails ruby date

我必须使用Ruby在两个日期之间按顺序获取所有月份名称。例如,我想得到:

['jan','feb','mars']

当我在两者之间做差异时:

1st january and March 15th

当日期差异大于一年时,我也希望它能够正常工作。

有什么想法吗?

6 个答案:

答案 0 :(得分:10)

我会选择:

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.strftimeDate.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”现在正在落后。从测试中拉出它并使环100x:

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,因为如果我这样做数百万次会更快一些,但你的里程可能会有所不同。

答案 1 :(得分:3)

所以,假设您有两个日期d1d2

> 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或者你有什么一样好用)

创建一个包含输出的数组:

 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"]

答案 2 :(得分:0)

我建议使用数组。

这样的事情应该这样做:

months = ['jan', 'feb', 'mar', etc...]

然后您可以简单地从较低月份迭代到较高月份来创建列表。或者像这样使用对象的索引:

months[months.index('jan')..months.index('mar')]

答案 3 :(得分:0)

这是我写一篇解决这个问题的方法。 这是为处理哈希数据而设计的,例如: {Sun,2012年1月1日=&gt; 58,Wed,2012年2月1日=&gt; 0,Thu,2012年3月1日=&gt; 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)

答案 4 :(得分:-2)

(start_date..end_date).map{|m| m.beginning_of_month}.uniq.map{|m| Date::ABBR_MONTHNAMES[m.month]}

答案 5 :(得分:-2)

使用自然语言日期解析器= the Chronic Gem将日期转换为Ruby日期。然后将两个日期区分开来。