chronic_2011 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
 - data/HISTORY.md +4 -0
 - data/LICENSE +21 -0
 - data/README.md +180 -0
 - data/Rakefile +46 -0
 - data/chronic.gemspec +18 -0
 - data/lib/chronic.rb +117 -0
 - data/lib/chronic/chronic.rb +346 -0
 - data/lib/chronic/grabber.rb +33 -0
 - data/lib/chronic/handler.rb +88 -0
 - data/lib/chronic/handlers.rb +553 -0
 - data/lib/chronic/mini_date.rb +38 -0
 - data/lib/chronic/numerizer.rb +121 -0
 - data/lib/chronic/ordinal.rb +47 -0
 - data/lib/chronic/pointer.rb +32 -0
 - data/lib/chronic/repeater.rb +142 -0
 - data/lib/chronic/repeaters/repeater_day.rb +53 -0
 - data/lib/chronic/repeaters/repeater_day_name.rb +52 -0
 - data/lib/chronic/repeaters/repeater_day_portion.rb +108 -0
 - data/lib/chronic/repeaters/repeater_fortnight.rb +71 -0
 - data/lib/chronic/repeaters/repeater_hour.rb +58 -0
 - data/lib/chronic/repeaters/repeater_minute.rb +58 -0
 - data/lib/chronic/repeaters/repeater_month.rb +79 -0
 - data/lib/chronic/repeaters/repeater_month_name.rb +94 -0
 - data/lib/chronic/repeaters/repeater_season.rb +109 -0
 - data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
 - data/lib/chronic/repeaters/repeater_second.rb +42 -0
 - data/lib/chronic/repeaters/repeater_time.rb +128 -0
 - data/lib/chronic/repeaters/repeater_week.rb +74 -0
 - data/lib/chronic/repeaters/repeater_weekday.rb +85 -0
 - data/lib/chronic/repeaters/repeater_weekend.rb +66 -0
 - data/lib/chronic/repeaters/repeater_year.rb +77 -0
 - data/lib/chronic/scalar.rb +116 -0
 - data/lib/chronic/season.rb +26 -0
 - data/lib/chronic/separator.rb +94 -0
 - data/lib/chronic/span.rb +31 -0
 - data/lib/chronic/tag.rb +36 -0
 - data/lib/chronic/time_zone.rb +32 -0
 - data/lib/chronic/token.rb +47 -0
 - data/test/helper.rb +12 -0
 - data/test/test_chronic.rb +148 -0
 - data/test/test_daylight_savings.rb +118 -0
 - data/test/test_handler.rb +104 -0
 - data/test/test_mini_date.rb +32 -0
 - data/test/test_numerizer.rb +72 -0
 - data/test/test_parsing.rb +977 -0
 - data/test/test_repeater_day_name.rb +51 -0
 - data/test/test_repeater_day_portion.rb +254 -0
 - data/test/test_repeater_fortnight.rb +62 -0
 - data/test/test_repeater_hour.rb +68 -0
 - data/test/test_repeater_minute.rb +34 -0
 - data/test/test_repeater_month.rb +50 -0
 - data/test/test_repeater_month_name.rb +56 -0
 - data/test/test_repeater_season.rb +40 -0
 - data/test/test_repeater_time.rb +70 -0
 - data/test/test_repeater_week.rb +62 -0
 - data/test/test_repeater_weekday.rb +55 -0
 - data/test/test_repeater_weekend.rb +74 -0
 - data/test/test_repeater_year.rb +69 -0
 - data/test/test_span.rb +23 -0
 - data/test/test_token.rb +25 -0
 - metadata +156 -0
 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Chronic
         
     | 
| 
      
 2 
     | 
    
         
            +
              class MiniDate
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :month, :day
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def self.from_time(time)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  new(time.month, time.day)
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(month, day)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  unless (1..12).include?(month)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    raise ArgumentError, "1..12 are valid months"
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  @month = month
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @day = day
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def is_between?(md_start, md_end)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  return false if (@month == md_start.month && @month == md_end.month) &&
         
     | 
