[QUIZ] DayRange(#92) - My solution

I sent this before with a big zipfile attachment including rdoc, and I
think that the mailing list rejected it due to the size of the
attachment. So I'm sending it again with just the source files.

After solving the basic problem in a few minutes, doing the "simplest
thing which could possibly work," I've spent my spare time over the
last 4 or 5 days letting features, well, creep.

I've noticed that there are two major styles of quiz respondants, some
go for brief code, others go for well-documented code sometimes with
lots of new features. I guess I fall into the latter camp.

I really used this as an exercise to explore implementing rails-like
options parameters. The code is documented, and I've included the
html output of rdoc in the attached zip file. There's also a test
script.

So here's my solution. A few goodies I've added.

1) Rails style options can be used on new, to_s and a few more.
2) Option to select when the week starts, the ranges produced in to_s
will never span over a week boundary.
I just realized that I really should let you specify the start of the
week as either a number or a name, but it's time to lay my cards on
the table, so names will have to wait until the next 'release' <G>.
3) Option to select a language. English and French are built-in.
4) Support for ad-hoc languages, and adding new languages via a class
method, see the rdoc and testcases for examples of how to do this.
5) A DayRange is enumerable and should act like an array containing
the day numbers with the week-start fixed as Monday=1, extension to
add options are on the 'roadmap' <G>
6) Probably some other things I'm overlooking as I type this note.

day_range.rb (12.4 KB)

subranges.rb (565 Bytes)

testdayrange.rb (3.4 KB)

···

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Brian pointed out that I'd missed the spec and was hypenating two day
runs instead of the minimum run called for.

So, I've updated my solution to fix this, and keeping with the spirit
of options, I added a :min_span option to set the minimum number of
days to coalesce.

I've also made to_s work with the :week_start option as well.

I was going to go ahead and add the ability to specify the :week_start
as a string as well as an integer, but I realized that I really need
to refactor a bit before I do that, so I stopped for now.

day_range.rb (13.1 KB)

testdayrange.rb (4.65 KB)

···

---------- Forwarded message ----------
From: Bryan Donovan <brdonovan@gmail.com>
Date: Aug 30, 2006 2:45 PM
Subject: Re: [QUIZ] DayRange(#92) - My solution
To: rick.denatale@gmail.com

Rick,

Wow, a very detailed and extensive solution...

I did notice one thing, and maybe this was intentional, but when you
pass in 1,2,4,5 you should get "Mon, Tue, Thu, Fri", not "Mon-Tue,
Thu-Fri". However, this is just according to the quiz "rules" and is
not necessarily the best way. Maybe an option to specify the minimum
number of consecutive days to constitute a "range" would be a good
addition. I'll probably add that to my own version ...

Regards,
Bryan

On 8/30/06, Rick DeNatale <rick.denatale@gmail.com> wrote:

I sent this before with a big zipfile attachment including rdoc, and I
think that the mailing list rejected it due to the size of the
attachment. So I'm sending it again with just the source files.

After solving the basic problem in a few minutes, doing the "simplest
thing which could possibly work," I've spent my spare time over the
last 4 or 5 days letting features, well, creep.

I've noticed that there are two major styles of quiz respondants, some
go for brief code, others go for well-documented code sometimes with
lots of new features. I guess I fall into the latter camp.

I really used this as an exercise to explore implementing rails-like
options parameters. The code is documented, and I've included the
html output of rdoc in the attached zip file. There's also a test
script.

So here's my solution. A few goodies I've added.

1) Rails style options can be used on new, to_s and a few more.
2) Option to select when the week starts, the ranges produced in to_s
will never span over a week boundary.
I just realized that I really should let you specify the start of the
week as either a number or a name, but it's time to lay my cards on
the table, so names will have to wait until the next 'release' <G>.
3) Option to select a language. English and French are built-in.
4) Support for ad-hoc languages, and adding new languages via a class
method, see the rdoc and testcases for examples of how to do this.
5) A DayRange is enumerable and should act like an array containing
the day numbers with the week-start fixed as Monday=1, extension to
add options are on the 'roadmap' <G>
6) Probably some other things I'm overlooking as I type this note.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

IPMS/USA Region 12 Coordinator
http://ipmsr12.denhaven2.com/

Visit the Project Mercury Wiki Site
http://www.mercuryspacecraft.com/

