列出日期数组中缺失的月份

Listing missing months in an array of dates

我有一个数组中的事务列表。

=> [Wed, 23 Oct 2013, Mon, 18 Nov 2013, Fri, 22 Nov 2013, Mon, 13 Jan 2014, Tue, 28 Jan 2014, Mon, 03 Feb 2014, Mon, 10 Feb 2014, Tue, 18 Feb 2014, Fri, 07 Mar 2014, Mon, 31 Mar 2014, Mon, 07 Apr 2014, Tue, 10 Jun 2014, Mon, 30 Jun 2014, Mon, 22 Sep 2014, Mon, 06 Oct 2014, Fri, 14 Nov 2014, Tue, 18 Nov 2014, Fri, 26 Dec 2014, Thu, 15 Jan 2015, Mon, 23 Mar 2015, Mon, 20 Apr 2015]

我需要比较每笔交易的日期并列出月份和年份列表中缺少的任何月份。这是我现在拥有的...

@find_transactions = (@user.transactions.find_all { |t| (t.name 'name' })
@trans_dates = @find_transactions.map(&:date).sort!.map { |s| Date.strptime(s, '%Y-%m') }.each_cons(2).map{ |d1,d2| d1.next_month == d2 }

如果每个月都存在,此方法目前会给我一个 true 或 false,但我实际上需要让该方法打印缺少的月份列表。我想让它一起打印月份和年份。

这是此方法给我的响应...

=> [true, false, true, false, false, true, false, true, false, false, false, true, true]

我想要这样的回复...

=> [March 2015, December 2014, September 2014]

提前致谢!

这是您可以做到这一点的一种方式。

代码

require 'date'

def missing_months(dates)    
  a = dates.map { |s| d = Date.strptime(s, '%a, %d %b %Y'); d - d.day + 1 }
  (all_months_in_range(*a.minmax) -a).map { |d| d.strftime('%b %Y') } 
end

def all_months_in_range(f,l)
  (12*(l.year-f.year)+l.month-f.month+1).times.map do |i|
    y,m = (f.month+i).divmod(12)
    y += f.year
    (m=12; y-=1) if m ==0
    Date.new(y,m)
  end
end

例子

dates = ['Wed, 23 Oct 2013', 'Mon, 18 Nov 2013', 'Fri, 22 Nov 2013',
         'Fri, 14 Nov 2014', 'Tue, 18 Nov 2014', 'Fri, 26 Dec 2014',
         'Mon, 13 Jan 2014', 'Tue, 28 Jan 2014', 'Mon, 03 Feb 2014',
         'Mon, 31 Mar 2014', 'Mon, 07 Apr 2014', 'Tue, 10 Jun 2014',
         'Mon, 30 Jun 2014', 'Mon, 22 Sep 2014', 'Mon, 06 Oct 2014',
         'Mon, 10 Feb 2014', 'Tue, 18 Feb 2014', 'Fri, 07 Mar 2014',
         'Thu, 15 Jan 2015', 'Mon, 23 Mar 2015', 'Mon, 20 Apr 2015']

missing_months(dates)     
  #=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"] 

请注意 dates 不需要排序。

说明

对于上面的例子:

  a = dates.map { |s| d = Date.strptime(s, '%a, %d %b %Y'); d - d.day + 1 }
    #=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
    #    #<Date: 2013-11-01 ((2456598j,0s,0n),+0s,2299161j)>,
    #   ...
    #    #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>] 

请注意,每个日期都在每月的第一天。接下来,获取这些日期中的第一个和最后一个:

  f,l = a.minmax
  f #=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
  l #=>  #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>]

现在将 fl 传递给 all_months_in_range 以创建一个数组,其中包含 f 和 [=20] 之间每个月的第一天的日期对象=].

  b = all_months_in_range(f,l) 
    #=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
    #    #<Date: 2013-11-01 ((2456598j,0s,0n),+0s,2299161j)>,
    #    ...
    #    #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>] 

  b.size #=> 19 

我将跳过对这个辅助方法的解释,因为它非常简单。

计算数组 ba 之间的差异以获得缺失的月初日期:

  c = b-a
    #=> [#<Date: 2013-12-01 ((2456628j,0s,0n),+0s,2299161j)>,
    #    #<Date: 2014-05-01 ((2456779j,0s,0n),+0s,2299161j)>,
    #    #<Date: 2014-07-01 ((2456840j,0s,0n),+0s,2299161j)>,
    #    #<Date: 2014-08-01 ((2456871j,0s,0n),+0s,2299161j)>,
    #    #<Date: 2015-02-01 ((2457055j,0s,0n),+0s,2299161j)>] 

最后,将这些日期转换为所需的格式:

 c.map { |d| d.strftime('%b %Y') } 
   #=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]

附录:阅读@Sid 的回答后,我发现我可以通过使用 Date#next_month:

在我的辅助方法中为自己省去一些麻烦
def all_months_in_range(f,l)
  (12*(l.year-f.year)+l.month-f.month+1).times.map { |i| f.next_month(i) }