| 
      
 20 
     | 
    
         
            +
                                  (@day < md_start.day || @day > md_end.day)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return true if (@month == md_start.month && @day >= md_start.day) ||
         
     | 
| 
      
 22 
     | 
    
         
            +
                                 (@month == md_end.month && @day <= md_end.day)
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  i = (md_start.month % 12) + 1
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  until i == md_end.month
         
     | 
| 
      
 27 
     | 
    
         
            +
                    return true if @month == i
         
     | 
| 
      
 28 
     | 
    
         
            +
                    i = (i % 12) + 1
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def equals?(other)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @month == other.month and @day == other.day
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,121 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'strscan'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Chronic
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Numerizer
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                DIRECT_NUMS = [
         
     | 
| 
      
 7 
     | 
    
         
            +
                  ['eleven', '11'],
         
     | 
| 
      
 8 
     | 
    
         
            +
                  ['twelve', '12'],
         
     | 
| 
      
 9 
     | 
    
         
            +
                  ['thirteen', '13'],
         
     | 
| 
      
 10 
     | 
    
         
            +
                  ['fourteen', '14'],
         
     | 
| 
      
 11 
     | 
    
         
            +
                  ['fifteen', '15'],
         
     | 
| 
      
 12 
     | 
    
         
            +
                  ['sixteen', '16'],
         
     | 
| 
      
 13 
     | 
    
         
            +
                  ['seventeen', '17'],
         
     | 
| 
      
 14 
     | 
    
         
            +
                  ['eighteen', '18'],
         
     | 
| 
      
 15 
     | 
    
         
            +
                  ['nineteen', '19'],
         
     | 
| 
      
 16 
     | 
    
         
            +
                  ['ninteen', '19'], # Common mis-spelling
         
     | 
| 
      
 17 
     | 
    
         
            +
                  ['zero', '0'],
         
     | 
| 
      
 18 
     | 
    
         
            +
                  ['one', '1'],
         
     | 
| 
      
 19 
     | 
    
         
            +
                  ['two', '2'],
         
     | 
| 
      
 20 
     | 
    
         
            +
                  ['three', '3'],
         
     | 
| 
      
 21 
     | 
    
         
            +
                  ['four(\W|$)', '4\1'],  # The weird regex is so that it matches four but not fourty
         
     | 
| 
      
 22 
     | 
    
         
            +
                  ['five', '5'],
         
     | 
| 
      
 23 
     | 
    
         
            +
                  ['six(\W|$)', '6\1'],
         
     | 
| 
      
 24 
     | 
    
         
            +
                  ['seven(\W|$)', '7\1'],
         
     | 
| 
      
 25 
     | 
    
         
            +
                  ['eight(\W|$)', '8\1'],
         
     | 
| 
      
 26 
     | 
    
         
            +
                  ['nine(\W|$)', '9\1'],
         
     | 
| 
      
 27 
     | 
    
         
            +
                  ['ten', '10'],
         
     | 
| 
      
 28 
     | 
    
         
            +
                  ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
         
     | 
| 
      
 29 
     | 
    
         
            +
                ]
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                ORDINALS = [
         
     | 
| 
      
 32 
     | 
    
         
            +
                  ['first', '1'],
         
     | 
| 
      
 33 
     | 
    
         
            +
                  ['third', '3'],
         
     | 
| 
      
 34 
     | 
    
         
            +
                  ['fourth', '4'],
         
     | 
| 
      
 35 
     | 
    
         
            +
                  ['fifth', '5'],
         
     | 
| 
      
 36 
     | 
    
         
            +
                  ['sixth', '6'],
         
     | 
| 
      
 37 
     | 
    
         
            +
                  ['seventh', '7'],
         
     | 
| 
      
 38 
     | 
    
         
            +
                  ['eighth', '8'],
         
     | 
| 
      
 39 
     | 
    
         
            +
                  ['ninth', '9'],
         
     | 
| 
      
 40 
     | 
    
         
            +
                  ['tenth', '10']
         
     | 
| 
      
 41 
     | 
    
         
            +
                ]
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                TEN_PREFIXES = [
         
     | 
| 
      
 44 
     | 
    
         
            +
                  ['twenty', 20],
         
     | 
| 
      
 45 
     | 
    
         
            +
                  ['thirty', 30],
         
     | 
| 
      
 46 
     | 
    
         
            +
                  ['forty', 40],
         
     | 
| 
      
 47 
     | 
    
         
            +
                  ['fourty', 40], # Common mis-spelling
         
     | 
| 
      
 48 
     | 
    
         
            +
                  ['fifty', 50],
         
     | 
| 
      
 49 
     | 
    
         
            +
                  ['sixty', 60],
         
     | 
| 
      
 50 
     | 
    
         
            +
                  ['seventy', 70],
         
     | 
| 
      
 51 
     | 
    
         
            +
                  ['eighty', 80],
         
     | 
| 
      
 52 
     | 
    
         
            +
                  ['ninety', 90]
         
     | 
| 
      
 53 
     | 
    
         
            +
                ]
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                BIG_PREFIXES = [
         
     | 
| 
      
 56 
     | 
    
         
            +
                  ['hundred', 100],
         
     | 
| 
      
 57 
     | 
    
         
            +
                  ['thousand', 1000],
         
     | 
| 
      
 58 
     | 
    
         
            +
                  ['million', 1_000_000],
         
     | 
| 
      
 59 
     | 
    
         
            +
                  ['billion', 1_000_000_000],
         
     | 
| 
      
 60 
     | 
    
         
            +
                  ['trillion', 1_000_000_000_000],
         
     | 
| 
      
 61 
     | 
    
         
            +
                ]
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def self.numerize(string)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  string = string.dup
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  # preprocess
         
     | 