Ackkkk!

I just noticed how my attachments came over on the rubyquiz website.
So here's the code in-line in the message
=== subranges.rb ===
module Enumerable

  # Return an array containing the sub-ranges of sorted contents of the receiver
  # Each element must be comparable, and must respond to succ
  def subranges(min_span = 2)
    range_start = range_end = nil
    subranges = []
    self.sort.each do |elem|
      if range_start.nil?
        range_start = range_end = elem
      else
        if range_end.succ == elem
          range_end = elem
        else
          subrange = (range_start..range_end)
          if subrange.entries.length >= min_span
            subranges << subrange
          else
            subrange.each {|ea| subranges << (ea..ea) }
          end
          range_start = range_end = elem
        end
      end
    end
    unless range_start.nil?
      subrange = (range_start..range_end)
      if subrange.entries.length >= min_span
        subranges << subrange
      else
        subrange.each {|ea| subranges << (ea..ea)}
      end
    end
    subranges
  end

end

=== test_day_range.rb===
require 'day_range.rb'
require 'test/unit'

class TestDayRange < Test::Unit::TestCase

  EsperantoMap = {"Lundo" => 1, "Lun" => 1, "Mardo" => 2, "Mar" => 2,
"Merkredo" => 3, "Mer" => 3,
      "Jhaudo" => 4, "Jha" => 4, "Vendredo" => 5, "Ven" => 5, "Sabato" =>
6, "Sab" => 6,
      "Dimancho" => 7, "Dim" => 7}

  EsperantoNames = ["Lun", "Mar", "Mer", "Jha", "Ven", "Sab","Dim"]

  GermanMap = { "Montag" => 1, "Mon" => 1, "Dienstag" => 2, "Die" => 2,
"Mittwoch" => 3, "Mitt" => 3,
          "Donnerstag" => 4, "Don" => 4, "Freitag" => 5, "Frei" => 5,
"Samstag" => 6, "Sam" => 6,
          "Sonntag" => 7, "Sonn" => 7 }
  GermanNames = ["Mon", "Die", "Mitt", "Don", "Frei", "Sam", "Sonn"]

  def test_equal_1
    dr1 = DayRange.new(1,2,4,5)
    dr2 = DayRange.new(1,2,4,5)
    dr3 = DayRange.new(2,5,4,1)
    assert_equal(dr1,dr2)
    assert_equal(dr1,dr3)
  end

  def test_weekstart_1
    dr1 = DayRange.new('Mon', 'Tuesday', 'Thursday', 'Friday', 'Sat',
:week_start => 2)
    assert_equal("Tue, Thu-Sat, Mon",dr1.to_s)
  end

  def test_equal_2
    dr1 = DayRange.new(1,2,4,5)
    dr2 = DayRange.new('Monday', 'Tuesday', 'Thursday', 'Friday')
    assert_equal(dr1,dr2)
  end

  def test_to_s_numbers
    dr = DayRange.new(1,2,4,5)
    assert_equal("Mon-Tue, Thu-Fri",dr.to_s(:min_span => 2))
    assert_equal("Mon, Tue, Thu, Fri",dr.to_s)
    dr = DayRange.new(1,2,3, 5,6)
    assert_equal("Mon-Wed, Fri-Sat",dr.to_s(:min_span => 2))
    assert_equal("Mon-Wed, Fri, Sat",dr.to_s)
  end

  def test_to_s_names
    dr = DayRange.new('Monday', 'Tuesday', 'Thursday', 'Friday')
    assert_equal("Mon, Tue, Thu, Fri",dr.to_s)
    assert_equal("Mon-Tue, Thu-Fri",dr.to_s(:min_span => 2))
  end

  def test_to_s_options
    dr = DayRange.new('Monday', 'Tuesday', 'Thursday', 'Friday')
    assert_equal("Lun, Mar, Jeu, Ven", dr.to_s(:language => :French))
    assert_equal("Lun-Mar, Jeu-Ven", dr.to_s(:language => :French,
:min_span => 2))
    assert_equal("Mercury-Venus, Mars-Saturn",
           dr.to_s(:day_names => %w[Mercury Venus Earth Mars Saturn
Jupiter Uranus],
            :min_span => 2)
          )

          dr = DayRange.new(1, 2, 3, 6, 7)
    assert_equal("Mon-Wed, Sat, Sun", dr.to_s)
    assert_equal("Tue, Wed, Sat-Mon", dr.to_s(:week_start => 2))
    assert_equal("Wed, Sat-Tue",dr.to_s(:week_start => 3))
    assert_equal("Sat-Wed", dr.to_s(:week_start => 4))
    assert_equal("Sat-Wed", dr.to_s(:week_start => 5))
    assert_equal("Sat-Wed", dr.to_s(:week_start => 6))
    assert_equal("Sun-Wed, Sat", dr.to_s(:week_start => 7))
  end
  def test_translate_to_french
    dr = DayRange.new(1,2,4,5)
    assert_equal("Lun, Mar, Jeu, Ven",dr.to_s(:language => 'French'))
    assert_equal("Lun-Mar, Jeu-Ven",dr.to_s(:language => 'French',
:min_span => 2))
  end

  def test_new_french
    dr1F = DayRange.new(1,2,4,5, :language => 'French')
    dr1 = DayRange.new(1,2,4,5)
    assert_equal(dr1, dr1F)
    assert_equal("Lun-Mar, Jeu-Ven",dr1F.to_s(:min_span => 2))
    assert_equal("Mon-Tue, Thu-Fri",dr1F.to_s(:language => 'English',
:min_span => 2))
    assert_equal("Lun, Mar, Jeu, Ven",dr1F.to_s)
    assert_equal("Mon, Tue, Thu, Fri",dr1F.to_s(:language => 'English'))
  end

  def test_new_esperanto
    dr1Esp = DayRange.new(1,2,4,5, :day_map => EsperantoMap)
    dr1 = DayRange.new(1,2,4,5)
    assert_equal(dr1, dr1Esp)
    assert_equal("Lun-Mar, Jha-Ven", dr1Esp.to_s(:min_span => 2))
  end

  def test_bad_days
    assert_raise(ArgumentError) {DayRange.new(1,2,8)}
    assert_raise(ArgumentError) {DayRange.new(1, :day_map => {'Mon' =>
1, "Tue" => 9})}
  end

  def test_add_german
    DayRange.remove_language(:German)
    DayRange.add_language(:German, GermanMap, GermanNames)
    assert_equal("Die, Don, Sam, Sonn", DayRange.new(2,4,6,7, :language
=> 'German').to_s)
    assert_equal("Die, Don, Sam-Sonn", DayRange.new(2,4,6,7,
                :language => 'German').to_s(:min_span => 2))
    
    DayRange.remove_language(:German)
    DayRange.add_language(:German, GermanMap)
    assert_equal("Die, Don, Sam, Sonn", DayRange.new(2,4,6,7, :language
=> 'German').to_s)
    assert_equal("Die, Don, Sam-Sonn", DayRange.new(2,4,6,7,
                :language => 'German').to_s(:min_span => 2))
  end

  def test_each_name
    dr = DayRange.new(1,2, 5, 6)
    expected = ['Mon', 'Tue', 'Fri', 'Sat']
    dr.each_name { |name| assert_equal(expected.shift, name)}
    assert(expected.empty?, "Missing results #{expected.inspect}")
    expected = ['Doc', 'Grumpy', 'Bashful', 'Sleepy']
    dwarves = ['Doc', 'Grumpy', 'Happy', 'Sneezy','Bashful', 'Sleepy', 'Dopey']
    dr.each_name(:day_names => dwarves) { |name|
assert_equal(expected.shift, name)}
    assert(expected.empty?, "Missing results #{expected.inspect}")
    expected = ['Lun', 'Mar', 'Ven', 'Sam']
    dr.each_name(:language => 'French') { |name|
assert_equal(expected.shift, name)}
    assert(expected.empty?, "Missing results #{expected.inspect}")
  end
