After looking through some of the other solutions to this quiz, I  
decided to adapt some of the good ideas I found to improve my own  
solution. Using Test::Unit to for testing was the first modification  
I wanted to make. I have never used Test::Unit up to now. I thought  
it was high time for me to do so.

Since changing over to Test::Unit doesn't really change my solution,  
I didn't plan to repost it after making the change. I changed my mind  
because I ran into something with Test::Unit that really surprised  
me. First, the modified code and then some more discussion.

<code>
require 'test/unit'

class DayRange

    DAY_DIGITS = {
       'mon' => 1,
       'tue' => 2,
       'wed' => 3,
       'thu' => 4,
       'fri' => 5,
       'sat' => 6,
       'sun' => 7,
       'monday' => 1,
       'tuesday' => 2,
       'wednesday' => 3,
       'thursday' => 4,
       'friday' => 5,
       'saturday' => 6,
       'sunday' => 7,
       '1' => 1,
       '2' => 2,
       '3' => 3,
       '4' => 4,
       '5' => 5,
       '6' => 6,
       '7' => 7
    }

    SHORT_NAMES = %w[_ Mon Tue Wed Thu Fri Sat Sun].freeze

    LONG_NAMES = %w[_ Monday Tuesday Wednesday Thursday
                    Friday Saturday Sunday].freeze

    # Return day range as nicely formatted string.
    # If @long is true, day names appear in long form; otherwise, they
    # appear in short form.
    def to_s
       names = @long ? LONG_NAMES : SHORT_NAMES
       result = []
       @days.each do |d|
          case d
          when Integer
             result << names[d]
          when Range
             result << names[d.first] + "-" + names[d.last]
          end
       end
       result.join(", ")
    end

    # Return day range as array of integers.
    def to_a
       result = @days.collect do |d|
          case d
          when Integer then d
          when Range then d.to_a
          end
       end
       result.flatten
    end

    def initialize(*args)
       @days = []
       @long = false
       @args = args
       @args.each do |arg|
          case arg
          when Integer
             bad_arg if arg < 1 || arg > 7
             @days << arg
          when /^(.+)-(.+)$/
             begin
                d1 = DAY_DIGITS[$1.downcase]
                d2 = DAY_DIGITS[$2.downcase]
                bad_arg unless d1 && d2 && d1 <= d2
                d1.upto(d2) {|d| @days << d}
             rescue StandardError
                bad_arg
             end
          else
             d = DAY_DIGITS[arg.downcase]
             bad_arg unless d
             @days << d
          end
       end
       @days.uniq!
       @days.sort!
       normalize
    end

# Use this to change printing behavior from short day names to long day
# names or vice-versa.
attr_accessor :long

private

    # Convert @days from an array of digits to normal form where runs of
    # three or more consecutive digits appear as ranges.
    def normalize
       runs = []
       first = 0
       for k in 1... / days.size
          unless @days[k] == @days[k - 1].succ
             runs << [first, k - 1] if k - first > 2
             first = k
          end
       end
       runs << [first, k] if k - first > 1
       runs.reverse_each do |r|
          @days[r[0]..r[1]] = @days[r[0]]..@days[r[1]]
       end
    end

    def bad_arg
       raise(ArgumentError,
             "Can't create a DayRange from #{@args.inspect}")
    end

end

class TestDayRange < Test::Unit::TestCase

    # All these produce @days == [1..7].
    ONE_RANGE = [
       %w[mon tue wed thu fri sat sun],
       %w[monDay tuesday Wednesday Thursday friDAY saturday SUNDAY],
       %w[mon-fri sat-sun],
       %w[4-7 1-3],
       (1..7).to_a.reverse,
       [4, 7, 6, 5, 4, 1, 2, 1, 2, 3, 3, 7, 6, 5],
    ]

    # Both these produce @days == [1..3, 5..7].
    TWO_RANGES = [
       %w[mon-mon tue-tue wed-wed fri-sun],
       [1, 2, 'mon-wed', 'friday', 6, 7]
    ]

    INVALID_ARGS = [
       [1, 2, 'foo'],
       %w[foo-bar],
       %w[sat-mon],
       (0..7).to_a.reverse,
       (1..8).to_a
    ]

    @@one_range = []
    @@two_ranges = []

    def test_args_helper(args, expected)
       obj = nil
       assert_nothing_raised(ArgumentError) {obj = DayRange.new(*args)}
       assert_equal(expected, obj.instance_variable_get(:@days))
       obj
    end

    def test_valid_args
       ONE_RANGE.each do |args|
          @@one_range << test_args_helper(args, [1..7])
       end
       TWO_RANGES.each do |args|
          @@two_ranges << test_args_helper(args, [1..3, 5..7])
       end
       puts "test_valid_args -- #{@@one_range.size}, # 
{@@two_ranges.size}"
    end

    def test_bad_args
       puts "test_bad_args"
       INVALID_ARGS.each do |args|
          assert_raise(ArgumentError) {DayRange.new(*args)}
       end
    end

    def test_to_s
       puts "test_to_s -- #{@@one_range.size}, #{@@two_ranges.size}"
       @@one_range.each do |obj|
          assert_equal('Mon-Sun', obj.to_s)
       end
       @@two_ranges.each do |obj|
          assert_equal('Mon-Wed, Fri-Sun', obj.to_s)
       end
    end

    def test_to_a
       puts "test_to_a -- #{@@one_range.size}, #{@@two_ranges.size}"
       @@one_range.each do |obj|
          assert_equal((1..7).to_a, obj.to_a)
       end
       @@two_ranges.each do |obj|
          assert_equal([1, 2, 3, 5, 6, 7], obj.to_a)
       end
    end

end
</code>

<result>
Loaded suite /Users/mg/Projects/Ruby/Ruby Quiz/Quiz 92/quiz_92
Started
test_bad_args
.test_to_a -- 0, 0
.test_to_s -- 0, 0
.test_valid_args -- 6, 2
.
Finished in 0.010845 seconds.

4 tests, 21 assertions, 0 failures, 0 errors
</result>

None of the assertions in test_to_s and test_to_a execute. This is  
because @@one_range and @@two_ranges are both empty when these tests  
are run. My plan of creating the test objects in test_valid_args and  
reusing them in test_to_s and test_to_a fails. This happens because  
the tests are run, not in the order in which they appear in the  
source code, but in the sort order of their names. How strange!

The fix is obvious and I have already applied it: rename  
test_valid_args to test_args so it runs first.

I've posted this because it took me longer to trace down this problem  
than it took me to write the TestDayRange class. I will consider it  
worth the effort if the posting helps one other person to avoid this  
trap. On the other hand, if I'm guilty of belaboring a well-known  
"feature" of Test::Unit, I apologize.

Regards, Morton