| 
      
 67 
     | 
    
         
            +
                  string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
         
     | 
| 
      
 68 
     | 
    
         
            +
                  string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  # easy/direct replacements
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  DIRECT_NUMS.each do |dn|
         
     | 
| 
      
 73 
     | 
    
         
            +
                    string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  ORDINALS.each do |on|
         
     | 
| 
      
 77 
     | 
    
         
            +
                    string.gsub!(/#{on[0]}/i, '<num>' + on[1] + on[0][-2, 2])
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  # ten, twenty, etc.
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  TEN_PREFIXES.each do |tp|
         
     | 
| 
      
 83 
     | 
    
         
            +
                    string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  TEN_PREFIXES.each do |tp|
         
     | 
| 
      
 87 
     | 
    
         
            +
                    string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  # hundreds, thousands, millions, etc.
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  BIG_PREFIXES.each do |bp|
         
     | 
| 
      
 93 
     | 
    
         
            +
                    string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
         
     | 
| 
      
 94 
     | 
    
         
            +
                    andition(string)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  # fractional addition
         
     | 
| 
      
 98 
     | 
    
         
            +
                  # I'm not combining this with the previous block as using float addition complicates the strings
         
     | 
| 
      
 99 
     | 
    
         
            +
                  # (with extraneous .0's and such )
         
     | 
| 
      
 100 
     | 
    
         
            +
                  string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  string.gsub(/<num>/, '')
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 106 
     | 
    
         
            +
                  private
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  def andition(string)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    sc = StringScanner.new(string)
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                    while sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i)
         
     | 
| 
      
 112 
     | 
    
         
            +
                      if sc[2] =~ /and/ || sc[1].size > sc[3].size
         
     | 
| 
      
 113 
     | 
    
         
            +
                        string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
         
     | 
| 
      
 114 
     | 
    
         
            +
                        sc.reset
         
     | 
| 
      
 115 
     | 
    
         
            +
                      end
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
              end
         
     | 
| 
      
 121 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Chronic
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Ordinal < Tag
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                # Scan an Array of Token objects and apply any necessary Ordinal
         
     | 
