6

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

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

当我在以下之间进行区分时:

1st january and March 15th

我也希望它在日期差异大于一年时工作。

任何的想法?

4

6 回答 6

11

我会去:

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

于 2012-09-19T22:37:21.720 回答
4

因此,假设您有两个日期,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"]
于 2012-09-19T21:05:40.797 回答
0

我建议使用数组。

这样的事情应该这样做:

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

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

months[months.index('jan')..months.index('mar')]
于 2012-09-19T20:27:55.280 回答
0

这是我为解决此问题而写的一种方法。这是为处理哈希数据而设计的,例如:{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)

于 2013-09-21T04:28:11.833 回答
-1
(start_date..end_date).map{|m| m.beginning_of_month}.uniq.map{|m| Date::ABBR_MONTHNAMES[m.month]}
于 2012-09-19T21:11:32.990 回答
-2

使用 Natural Language Date Parser = the Chronic Gem将日期转换为 Ruby 日期。然后区分这两个日期。

于 2012-09-19T22:18:21.887 回答