Here's my solution with unit tests (I'm trying to get into the habit).
I made Day a subclass of Date, but I ended up overriding most of the methods I needed, so I don't know if it bought me much. It also handles wrapping, but can be disabled by commenting out the Day#succ method.
···
#
# day_range.rb
#
class Array
def collapse_ranges(options = {})
range = []
return_array = []
self.each_with_index do |item, i|
range.push(item)
# if this is the last item
# - or -
# there is another item after this one
if item == self.last || self[i + 1]
# if this is the last item
# - or -
# the next item is not the item after the current one
if item == self.last|| item.succ != self[i + 1]
# if there is a range of 3 items or more
if range.length >= 3
return_array.push(range.first..range.last)
# else empty the range individually
else
return_array.concat range
end
# clear out the range
range.clear
end
end
end
return return_array
end
def to_s
self.map { |i| i.to_s }.join(', ')
end
end
class Range
def to_s
"#{first}-#{last}"
end
end
require 'date'
class Day < Date
Days = [nil, "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
Abbr = [nil, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
def self.commercial(int)
day = send("from_#{int.class}".downcase.intern, int)
super(1984,1,day)
end
def succ
if cwday == 7
Day.commercial(1)
else
super
end
end
def to_s
Days[cwday]
end
def to_abbr
Abbr[cwday]
end
alias_method :to_s, :to_abbr
def self.from_string(string)
# If string isn't in Days or Abbr, return string and let Date#commercial raise ArgumentError
Days.index(string.capitalize) || Abbr.index(string.capitalize) || string.capitalize
end
def self.from_fixnum(int)
# Date#commercial allows integers over 7, so raise ArgumentErrror here
if (1..7).include? int
int
else
raise ArgumentError
end
end
end
class DayRange
def initialize(array)
@array = array.map{|i| Day.commercial(i) }.collapse_ranges
end
def to_s
@array.to_s
end
end
#
# test_day_range.rb
#
require 'test/unit'
require 'lib/days.rb'
class TestArray < Test::Unit::TestCase
def test_collapse_ranges
assert_equal( [(1..4),6], [1,2,3,4,6].collapse_ranges)
end
def test_to_s
assert_equal([1,2,3].to_s, '1, 2, 3')
end
end
class TestRange < Test::Unit::TestCase
def test_to_s
assert_equal((1..3).to_s, '1-3')
end
end
class TestDay < Test::Unit::TestCase
def setup
@day = Day.commercial(6)
@next_day = Day.commercial('Sun')
end
def test_error
assert_raise(ArgumentError){ Day.commercial('not') }
assert_raise(ArgumentError){ Day.commercial(8) }
end
def test_succ
assert_equal(@day.succ.cwday,7)
end
def test_spaceship
assert(@day < @next_day)
end
def test_to_s
assert_equal('Sat', @day.to_s)
end
end
class TestDayRange< Test::Unit::TestCase
def test_to_s
[
[[1,2,3,4,5,6,7],'Mon-Sun'],
[[1,2,3,6,7], "Mon-Wed, Sat, Sun"],
[[1,3,4,5,6], "Mon, Wed-Sat"],
[[2,3,4,6,7], "Tue-Thu, Sat, Sun"],
[[1,3,4,6,7], "Mon, Wed, Thu, Sat, Sun"],
[[7], "Sun"],
[[1,7], "Mon, Sun"] ,
[['Tue','Wed','Thu','Fri'],"Tue-Fri"],
[['Wednesday','Thursday','Friday','Saturday'],"Wed-Sat"],
[['tue','fri','sat','sun'], "Tue, Fri-Sun"],
[[5,6,7,1],"Fri-Mon"],
[[1,5,6,7],"Mon, Fri-Sun"]
].each do |arr, str|
assert_equal(str, DayRange.new(arr).to_s)
end
assert_raise(ArgumentError){ DayRange.new([1,8]).to_s }
end
end