| 
      
 5 
     | 
    
         
            +
                # tags to each token.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # tokens - An Array of tokens to scan.
         
     | 
| 
      
 8 
     | 
    
         
            +
                # options - The Hash of options specified in Chronic::parse.
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # Returns an Array of tokens.
         
     | 
| 
      
 11 
     | 
    
         
            +
                def self.scan(tokens, options)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  tokens.each do |token|
         
     | 
| 
      
 13 
     | 
    
         
            +
                    if t = scan_for_ordinals(token) then token.tag(t) end
         
     | 
| 
      
 14 
     | 
    
         
            +
                    if t = scan_for_days(token) then token.tag(t) end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
      
 20 
     | 
    
         
            +
                # Returns a new Ordinal object.
         
     | 
| 
      
 21 
     | 
    
         
            +
                def self.scan_for_ordinals(token)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                # Returns a new Ordinal object.
         
     | 
| 
      
 28 
     | 
    
         
            +
                def self.scan_for_days(token)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  if token.word =~ /^(\d*)(st|nd|rd|th)$/
         
     | 
| 
      
 30 
     | 
    
         
            +
                    unless $1.to_i > 31 || $1.to_i < 1
         
     | 
| 
      
 31 
     | 
    
         
            +
                      OrdinalDay.new(token.word.to_i)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 37 
     | 
    
         
            +
                  'ordinal'
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              class OrdinalDay < Ordinal #:nodoc:
         
     | 
| 
      
 42 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 43 
     | 
    
         
            +
                  super << '-day-' << @type.to_s
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Chronic
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Pointer < Tag
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                # Scan an Array of Token objects and apply any necessary Pointer
         
     | 
| 
      
 5 
     | 
    
         
            +
                # tags to each token.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # tokens - An Array of tokens to scan.
         
     | 
| 
      
 8 
     | 
    
         
            +
                # options - The Hash of options specified in Chronic::parse.
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # Returns an Array of tokens.
         
     | 
| 
      
 11 
     | 
    
         
            +
                def self.scan(tokens, options)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  tokens.each do |token|
         
     | 
| 
      
 13 
     | 
    
         
            +
                    if t = scan_for_all(token) then token.tag(t) end
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # Returns a new Pointer object.
         
     | 
| 
      
 20 
     | 
    
         
            +
                def self.scan_for_all(token)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  scan_for token, self,
         
     | 
| 
      
 22 
     | 
    
         
            +
                  {
         
     | 
| 
      
 23 
     | 
    
         
            +
                    /\bpast\b/ => :past,
         
     | 
| 
      
 24 
     | 
    
         
            +
                    /\b(?:future|in)\b/ => :future,
         
     | 
| 
      
 25 
     | 
    
         
            +
                  }
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 29 
     | 
    
         
            +
                  'pointer-' << @type.to_s
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,142 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Chronic
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Repeater < Tag
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                # Scan an Array of Token objects and apply any necessary Repeater
         
     | 
| 
      
 5 
     | 
    
         
            +
                # tags to each token.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # tokens - An Array of tokens to scan.
         
     | 
| 
      
 8 
     | 
    
         
            +
                # options - The Hash of options specified in Chronic::parse.
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # Returns an Array of tokens.
         
     | 
| 
      
 11 
     | 
    
         
            +
                def self.scan(tokens, options)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  tokens.each do |token|
         
     | 
| 
      
 13 
     | 
    
         
            +
                    if t = scan_for_season_names(token) then token.tag(t); next end
         
     | 
| 
      
 14 
     | 
    
         
            +
                    if t = scan_for_month_names(token) then token.tag(t); next end
         
     | 
| 
      
 15 
     | 
    
         
            +
                    if t = scan_for_day_names(token) then token.tag(t); next end
         
     | 
| 
      
 16 
     | 
    
         
            +
                    if t = scan_for_day_portions(token) then token.tag(t); next end
         
     | 
