How do I find interruptions in several dates with Ruby

advertisements

Let's say I have the following date ranges in Ruby:

Sat, 01 Jan 2011..Tue, 01 Feb 2011

Wed, 05 Jan 2011..Thu, 17 Feb 2011

Wed, 02 Feb 2011..Tue, 01 Mar 2011

Sun, 01 Jan 2012..Thu, 05 Jan 2012

By what process can I take all four of these ranges and get output that tells me there is a break in the ranges on Wed, 02 Mar 2011?

EDIT Sergio is right and there appears to be nothing built in. I can grok this like so:

x = Range.new(Date.parse('2011-01-01'), Date.parse('2011-02-01'))
r = Range.new(Date.parse('2011-01-05'), Date.parse('2011-02-17'))
y = Range.new(Date.parse('2011-02-02'), Date.parse('2011-03-01'))
z = Range.new(Date.parse('2012-01-01'), Date.parse('2012-01-05'))

ranges = [x,y,z,r]

dates = ranges.collect!{|r|r.to_a}.flatten!.uniq!.sort!

dates.delete_if do |date|
  index = ranges.index(date)
  next_date = ranges[index + 1]
  next_date == date + 1 || next_date.nil?
end

Still looking for the best solution though.


Ruby's Enumerable.chunk is useful here. I shortened the ranges being checked for simplicity, and added an additional range that was out of order, to show that it's handling out of order ranges:

require 'date'

date_ranges = [
  '01 Jan 2011', '03 Jan 2011',
  '02 Jan 2011', '04 Jan 2011',
  '02 Mar 2011', '03 Mar 2011',
  '01 Jan 2000', '02 Jan 2000'
].each_slice(2).map{ |dates|
  Range.new(
    *dates.map{ |d|
      Date.parse(d)
    }
  )
}

gaps = date_ranges
  .inject([]){ |a, d| a |= d.to_a }   # accumulate the unique dates in the ranges
  .sort                               # sort to get them in ascending order
  .each_with_index                    # add an offset into the order
  .chunk{ |d,i| d - i }               # group by the delta
  .to_a[1 .. -1]                      # grab all but the first group
  .map{ |g,dates| dates.first.first } # strip off the groups and indexes

puts gaps

Outputs:

2011-01-01
2011-03-02

Because I added the out of order range, the original starting range is now a gap, as is the March 02 2011 date.

This will give you an example what chunk is doing:

[1,2,3,4,5].each_with_index.chunk{ |n,i| n-i }.to_a # => [[1, [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]]]
[1,2,  4,5].each_with_index.chunk{ |n,i| n-i }.to_a # => [[1, [[1, 0], [2, 1]]], [2, [[4, 2], [5, 3]]]]