B
Boris Prinz
Hi,
my solution is definitely over-engineered
There are three classes:
WeekDay: a day of the week
DayRange: a range between two days of the week
DayRangeArray: a list of DayRanges.
The DayRangeArray constructor does the work of splitting a list
of days into DayRange instances.
Regards,
Boris
### day_range.rb
require 'date'
# A day of the week. In calculations and comparisons a WeekDay behaves
# like an integer with 1=Monday, ..., 7=Sunday.
class WeekDay
# A WeekDay can be constructed from a number between 1 and 7 or a
# string like 'mon' or 'monday'.
def initialize(arg)
case arg
when Fixnum
if arg < 1 or arg > 7
raise ArgumentError.new("day number must be between 1 and 7")
end
@daynum = arg
when WeekDay
@daynum = arg.to_i
else
s = arg.to_s.downcase
if Date::ABBR_DAYS.has_key?(s)
@daynum = Date::ABBR_DAYS
elsif Date:AYS.has_key?(s)
@daynum = Date:AYS
else
raise ArgumentError.new("#{s} is not a day")
end
@daynum = 7 if @daynum == 0
end
end
# Returns the abbreviated name of the day (e.g. 'Mon')
def to_s
Date::ABBR_DAYNAMES[@daynum % 7]
end
# Returns the number of the day (1=Monday, ..., 7=Sunday)
def to_i
@daynum
end
%w{== <=> + - >}.each do |meth|
define_method meth do |other|
self.to_i.send(meth, other.to_i)
end
end
end
# A Range of days between two days of the week.
class DayRange < Range
# The first and last day of the range can be given as instances of
# class WeekDay, numbers or strings.
def initialize(from, to, exclusive=false)
from_day = WeekDay.new(from)
to_day = WeekDay.new(to)
super(from_day, to_day, exclusive)
end
# Returns a string representation of the range. Two consecutive days
# are returned as a list, e.g. 'Mon, Tue'.
def to_s
from = self.begin.to_s
to = self.end.to_s
case self.end - self.begin
when 0 then return from
when 1 then return from + ', ' + to
else return from + '-' + to
end
end
end
# An array containing several DayRange instances.
class DayRangeArray < Array
private
def normalize_days days
days.collect{|d| WeekDay.new(d)}.sort.uniq
end
# Given a list of days (as numbers or strings), an array of
# DayRanges is created.
def initialize(*days)
return if days.size == 0
a = normalize_days(days)
first = a.first
1.upto(a.size - 1) do |i|
if a > a[i-1] + 1
self << DayRange.new(first, a[i-1])
first = a
end
end
self << DayRange.new(first, a.last)
end
public
# The DayRanges are separated by comma. For example:
# DayRangeArray.new(1, 2, 3, 5).to_s # => "Mon-Wed, Fri"
def to_s
self.join(', ')
end
end
### day_range_test.rb
require 'day_range'
require 'test/unit'
class DayRangeTest < Test::Unit::TestCase
def test_new
dr = DayRange.new(1, 5)
assert_equal WeekDay.new(1), dr.begin
assert_equal WeekDay.new(5), dr.end
end
def test_argument_error
assert_raise(ArgumentError) { DayRange.new(1, 8) }
assert_raise(ArgumentError) { DayRange.new(0, 3) }
assert_raise(ArgumentError) { DayRange.new(-1, 3) }
assert_raise(ArgumentError) { DayRangeArray.new(1, 8) }
assert_raise(ArgumentError) { DayRangeArray.new('funday') }
end
def test_to_s
assert_equal 'Fri-Sun', DayRange.new(5, 7).to_s
assert_equal 'Mon-Fri', DayRange.new(1, 5).to_s
assert_equal 'Wed', DayRange.new(3, 3).to_s
assert_equal 'Mon, Tue', DayRange.new(1, 2).to_s
end
def test_day_range_list
exp = {
[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',
[7,6,7,4,3] => 'Wed, Thu, Sat, Sun',
[] => '',
['mon', 'Tuesday', 'WED', 5, 'saturday', 'sUnDaY'] => 'Mon-
Wed, Fri-Sun'
}
exp.each do |list, string|
assert_equal string, DayRangeArray.new(*list).to_s
end
end
end
my solution is definitely over-engineered
There are three classes:
WeekDay: a day of the week
DayRange: a range between two days of the week
DayRangeArray: a list of DayRanges.
The DayRangeArray constructor does the work of splitting a list
of days into DayRange instances.
Regards,
Boris
### day_range.rb
require 'date'
# A day of the week. In calculations and comparisons a WeekDay behaves
# like an integer with 1=Monday, ..., 7=Sunday.
class WeekDay
# A WeekDay can be constructed from a number between 1 and 7 or a
# string like 'mon' or 'monday'.
def initialize(arg)
case arg
when Fixnum
if arg < 1 or arg > 7
raise ArgumentError.new("day number must be between 1 and 7")
end
@daynum = arg
when WeekDay
@daynum = arg.to_i
else
s = arg.to_s.downcase
if Date::ABBR_DAYS.has_key?(s)
@daynum = Date::ABBR_DAYS
elsif Date:AYS.has_key?(s)
@daynum = Date:AYS
else
raise ArgumentError.new("#{s} is not a day")
end
@daynum = 7 if @daynum == 0
end
end
# Returns the abbreviated name of the day (e.g. 'Mon')
def to_s
Date::ABBR_DAYNAMES[@daynum % 7]
end
# Returns the number of the day (1=Monday, ..., 7=Sunday)
def to_i
@daynum
end
%w{== <=> + - >}.each do |meth|
define_method meth do |other|
self.to_i.send(meth, other.to_i)
end
end
end
# A Range of days between two days of the week.
class DayRange < Range
# The first and last day of the range can be given as instances of
# class WeekDay, numbers or strings.
def initialize(from, to, exclusive=false)
from_day = WeekDay.new(from)
to_day = WeekDay.new(to)
super(from_day, to_day, exclusive)
end
# Returns a string representation of the range. Two consecutive days
# are returned as a list, e.g. 'Mon, Tue'.
def to_s
from = self.begin.to_s
to = self.end.to_s
case self.end - self.begin
when 0 then return from
when 1 then return from + ', ' + to
else return from + '-' + to
end
end
end
# An array containing several DayRange instances.
class DayRangeArray < Array
private
def normalize_days days
days.collect{|d| WeekDay.new(d)}.sort.uniq
end
# Given a list of days (as numbers or strings), an array of
# DayRanges is created.
def initialize(*days)
return if days.size == 0
a = normalize_days(days)
first = a.first
1.upto(a.size - 1) do |i|
if a > a[i-1] + 1
self << DayRange.new(first, a[i-1])
first = a
end
end
self << DayRange.new(first, a.last)
end
public
# The DayRanges are separated by comma. For example:
# DayRangeArray.new(1, 2, 3, 5).to_s # => "Mon-Wed, Fri"
def to_s
self.join(', ')
end
end
### day_range_test.rb
require 'day_range'
require 'test/unit'
class DayRangeTest < Test::Unit::TestCase
def test_new
dr = DayRange.new(1, 5)
assert_equal WeekDay.new(1), dr.begin
assert_equal WeekDay.new(5), dr.end
end
def test_argument_error
assert_raise(ArgumentError) { DayRange.new(1, 8) }
assert_raise(ArgumentError) { DayRange.new(0, 3) }
assert_raise(ArgumentError) { DayRange.new(-1, 3) }
assert_raise(ArgumentError) { DayRangeArray.new(1, 8) }
assert_raise(ArgumentError) { DayRangeArray.new('funday') }
end
def test_to_s
assert_equal 'Fri-Sun', DayRange.new(5, 7).to_s
assert_equal 'Mon-Fri', DayRange.new(1, 5).to_s
assert_equal 'Wed', DayRange.new(3, 3).to_s
assert_equal 'Mon, Tue', DayRange.new(1, 2).to_s
end
def test_day_range_list
exp = {
[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',
[7,6,7,4,3] => 'Wed, Thu, Sat, Sun',
[] => '',
['mon', 'Tuesday', 'WED', 5, 'saturday', 'sUnDaY'] => 'Mon-
Wed, Fri-Sun'
}
exp.each do |list, string|
assert_equal string, DayRangeArray.new(*list).to_s
end
end
end