| 
      
 17 
     | 
    
         
            +
                    if t = scan_for_times(token) then token.tag(t); next end
         
     | 
| 
      
 18 
     | 
    
         
            +
                    if t = scan_for_units(token) then token.tag(t); next end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                # Returns a new Repeater object.
         
     | 
| 
      
 25 
     | 
    
         
            +
                def self.scan_for_season_names(token)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  scan_for token, RepeaterSeasonName,
         
     | 
| 
      
 27 
     | 
    
         
            +
                  {
         
     | 
| 
      
 28 
     | 
    
         
            +
                    /^springs?$/ => :spring,
         
     | 
| 
      
 29 
     | 
    
         
            +
                    /^summers?$/ => :summer,
         
     | 
| 
      
 30 
     | 
    
         
            +
                    /^(autumn)|(fall)s?$/ => :autumn,
         
     | 
| 
      
 31 
     | 
    
         
            +
                    /^winters?$/ => :winter
         
     | 
| 
      
 32 
     | 
    
         
            +
                  }
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 36 
     | 
    
         
            +
                #
         
     | 
| 
      
 37 
     | 
    
         
            +
                # Returns a new Repeater object.
         
     | 
| 
      
 38 
     | 
    
         
            +
                def self.scan_for_month_names(token)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  scan_for token, RepeaterMonthName,
         
     | 
| 
      
 40 
     | 
    
         
            +
                  {
         
     | 
| 
      
 41 
     | 
    
         
            +
                    /^jan\.?(uary)?$/ => :january,
         
     | 
| 
      
 42 
     | 
    
         
            +
                    /^feb\.?(ruary)?$/ => :february,
         
     | 
| 
      
 43 
     | 
    
         
            +
                    /^mar\.?(ch)?$/ => :march,
         
     | 
| 
      
 44 
     | 
    
         
            +
                    /^apr\.?(il)?$/ => :april,
         
     | 
| 
      
 45 
     | 
    
         
            +
                    /^may$/ => :may,
         
     | 
| 
      
 46 
     | 
    
         
            +
                    /^jun\.?e?$/ => :june,
         
     | 
| 
      
 47 
     | 
    
         
            +
                    /^jul\.?y?$/ => :july,
         
     | 
| 
      
 48 
     | 
    
         
            +
                    /^aug\.?(ust)?$/ => :august,
         
     | 
| 
      
 49 
     | 
    
         
            +
                    /^sep\.?(t\.?|tember)?$/ => :september,
         
     | 
| 
      
 50 
     | 
    
         
            +
                    /^oct\.?(ober)?$/ => :october,
         
     | 
| 
      
 51 
     | 
    
         
            +
                    /^nov\.?(ember)?$/ => :november,
         
     | 
| 
      
 52 
     | 
    
         
            +
                    /^dec\.?(ember)?$/ => :december
         
     | 
| 
      
 53 
     | 
    
         
            +
                  }
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 57 
     | 
    
         
            +
                #
         
     | 
| 
      
 58 
     | 
    
         
            +
                # Returns a new Repeater object.
         
     | 
| 
      
 59 
     | 
    
         
            +
                def self.scan_for_day_names(token)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  scan_for token, RepeaterDayName,
         
     | 
| 
      
 61 
     | 
    
         
            +
                  {
         
     | 
| 
      
 62 
     | 
    
         
            +
                    /^m[ou]n(day)?$/ => :monday,
         
     | 
| 
      
 63 
     | 
    
         
            +
                    /^t(ue|eu|oo|u|)s?(day)?$/ => :tuesday,
         
     | 
| 
      
 64 
     | 
    
         
            +
                    /^we(d|dnes|nds|nns)(day)?$/ => :wednesday,
         
     | 
| 
      
 65 
     | 
    
         
            +
                    /^th(u|ur|urs|ers)(day)?$/ => :thursday,
         
     | 
| 
      
 66 
     | 
    
         
            +
                    /^fr[iy](day)?$/ => :friday,
         
     | 
| 
      
 67 
     | 
    
         
            +
                    /^sat(t?[ue]rday)?$/ => :saturday,
         
     | 
| 
      
 68 
     | 
    
         
            +
                    /^su[nm](day)?$/ => :sunday
         
     | 
| 
      
 69 
     | 
    
         
            +
                  }
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 73 
     | 
    
         
            +
                #
         
     | 