end

这不是很优雅,但它起作用了。我从您的原始代码开始,@SupremeA,并以此为基础。

require 'date'

dates = ['Wed, 23 Oct 2013', 'Mon, 18 Nov 2013', 'Fri, 22 Nov 2013', 'Mon, 13 Jan 2014', 'Tue, 28 Jan 2014', 'Mon, 03 Feb 2014', 'Mon, 10 Feb 2014', 'Tue, 18 Feb 2014', 'Fri, 07 Mar 2014', 'Mon, 31 Mar 2014', 'Mon, 07 Apr 2014', 'Tue, 10 Jun 2014', 'Mon, 30 Jun 2014', 'Mon, 22 Sep 2014', 'Mon, 06 Oct 2014', 'Fri, 14 Nov 2014', 'Tue, 18 Nov 2014', 'Fri, 26 Dec 2014', 'Thu, 15 Jan 2015', 'Mon, 23 Mar 2015', 'Mon, 20 Apr 2015']

new_dates = []
dates.each { |d| new_dates.push(Date.parse(d).strftime('%B %Y')) }
sorted_dates = new_dates.map { |s| Date.strptime(s, '%B %Y') }.sort.uniq
missing_months = []
sorted_dates.each_cons(2) do |d1,d2| 
  d = d1
  while d.next_month != d2 
    missing_months.push(d.next_month.strftime('%B %Y')) 
    d = d >> 1
  end 
end
p missing_months

=> ["December 2013", "May 2014", "July 2014", "August 2014", "February 2015"]

编辑:对于已经由日期对象组成的数组,您可以这样做:

require 'date'

dates = [Wed, 23 Oct 2013, Mon, 18 Nov 2013, Fri, 22 Nov 2013, Mon, 13 Jan 2014, Tue, 28 Jan 2014, Mon, 03 Feb 2014, Mon, 10 Feb 2014, Tue, 18 Feb 2014, Fri, 07 Mar 2014, Mon, 31 Mar 2014, Mon, 07 Apr 2014, Tue, 10 Jun 2014, Mon, 30 Jun 2014, Mon, 22 Sep 2014, Mon, 06 Oct 2014, Fri, 14 Nov 2014, Tue, 18 Nov 2014, Fri, 26 Dec 2014, Thu, 15 Jan 2015, Mon, 23 Mar 2015, Mon, 20 Apr 2015]

all_dates = []
dates.first.upto(dates.last) {|x| all_dates << x.strftime('%b %Y') if x.day == 1 || x == dates.first}

d = dates.map {|x| x.strftime('%b %Y')}.uniq

p (all_dates - d)
  #=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]

编辑:以下方法用于日期字符串数组

你可以试试这个:

require 'date'

dates = ["Wed, 23 Oct 2013", "Mon, 18 Nov 2013", "Fri, 22 Nov 2013", "Mon, 13 Jan 2014", "Tue, 28 Jan 2014", "Mon, 03 Feb 2014", "Mon, 10 Feb 2014", "Tue, 18 Feb 2014", "Fri, 07 Mar 2014", "Mon, 31 Mar 2014", "Mon, 07 Apr 2014", "Tue, 10 Jun 2014", "Mon, 30 Jun 2014", "Mon, 22 Sep 2014", "Mon, 06 Oct 2014", "Fri, 14 Nov 2014", "Tue, 18 Nov 2014", "Fri, 26 Dec 2014", "Thu, 15 Jan 2015", "Mon, 23 Mar 2015", "Mon, 20 Apr 2015"]

all_dates = []
d = dates.map {|x| Date.parse(x[8..-1])}.uniq
counter = d.first

until counter == d.last
  all_dates << counter
  counter = counter.next_month
end


p (all_dates - d).map {|x| x.strftime('%b %Y')}
  #=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]

另一种(更简洁)的方式是:

require 'date'

dates = ["Wed, 23 Oct 2013", "Mon, 18 Nov 2013", "Fri, 22 Nov 2013", "Mon, 13 Jan 2014", "Tue, 28 Jan 2014", "Mon, 03 Feb 2014", "Mon, 10 Feb 2014", "Tue, 18 Feb 2014", "Fri, 07 Mar 2014", "Mon, 31 Mar 2014", "Mon, 07 Apr 2014", "Tue, 10 Jun 2014", "Mon, 30 Jun 2014", "Mon, 22 Sep 2014", "Mon, 06 Oct 2014", "Fri, 14 Nov 2014", "Tue, 18 Nov 2014", "Fri, 26 Dec 2014", "Thu, 15 Jan 2015", "Mon, 23 Mar 2015", "Mon, 20 Apr 2015"]

all_dates = []
d = dates.map {|x| Date.parse(x[8..-1])}.uniq

d.first.upto(d.last) {|x| all_dates << x if x.day == 1}

p (all_dates - d).map {|x| x.strftime('%b %Y')}
  #=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]