end

=== day_range.rb===
# This class was written as an answer to RubyQuiz92.

···

#
# The DayRange.new method takes one or more day specifications as
either integers or natural language
# strings representing day names. An instance will respond to to_s
with a string representing
# the list of days with consecutive days collapsed to a form like 'Mon-Fri'
#
# Several methods take "Rails-style" options, one or more associations
after any normal parameters.
# The keys of these associations can be Strings or symbols which will
be converted using to_sym.
#
# Features not called for in the quiz include:
#
# * A number for the start of the week may be specified. This will
affect the output of
# to_s. For example, :week_start => 7, indicates that the week
starts on Sunday, and
# DayRange.new('Sat', 'Sun', 'Mon', :week_start => 7).to_s =>
"Sun-Mon, Sat"
#
# * Support is provided for languages other than English, French is
built in, but additional
# languages can be added, either on the new call, or by a class
method DayRange.add_language
#
# * DayRanges are enumerable and produce the numbers of the day they
contain, in Monday-Sunday
# order.
#
# * Two Dayranges are == if they contain the same days
#
# Author: Rick DeNatale http://talklikeaduck.denhaven2.com
#
# Test cases are in the file testdayrange.rb
#
# The code which does most of the work in detecting sub-ranges is in
the file subranges.rb
# This adds a method to Enumerable which produces an array of ranges
which cover the same contents
# as the Enumeration.