| 
      
 74 
     | 
    
         
            +
                # Returns a new Repeater object.
         
     | 
| 
      
 75 
     | 
    
         
            +
                def self.scan_for_day_portions(token)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  scan_for token, RepeaterDayPortion,
         
     | 
| 
      
 77 
     | 
    
         
            +
                  {
         
     | 
| 
      
 78 
     | 
    
         
            +
                    /^ams?$/ => :am,
         
     | 
| 
      
 79 
     | 
    
         
            +
                    /^pms?$/ => :pm,
         
     | 
| 
      
 80 
     | 
    
         
            +
                    /^mornings?$/ => :morning,
         
     | 
| 
      
 81 
     | 
    
         
            +
                    /^afternoons?$/ => :afternoon,
         
     | 
| 
      
 82 
     | 
    
         
            +
                    /^evenings?$/ => :evening,
         
     | 
| 
      
 83 
     | 
    
         
            +
                    /^(night|nite)s?$/ => :night
         
     | 
| 
      
 84 
     | 
    
         
            +
                  }
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 88 
     | 
    
         
            +
                #
         
     | 
| 
      
 89 
     | 
    
         
            +
                # Returns a new Repeater object.
         
     | 
| 
      
 90 
     | 
    
         
            +
                def self.scan_for_times(token)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  scan_for token, RepeaterTime, /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                # token - The Token object we want to scan.
         
     | 
| 
      
 95 
     | 
    
         
            +
                #
         
     | 
| 
      
 96 
     | 
    
         
            +
                # Returns a new Repeater object.
         
     | 
| 
      
 97 
     | 
    
         
            +
                def self.scan_for_units(token)
         
     | 
| 
      
 98 
     | 
    
         
            +
                  {
         
     | 
| 
      
 99 
     | 
    
         
            +
                    /^years?$/ => :year,
         
     | 
| 
      
 100 
     | 
    
         
            +
                    /^seasons?$/ => :season,
         
     | 
| 
      
 101 
     | 
    
         
            +
                    /^months?$/ => :month,
         
     | 
| 
      
 102 
     | 
    
         
            +
                    /^fortnights?$/ => :fortnight,
         
     | 
| 
      
 103 
     | 
    
         
            +
                    /^weeks?$/ => :week,
         
     | 
| 
      
 104 
     | 
    
         
            +
                    /^weekends?$/ => :weekend,
         
     | 
| 
      
 105 
     | 
    
         
            +
                    /^(week|business)days?$/ => :weekday,
         
     | 
| 
      
 106 
     | 
    
         
            +
                    /^days?$/ => :day,
         
     | 
| 
      
 107 
     | 
    
         
            +
                    /^hours?$/ => :hour,
         
     | 
| 
      
 108 
     | 
    
         
            +
                    /^minutes?$/ => :minute,
         
     | 
| 
      
 109 
     | 
    
         
            +
                    /^seconds?$/ => :second
         
     | 
| 
      
 110 
     | 
    
         
            +
                  }.each do |item, symbol|
         
     | 
| 
      
 111 
     | 
    
         
            +
                    if item =~ token.word
         
     | 
| 
      
 112 
     | 
    
         
            +
                      klass_name = 'Repeater' + symbol.to_s.capitalize
         
     | 
| 
      
 113 
     | 
    
         
            +
                      klass = Chronic.const_get(klass_name)
         
     | 
| 
      
 114 
     | 
    
         
            +
                      return klass.new(symbol)
         
     | 
| 
      
 115 
     | 
    
         
            +
                    end
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                def <=>(other)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  width <=> other.width
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                # returns the width (in seconds or months) of this repeatable.
         
     | 
| 
      
 125 
     | 
    
         
            +
                def width
         
     | 
| 
      
 126 
     | 
    
         
            +
                  raise("Repeater#width must be overridden in subclasses")
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                # returns the next occurance of this repeatable.
         
     | 
| 
      
 130 
     | 
    
         
            +
                def next(pointer)
         
     | 
| 
      
 131 
     | 
    
         
            +
                  raise("Start point must be set before calling #next") unless @now
         
     | 
| 
      
 132 
     | 
    
         
            +
                end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                def this(pointer)
         
     | 
| 
      
 135 
     | 
    
         
            +
                  raise("Start point must be set before calling #this") unless @now
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 139 
     | 
    
         
            +
                  'repeater'
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
              end
         
     | 
| 
      
 142 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Chronic
         
     | 
| 
      
 2 
     | 
    
         
            +
              class RepeaterDay < Repeater #:nodoc:
         
     | 
| 
      
 3 
     | 
    
         
            +
                DAY_SECONDS = 86_400 # (24 * 60 * 60)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(type)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  super
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def next(pointer)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  super
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  if !@current_day_start
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @current_day_start = Chronic.time_class.local(@now.year, @now.month, @now.day)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  direction = pointer == :future ? 1 : -1
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @current_day_start += direction * DAY_SECONDS
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def this(pointer = :future)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  super
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  case pointer
         
     | 
| 
      
 26 
     | 
    
         
            +
                  when :future
         
     | 
| 
      
 27 
     | 
    
         
            +
                    day_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
         
     | 
| 
      
 29 
     | 
    
         
            +
                  when :past
         
     | 
| 
      
 30 
     | 
    
         
            +
                    day_begin = Chronic.construct(@now.year, @now.month, @now.day)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    day_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  when :none
         
     | 
| 
      
 33 
     | 
    
         
            +
                    day_begin = Chronic.construct(@now.year, @now.month, @now.day)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  Span.new(day_begin, day_end)
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def offset(span, amount, pointer)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  direction = pointer == :future ? 1 : -1
         
     | 
| 
      
 42 
     | 
    
         
            +
                  span + direction * amount * DAY_SECONDS
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def width
         
     | 
| 
      
 46 
     | 
    
         
            +
                  DAY_SECONDS
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 50 
     | 
    
         
            +
                  super << '-day'
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Chronic
         
     | 
| 
      
 2 
     | 
    
         
            +
              class RepeaterDayName < Repeater #:nodoc:
         
     | 
| 
      
 3 
     | 
    
         
            +
                DAY_SECONDS = 86400 # (24 * 60 * 60)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(type)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  super
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def next(pointer)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  super
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  direction = pointer == :future ? 1 : -1
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  if !@current_date
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @current_date = Date.new(@now.year, @now.month, @now.day)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @current_date += direction
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    day_num = symbol_to_number(@type)
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    while @current_date.wday != day_num
         
     | 
| 
      
 21 
     | 
    
         
            +
                      @current_date += direction
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  else
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @current_date += direction * 7
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  next_date = @current_date.succ
         
     | 
| 
      
 27 
     | 
    
         
            +
                  Span.new(Chronic.construct(@current_date.year, @current_date.month, @current_date.day), Chronic.construct(next_date.year, next_date.month, next_date.day))
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def this(pointer = :future)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  super
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  pointer = :future if pointer == :none
         
     | 
| 
      
 34 
     | 
    
         
            +
                  self.next(pointer)
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def width
         
     | 
| 
      
 38 
     | 
    
         
            +
                  DAY_SECONDS
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 42 
     | 
    
         
            +
                  super << '-dayname-' << @type.to_s
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                private
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def symbol_to_number(sym)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
         
     | 
| 
      
 49 
     | 
    
         
            +
                  lookup[sym] || raise("Invalid symbol specified")
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     |