require 'subranges'
#
class DayRange

  include Enumerable

  # StringSymHash extends Hash so that symbol and string keys are
equivalent a la Rails
  # Normally I don't like implementing things like this via sub-classing but...
  class StringSymHash < Hash

    def [](key)
      super(key.to_sym)
    end

    def []=(key,value)
      super(key.to_sym, value)
    end

    def StringSymHash.[](hash)
      ssh = StringSymHash.new
      hash.each { |k, v| ssh[k] = v}
      ssh
    end
  end

  # maps and names for English and French
  @@day_maps = StringSymHash[ :English => {
                                             'Monday' => 1, 'Mon' =>
1, 'Tuesday' => 2, 'Tue' => 2,
                                 'Wednesday' => 3, 'Wed' => 3,
'Thursday' => 4, 'Thu' => 4,
                                 'Friday' => 5, 'Fri' => 5, 'Saturday'
=> 6, 'Sat' => 6,
                                 'Sunday' => 7, 'Sun' => 7 },
             :French => {
                                             'Lundi' => 1, 'Lun' => 1,
'Mardi' => 2, 'Mar' => 2,
                                 'Mercredi' => 3, 'Mer' => 3, 'Jeudi' =>
4, 'Jeu' => 4,
                                 'Vendredi' => 5, 'Ven' => 5, 'Samedi'
=> 6, 'Sam' => 6,
                                 'Dimanche' => 7, 'Dim' => 7 } ]
  @@day_names = StringSymHash[
    :English => [nil, 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    :French => [nil, 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'],
  ]

  # Add a language to those supported by the DayRange class
  #
  # :call-seq:
  # DayRange.add_language(lang_name, day_map[, day_names])
  #
  # The <em>lang_name_ parameter</em> is the name of the language. It
will be internally converted
  # to a symbol. So, for example, if you have:
  #
  # DayRange.add_language('Esperanto', ...)
  #
  # then one could ask for a DayRange in Esperanto with:
  #
  # DayRange.new(1, 3, :Language => :Esperanto)
  #
  # The <em>day_map</em> parameter should be a Hash which maps day
names to integers in
  # the range (1..7). More than one name may map to a particular day_number.
  #
  # The _day_names_ parameter must be duck-typeable to a 7-element
array or Strings, with the
  # first element containing the name which will be used for Monday for
output (e.g. for to_s),
  # and the last for Sunday.
  #
  # If _day_names_ is omitted, then it will be constructed by finding a
name for each day_number
  # as least as short as any other name which maps to that day_number
in _day_map_
  def DayRange.add_language(lang_name, day_map, day_names = nil)
    #HACK - if user supplied day_names pre-pend an element so that we can use
    # pseudo 1-origin indexing

    day_names = day_names.dup.unshift('') if day_names
    @@day_names[lang_name.to_s] = validated_day_names(day_names ||
day_names_from_day_map(day_map))
    @@day_maps[lang_name.to_s] = day_map.dup
  end

  # Remove the language _lang_name_
  # Do nothing silently if _lang_name_ is not present
  #
  # :call-seq:
  # DayRange.remove_language(lang_name)
  #
  def DayRange.remove_language(lang_name)
    @@day_names.delete(lang_name)
    @@day_maps.delete(lang_name)
  end

  def DayRange.day_names_from_day_map(day_map) #:nodoc:
    # set each days name to the shortest name
    # in the name mapping
    # puts("Debug- DayRange.day_names_from_day_map(#{day_map})")
    day_names = Array.new(8)
    day_map.each do |name ,number|
      current_name = day_names[number] || day_names[number] = name
      day_names[number] = name if name.length < current_name.length
    end
    validated_day_names(day_names)
  end

  def DayRange.validated_day_names(day_names) #:nodoc:
    (1..7).each do |i|
      check_arg(day_names[i], "No name for day number #{i}")
    end
    day_names
  end

  def DayRange.check_arg(assertion, msg) # :nodoc:
    raise ArgumentError.new( msg) unless assertion
  end

  def DayRange.day_names_from_options(options, day_map) # :nodoc:
    # puts "Debug- DayRange.day_names_from_options(options=#{options.inspect},"
    # puts " day_map=#{day_map.inspect})"
    return options[:day_names] if options.key?(:day_names)
    return DayRange.day_names_from_day_map(day_map)
  end

  def DayRange.language_from_options(options) # :nodoc:
    get_option(:language, options, :English)
  end

  def DayRange.get_option(option, options, default) # :nodoc:
    options.key?(option) ? options[option] : default
  end

  # Returns a new DayRange (which contains one or more days of the week)
  #
  # :call-seq:
  # DayRange.new(day* [, options])
  #
  # <em>day</em> arguments can be either numbers in the range
  # (1..7) or names in the <em>day_mapping</em> (see <b>:day_mapping</b> option).
  #
  # Options:
  #
  # [*:language* => symbol] Specifies the language to be used to
  # interpret the _day_s which are Strings, and for the default options
for output via DayName#to_s
  # The possible values for the symbol are :English, and :French,
  # additional languages can be added via the DayRange.addLanguage
  # method. If this option is not specified, :English will be used.
  #
  # [*:day__map* => hash ] The value _hash_ should be a hash which maps the names
  # of days to the number of the day, with 1 being the first
  # day of the week (normally Monday), up to 7 for the last day
  # of the week (normally Sunday).
  # More than one name may map to the same day. If not specified,
  # the day_mapping for the selected language is used.
  #
  # [*:day_names* => array] The value _array_ must be duck-typeable to
a 7-element array.
  # The elements are the names of the days to be used by default for
  # output (e.g. with DayRange#to_s. If not specified, then the
day_names for the selected
  # language will be used, unless *:day_map* is specified in which case
  # *:day_names* will be computed from one of the sortest names in the
map for each
  # day.
  #
  # [*:week_start* => int] The value _int_ must be in the range (1, 7).
  # It is used to shift the start of the week. For example to create a
  # DayRange for a week which starts on Sunday rather than Monday,
  # specify a week_start of 7. Although it is also possible to
  # achieve the same effect by changing the numbers in day_mapping,
  # using week_start allows the same day_mapping to be used for weeks
  # starting on different days.
  #
  # [*:min_span* => int] The value _int_ indicates the minimum span of
days which will
  # be collapsed into hyphenated form. The default is 3, as specified
by the Quiz spec
  # I missed this the first time.
  def initialize( *days ) #:doc:
    options = extract_options_from_args!(days)
    # puts "Debug: options = #{options.inspect}"
    @day_map = day_map_from_options(options)
    @day_map.each do |name, number|
      DayRange.check_arg((1..7) === number,
                                           "'#{number}' is not an
acceptable day for #{name.to_s}.")
    end
    @min_span = DayRange.get_option(:min_span, options, 3)
    @language = DayRange.language_from_options(options)
    @day_names = DayRange.day_names_from_options(options,@day_map)
    @week_start = week_start_from_options(options, @day_map)
    @day_numbers = days.map do | day |
      number = @day_map[day] || day
      DayRange.check_arg((1..7) === number, "'#{number.inspect}' is not
an acceptable day.")
      number
    end
    @day_numbers.sort!
  end

  # Return an array of subranges of @day_numbers adjusted for the week_start
  def adjusted_ranges(min_span, week_start)
    (week_start == 1 ? @day_numbers : @day_numbers.map { |elem|
ws_adj(elem,week_start) }).subranges(min_span)
  end

  # Two DayRanges are == if they contain the same day numbers
  def ==(other)
    false unless other.kind_of? DayRange
    self.to_a == other.to_a
  end
    
  # Call _block_ once for each day number in _day_range_ passing the
day number to the block.
  # The order should be the same regardless of week start, i.e. Monday
should always come first
  # then Tuesday, etc.
  #
  # :call-seq:
  # day_range.each {|day_number| block } -> _day_range_
  def each()
    @day_numbers.each { | elem | yield elem }
  end

  # Convert _day_range_ to an array, elements will be in order so that
Monday, if it is the range
  # will be first then Tuesday, etc. i.e. the effect of weekstart will
be removed
  # :call-seq:
  # day_range.to_a
  def to_a
    @day_numbers.dup
  end

  # Call _block_ once for each day name in _day_range_, passing that
name to the block
  #
  # :call-seq:
  # day_range.each_name [(options)] { |day_name| block } -> _day_range_
  #
  # *Options*
  #
  # Options are specified Rails style, as one or more associations at
the end of the argument
  # list.
  #
  # [*:language* => symbol] Specifies the language to be used for the names
  # The possible values for the _symbol_ are :English, and :French,
  # additional languages can be added via the DayRange.addLanguage
  # method. If this option is not specified, :English will be used.
  #
  # [*:day_names* => array] The _array_ must be 7-element array.
  # The elements are the names of the days to be used by default for
  # output via to_s. If not specified, then the day_name for the selected
  # language will be used.
  #
  def each_name(options={})
    names = get_names_override(options)
    to_a.each { | day_number | yield names[day_number] }
  end

  def get_names_override(options)
    return options[:day_names].dup.unshift('') if options.key?(:day_names)
    language = options[:language]
    return @@day_names[language] if language
    @day_names
  end
    
  # Returns a string representing the DayRange, Options can be specified.
  #
  # :call-seq:
  # day_range.to_s [(options)]
  #
  # Options:
  #
  # [*:language* => symbol] Specifies the language to be used for output
  # The possible values for _symbol_ are :English, and :French,
  # additional languages can be added via the DayRange.addLanguage
  # method. If this option is not specified, the language used when the DayRange
  # was created will be used.
  #
  # [*:day_names* => array] The _array_ must be duck-typeable to a
7-element array.
  # The elements are the names of the days to be used by default for
  # output via to_s. If not specified, then the day_names for the selected
  # language will be used.
  #
  # [*:min_span* => int] The value _int_ indicates the minimum span of
days which will
  # be collapsed into hyphenated form. The default is 3, as specified
by the Quiz spec
  #
  # [*:week_start* => int] The value _int_ must be in the range (1, 7).
  #
  def to_s(options={})
    names = get_names_override(options)
    #puts "Debug: @day_names=#{@day_names.inspect}, names=#{names}"
    min_span = DayRange.get_option(:min_span, options, @min_span)
    week_start = DayRange.get_option(:week_start, options, @week_start)
  
    result = ""
    adjusted_ranges(min_span,week_start).map {|range|
      range.first == range.last ?
                               "#{names[ws_unadj(range.first, week_start)]}" :
             "#{names[ws_unadj(range.first,
week_start)]}-#{names[ws_unadj(range.last,week_start)]}"
    }.join(", ")
  end

  private
        # convert a number where 1 = Mon.. 7 = Sunday to the equivalent
  # when the week starts on day number
  def ws_adj(number, week_start)
    ((number - week_start) % 7) + 1
  end
  
  # convert a number back to the original form
  def ws_unadj(number, week_start)
    ((number + week_start + 5) % 7) + 1
  end

  def extract_options_from_args!(args)
    #puts "Debug - extract_options_from_args!(#{args.inspect})"
    #puts " #{args.last.class}"
    StringSymHash[args.last.kind_of?(Hash) ? args.pop : {}]
  end

  def day_map_from_options(options)
    #puts "Debug: language=#{DayRange.language_from_options(options)}"
          #puts " map=#{@@day_maps[DayRange.language_from_options(options)]}"
    DayRange.get_option(:day_map, options,
@@day_maps[DayRange.language_from_options(options)])
  end

  def week_start_from_options(options, day_map)
    week_start = DayRange.get_option(:week_start, options, 1)
    week_start = day_map[week_start] || week_start
    DayRange.check_arg((1..7) === week_start,":week_start must be in the
range (1..7)")
    week_start
  end

end

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/