start adding base classes for date_parser and break out the locale-specific classes
authorChuck Hagenbuch <chuck@horde.org>
Tue, 16 Dec 2008 05:22:16 +0000 (00:22 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Tue, 16 Dec 2008 05:22:16 +0000 (00:22 -0500)
53 files changed:
framework/Horde_Date_Parser/lib/Horde/Date/Parser.php
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Grabber.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Handlers.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Ordinal.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Parser.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Pointer.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeater.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Day.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayName.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayPortion.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Fortnight.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Hour.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Minute.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Month.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/MonthName.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Season.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/SeasonName.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Second.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Time.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Week.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Weekend.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Year.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Scalar.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Separator.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/TimeZone.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php [new file with mode: 0644]
framework/Horde_Date_Parser/lib/Horde/Date/Span.php [new file with mode: 0644]

index 477240d..75777a2 100644 (file)
@@ -1,53 +1,29 @@
+<?php
+/**
+ *
+ */
+class Horde_Date_Parser
+{
+    public static function factory($args = array())
+    {
+        $locale = isset($args['locale']) ? $args['locale'] : null;
+        if ($locale && strtolower($locale) != 'base') {
+            $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
+            $class = 'Horde_Date_Parser_Locale_' . $locale;
+            if (class_exists($class)) {
+                return new $class($args);
+            }
 
-alias p_orig p
+            $language = array_shift(explode('_', $locale));
+            if ($language != $locale) {
+                $class = 'Horde_Date_Parser_Locale_' . $language;
+                if (class_exists($class)) {
+                    return new $class($args);
+                }
+            }
+        }
 
-def p(val)
-  p_orig val
-  puts
-end
+        return new Horde_Date_Parser_Locale_Base($args);
+    }
 
-class Time
-  def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
-    if second >= 60
-      minute += second / 60
-      second = second % 60
-    end
-    
-    if minute >= 60
-      hour += minute / 60
-      minute = minute % 60
-    end
-    
-    if hour >= 24
-      day += hour / 24
-      hour = hour % 24
-    end
-    
-    # determine if there is a day overflow. this is complicated by our crappy calendar
-    # system (non-constant number of days per month)
-    day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
-    if day > 28
-      # no month ever has fewer than 28 days, so only do this if necessary
-      leap_year = (year % 4 == 0) && !(year % 100 == 0)
-      leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-      common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-      days_this_month = leap_year ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
-      if day > days_this_month
-        month += day / days_this_month
-        day = day % days_this_month
-      end
-    end
-    
-    if month > 12
-      if month % 12 == 0
-        year += (month - 12) / 12
-        month = 12
-      else
-        year += month / 12
-        month = month % 12
-      end
-    end
-    
-    Time.local(year, month, day, hour, minute, second)
-  end
-end
+}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php
new file mode 100644 (file)
index 0000000..c6a8ac0
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+class Horde_Date_Parser_Exception extends Exception
+{
+}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Grabber.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Grabber.php
deleted file mode 100644 (file)
index 4162a26..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#module Chronic
-
-  class Chronic::Grabber < Chronic::Tag #:nodoc:
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-    
-    def self.scan_for_all(token)
-      scanner = {/last/ => :last,
-                 /this/ => :this,
-                 /next/ => :next}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'grabber-' << @type.to_s
-    end
-  end
-
-#end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Handlers.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Handlers.php
deleted file mode 100644 (file)
index 551d632..0000000
+++ /dev/null
@@ -1,469 +0,0 @@
-module Chronic
-
-       class << self
-         
-         def definitions #:nodoc:
-           @definitions ||= 
-      {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
-        
-       :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
-                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
-                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
-                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
-                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
-                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
-                 
-       # tonight at 7pm
-       :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
-                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
-                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
-                   
-       # 3 weeks from now, in 2 months
-       :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
-                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
-                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
-                  
-       # 3rd week in march
-       :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
-                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
-      }
-    end
-    
-    def tokens_to_span(tokens, options) #:nodoc:                   
-      # maybe it's a specific date
-      
-      self.definitions[:date].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-date" if Chronic.debug
-          good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-            
-      # I guess it's not a specific date, maybe it's just an anchor
-            
-      self.definitions[:anchor].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-anchor" if Chronic.debug
-          good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-            
-      # not an anchor, perhaps it's an arrow
-      
-      self.definitions[:arrow].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-arrow" if Chronic.debug
-          good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-      
-      # not an arrow, let's hope it's a narrow
-      
-      self.definitions[:narrow].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-narrow" if Chronic.debug
-          #good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, tokens, options)
-        end
-      end
-      
-      # I guess you're out of luck!
-      puts "-none" if Chronic.debug
-      return nil
-    end
-    
-    #--------------
-    
-    def day_or_time(day_start, time_tokens, options)
-      outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
-      
-      if !time_tokens.empty?
-        @now = outer_span.begin
-        time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
-        return time
-      else
-        return outer_span
-      end
-    end
-    
-    #--------------
-    
-    def handle_m_d(month, day, time_tokens, options) #:nodoc:
-      month.start = @now
-      span = month.this(options[:context])
-      
-      day_start = Time.local(span.begin.year, span.begin.month, day)
-      
-      day_or_time(day_start, time_tokens, options)
-    end
-    
-    def handle_rmn_sd(tokens, options) #:nodoc:
-      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
-    end
-    
-    def handle_rmn_od(tokens, options) #:nodoc:
-      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
-    end
-    
-    def handle_rmn_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(RepeaterMonthName).index
-      year = tokens[1].get_tag(ScalarYear).type
-      
-      if month == 12
-        next_month_year = year + 1
-        next_month_month = 1
-      else
-        next_month_year = year
-        next_month_month = month + 1
-      end
-      
-      begin
-        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
-      month = tokens[1].get_tag(RepeaterMonthName).index
-      day = tokens[2].get_tag(ScalarDay).type
-      year = tokens[5].get_tag(ScalarYear).type
-      
-      begin
-        day_start = Time.local(year, month, day)
-        day_or_time(day_start, [tokens[3]], options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_rmn_sd_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(RepeaterMonthName).index
-      day = tokens[1].get_tag(ScalarDay).type
-      year = tokens[2].get_tag(ScalarYear).type
-      
-      time_tokens = tokens.last(tokens.size - 3)
-      
-      begin
-        day_start = Time.local(year, month, day)
-        day_or_time(day_start, time_tokens, options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_sd_rmn_sy(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sm_sd_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(ScalarMonth).type
-      day = tokens[1].get_tag(ScalarDay).type
-      year = tokens[2].get_tag(ScalarYear).type
-      
-      time_tokens = tokens.last(tokens.size - 3)
-      
-      begin
-        day_start = Time.local(year, month, day) #:nodoc:
-        day_or_time(day_start, time_tokens, options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_sd_sm_sy(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sy_sm_sd(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[2], tokens[0]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sm_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(ScalarMonth).type
-      year = tokens[1].get_tag(ScalarYear).type
-      
-      if month == 12
-        next_month_year = year + 1
-        next_month_month = 1
-      else
-        next_month_year = year
-        next_month_month = month + 1
-      end
-      
-      begin
-        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    # anchors
-    
-    def handle_r(tokens, options) #:nodoc:
-      dd_tokens = dealias_and_disambiguate_times(tokens, options)
-      self.get_anchor(dd_tokens, options)
-    end
-    
-    def handle_r_g_r(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      self.handle_r(new_tokens, options)
-    end
-    
-    # arrows
-    
-    def handle_srp(tokens, span, options) #:nodoc:
-      distance = tokens[0].get_tag(Scalar).type
-      repeater = tokens[1].get_tag(Repeater)
-      pointer = tokens[2].get_tag(Pointer).type
-      
-      repeater.offset(span, distance, pointer)
-    end
-    
-    def handle_s_r_p(tokens, options) #:nodoc:
-      repeater = tokens[1].get_tag(Repeater)
-            
-      # span = 
-      # case true
-      # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
-      #   self.parse("this hour", :guess => false, :now => @now)
-      # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
-      #   self.parse("this minute", :guess => false, :now => @now)
-      # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
-      #   self.parse("this second", :guess => false, :now => @now)
-      # else
-      #   raise(ChronicPain, "Invalid repeater: #{repeater.class}")
-      # end
-      
-      span = self.parse("this second", :guess => false, :now => @now)
-      
-      self.handle_srp(tokens, span, options)
-    end
-    
-    def handle_p_s_r(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[2], tokens[0]]
-      self.handle_s_r_p(new_tokens, options)
-    end
-    
-    def handle_s_r_p_a(tokens, options) #:nodoc:
-      anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
-      self.handle_srp(tokens, anchor_span, options)
-    end
-    
-    # narrows
-    
-    def handle_orr(tokens, outer_span, options) #:nodoc:
-      repeater = tokens[1].get_tag(Repeater)
-      repeater.start = outer_span.begin - 1
-      ordinal = tokens[0].get_tag(Ordinal).type
-      span = nil
-      ordinal.times do
-        span = repeater.next(:future)
-        if span.begin > outer_span.end
-          span = nil
-          break
-        end
-      end
-      span
-    end
-    
-    def handle_o_r_s_r(tokens, options) #:nodoc:
-      outer_span = get_anchor([tokens[3]], options)
-      handle_orr(tokens[0..1], outer_span, options)
-    end
-    
-    def handle_o_r_g_r(tokens, options) #:nodoc:
-      outer_span = get_anchor(tokens[2..3], options)
-      handle_orr(tokens[0..1], outer_span, options)
-    end
-    
-    # support methods
-    
-    def get_anchor(tokens, options) #:nodoc:
-      grabber = Grabber.new(:this)
-      pointer = :future
-      
-      repeaters = self.get_repeaters(tokens)
-      repeaters.size.times { tokens.pop }
-                    
-      if tokens.first && tokens.first.get_tag(Grabber)
-        grabber = tokens.first.get_tag(Grabber)
-        tokens.pop
-      end
-      
-      head = repeaters.shift
-      head.start = @now
-                  
-      case grabber.type
-        when :last
-          outer_span = head.next(:past)
-        when :this
-          if repeaters.size > 0
-            outer_span = head.this(:none)
-          else
-            outer_span = head.this(options[:context])
-          end
-        when :next
-          outer_span = head.next(:future)
-        else raise(ChronicPain, "Invalid grabber")
-      end
-      
-      puts "--#{outer_span}" if Chronic.debug
-      anchor = find_within(repeaters, outer_span, pointer)
-    end
-    
-    def get_repeaters(tokens) #:nodoc:
-      repeaters = []
-                 tokens.each do |token|
-                   if t = token.get_tag(Repeater)
-          repeaters << t
-        end
-      end
-      repeaters.sort.reverse
-    end
-    
-    # Recursively finds repeaters within other repeaters.
-    # Returns a Span representing the innermost time span
-    # or nil if no repeater union could be found
-    def find_within(tags, span, pointer) #:nodoc:
-      puts "--#{span}" if Chronic.debug
-      return span if tags.empty?
-      
-      head, *rest = tags
-      head.start = pointer == :future ? span.begin : span.end
-      h = head.this(:none)
-            
-      if span.include?(h.begin) || span.include?(h.end)
-        return find_within(rest, h, pointer)
-      else
-        return nil
-      end
-    end
-    
-    def dealias_and_disambiguate_times(tokens, options) #:nodoc:
-      # handle aliases of am/pm
-      # 5:00 in the morning -> 5:00 am
-      # 7:00 in the evening -> 7:00 pm
-      
-      day_portion_index = nil
-      tokens.each_with_index do |t, i|
-        if t.get_tag(RepeaterDayPortion)
-          day_portion_index = i
-          break
-        end
-      end
-       
-      time_index = nil
-      tokens.each_with_index do |t, i|
-        if t.get_tag(RepeaterTime)
-          time_index = i
-          break
-        end
-      end
-      
-      if (day_portion_index && time_index)
-        t1 = tokens[day_portion_index]
-        t1tag = t1.get_tag(RepeaterDayPortion)
-      
-        if [:morning].include?(t1tag.type)
-          puts '--morning->am' if Chronic.debug
-          t1.untag(RepeaterDayPortion)
-          t1.tag(RepeaterDayPortion.new(:am))
-        elsif [:afternoon, :evening, :night].include?(t1tag.type)
-          puts "--#{t1tag.type}->pm" if Chronic.debug
-          t1.untag(RepeaterDayPortion)
-          t1.tag(RepeaterDayPortion.new(:pm))
-        end
-      end
-      
-      # tokens.each_with_index do |t0, i|
-      #   t1 = tokens[i + 1]
-      #   if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
-      #     if [:morning].include?(t1tag.type)
-      #       puts '--morning->am' if Chronic.debug
-      #       t1.untag(RepeaterDayPortion)
-      #       t1.tag(RepeaterDayPortion.new(:am))
-      #     elsif [:afternoon, :evening, :night].include?(t1tag.type)
-      #       puts "--#{t1tag.type}->pm" if Chronic.debug
-      #       t1.untag(RepeaterDayPortion)
-      #       t1.tag(RepeaterDayPortion.new(:pm))
-      #     end
-      #   end
-      # end
-            
-      # handle ambiguous times if :ambiguous_time_range is specified
-      if options[:ambiguous_time_range] != :none
-        ttokens = []
-        tokens.each_with_index do |t0, i|
-          ttokens << t0
-          t1 = tokens[i + 1]
-          if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
-            distoken = Token.new('disambiguator')
-            distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
-            ttokens << distoken
-          end
-        end
-        tokens = ttokens
-      end
-      
-      tokens
-    end
-    
-  end
-  
-  class Handler #:nodoc:
-    attr_accessor :pattern, :handler_method
-    
-    def initialize(pattern, handler_method)
-      @pattern = pattern
-      @handler_method = handler_method
-    end
-    
-    def constantize(name)
-      camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
-      ::Chronic.module_eval(camel, __FILE__, __LINE__)
-    end
-    
-    def match(tokens, definitions)
-      token_index = 0
-      @pattern.each do |element|
-        name = element.to_s
-        optional = name.reverse[0..0] == '?'
-        name = name.chop if optional
-        if element.instance_of? Symbol
-          klass = constantize(name)
-          match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
-          return false if !match && !optional
-          (token_index += 1; next) if match
-          next if !match && optional
-        elsif element.instance_of? String
-          return true if optional && token_index == tokens.size
-          sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
-          sub_handlers.each do |sub_handler|
-            return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
-          end
-          return false
-        else
-          raise(ChronicPain, "Invalid match type: #{element.class}")
-        end
-      end
-      return false if token_index != tokens.size
-      return true
-    end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php
new file mode 100644 (file)
index 0000000..436250c
--- /dev/null
@@ -0,0 +1,152 @@
+module Chronic
+  class << self
+
+    # Parses a string containing a natural language date or time. If the parser
+    # can find a date or time, either a Time or Chronic::Span will be returned
+    # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
+    # +nil+ will be returned.
+    #
+    # Options are:
+    #
+    # [<tt>:context</tt>]
+    #     <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
+    #
+    #     If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
+    #     and if an ambiguous string is given, it will assume it is in the
+    #     past. Specify <tt>:future</tt> or omit to set a future context.
+    #
+    # [<tt>:now</tt>]
+    #     Time (defaults to Time.now)
+    #
+    #     By setting <tt>:now</tt> to a Time, all computations will be based off
+    #     of that time instead of Time.now
+    #
+    # [<tt>:guess</tt>]
+    #     +true+ or +false+ (defaults to +true+)
+    #
+    #     By default, the parser will guess a single point in time for the
+    #     given date or time. If you'd rather have the entire time span returned,
+    #     set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
+    #
+    # [<tt>:ambiguous_time_range</tt>]
+    #     Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
+    #
+    #     If an Integer is given, ambiguous times (like 5:00) will be
+    #     assumed to be within the range of that time in the AM to that time
+    #     in the PM. For example, if you set it to <tt>7</tt>, then the parser will
+    #     look for the time between 7am and 7pm. In the case of 5:00, it would
+    #     assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
+    #     will be made, and the first matching instance of that time will
+    #     be used.
+    def parse(text, specified_options = {})
+      # get options and set defaults if necessary
+      default_options = {:context => :future,
+                         :now => Time.now,
+                         :guess => true,
+                         :ambiguous_time_range => 6}
+      options = default_options.merge specified_options
+
+      # ensure the specified options are valid
+      specified_options.keys.each do |key|
+        default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
+      end
+      [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
+
+      # store now for later =)
+      @now = options[:now]
+
+      # put the text into a normal format to ease scanning
+      text = self.pre_normalize(text)
+
+      # get base tokens for each word
+      @tokens = self.base_tokenize(text)
+
+      # scan the tokens with each token scanner
+      [Repeater].each do |tokenizer|
+        @tokens = tokenizer.scan(@tokens, options)
+      end
+
+      [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
+        @tokens = tokenizer.scan(@tokens)
+      end
+
+      # strip any non-tagged tokens
+      @tokens = @tokens.select { |token| token.tagged? }
+
+      if Chronic.debug
+        puts "+---------------------------------------------------"
+        puts "| " + @tokens.to_s
+        puts "+---------------------------------------------------"
+      end
+
+      # do the heavy lifting
+      begin
+        span = self.tokens_to_span(@tokens, options)
+      rescue
+        raise
+        return nil
+      end
+
+      # guess a time within a span if required
+      if options[:guess]
+        return self.guess(span)
+      else
+        return span
+      end
+    end
+
+    # Clean up the specified input text by stripping unwanted characters,
+    # converting idioms to their canonical form, converting number words
+    # to numbers (three => 3), and converting ordinal words to numeric
+    # ordinals (third => 3rd)
+    def pre_normalize(text) #:nodoc:
+      normalized_text = text.to_s.downcase
+      normalized_text = numericize_numbers(normalized_text)
+      normalized_text.gsub!(/['"\.]/, '')
+      normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
+      normalized_text.gsub!(/\btoday\b/, 'this day')
+      normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
+      normalized_text.gsub!(/\byesterday\b/, 'last day')
+      normalized_text.gsub!(/\bnoon\b/, '12:00')
+      normalized_text.gsub!(/\bmidnight\b/, '24:00')
+      normalized_text.gsub!(/\bbefore now\b/, 'past')
+      normalized_text.gsub!(/\bnow\b/, 'this second')
+      normalized_text.gsub!(/\b(ago|before)\b/, 'past')
+      normalized_text.gsub!(/\bthis past\b/, 'last')
+      normalized_text.gsub!(/\bthis last\b/, 'last')
+      normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
+      normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
+      normalized_text.gsub!(/\btonight\b/, 'this night')
+      normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
+      normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
+      normalized_text = numericize_ordinals(normalized_text)
+    end
+
+    # Convert number words to numbers (three => 3)
+    def numericize_numbers(text) #:nodoc:
+      Numerizer.numerize(text)
+    end
+
+    # Convert ordinal words to numeric ordinals (third => 3rd)
+    def numericize_ordinals(text) #:nodoc:
+      text
+    end
+
+    # Split the text on spaces and convert each word into
+    # a Token
+    def base_tokenize(text) #:nodoc:
+      text.split(' ').map { |word| Token.new(word) }
+    end
+
+    # Guess a specific time within the given span
+    def guess(span) #:nodoc:
+      return nil if span.nil?
+      if span.width > 1
+        span.begin + (span.width / 2)
+      else
+        span.begin
+      end
+    end
+  end
+
+end
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php
new file mode 100644 (file)
index 0000000..4162a26
--- /dev/null
@@ -0,0 +1,26 @@
+#module Chronic
+
+  class Chronic::Grabber < Chronic::Tag #:nodoc:
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+    
+    def self.scan_for_all(token)
+      scanner = {/last/ => :last,
+                 /this/ => :this,
+                 /next/ => :next}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'grabber-' << @type.to_s
+    end
+  end
+
+#end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php
new file mode 100644 (file)
index 0000000..551d632
--- /dev/null
@@ -0,0 +1,469 @@
+module Chronic
+
+       class << self
+         
+         def definitions #:nodoc:
+           @definitions ||= 
+      {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
+        
+       :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
+                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
+                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
+                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
+                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
+                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
+                 
+       # tonight at 7pm
+       :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
+                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
+                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
+                   
+       # 3 weeks from now, in 2 months
+       :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
+                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
+                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
+                  
+       # 3rd week in march
+       :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
+                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
+      }
+    end
+    
+    def tokens_to_span(tokens, options) #:nodoc:                   
+      # maybe it's a specific date
+      
+      self.definitions[:date].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-date" if Chronic.debug
+          good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+            
+      # I guess it's not a specific date, maybe it's just an anchor
+            
+      self.definitions[:anchor].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-anchor" if Chronic.debug
+          good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+            
+      # not an anchor, perhaps it's an arrow
+      
+      self.definitions[:arrow].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-arrow" if Chronic.debug
+          good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+      
+      # not an arrow, let's hope it's a narrow
+      
+      self.definitions[:narrow].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-narrow" if Chronic.debug
+          #good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, tokens, options)
+        end
+      end
+      
+      # I guess you're out of luck!
+      puts "-none" if Chronic.debug
+      return nil
+    end
+    
+    #--------------
+    
+    def day_or_time(day_start, time_tokens, options)
+      outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
+      
+      if !time_tokens.empty?
+        @now = outer_span.begin
+        time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
+        return time
+      else
+        return outer_span
+      end
+    end
+    
+    #--------------
+    
+    def handle_m_d(month, day, time_tokens, options) #:nodoc:
+      month.start = @now
+      span = month.this(options[:context])
+      
+      day_start = Time.local(span.begin.year, span.begin.month, day)
+      
+      day_or_time(day_start, time_tokens, options)
+    end
+    
+    def handle_rmn_sd(tokens, options) #:nodoc:
+      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
+    end
+    
+    def handle_rmn_od(tokens, options) #:nodoc:
+      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
+    end
+    
+    def handle_rmn_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(RepeaterMonthName).index
+      year = tokens[1].get_tag(ScalarYear).type
+      
+      if month == 12
+        next_month_year = year + 1
+        next_month_month = 1
+      else
+        next_month_year = year
+        next_month_month = month + 1
+      end
+      
+      begin
+        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
+      month = tokens[1].get_tag(RepeaterMonthName).index
+      day = tokens[2].get_tag(ScalarDay).type
+      year = tokens[5].get_tag(ScalarYear).type
+      
+      begin
+        day_start = Time.local(year, month, day)
+        day_or_time(day_start, [tokens[3]], options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_rmn_sd_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(RepeaterMonthName).index
+      day = tokens[1].get_tag(ScalarDay).type
+      year = tokens[2].get_tag(ScalarYear).type
+      
+      time_tokens = tokens.last(tokens.size - 3)
+      
+      begin
+        day_start = Time.local(year, month, day)
+        day_or_time(day_start, time_tokens, options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_sd_rmn_sy(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sm_sd_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(ScalarMonth).type
+      day = tokens[1].get_tag(ScalarDay).type
+      year = tokens[2].get_tag(ScalarYear).type
+      
+      time_tokens = tokens.last(tokens.size - 3)
+      
+      begin
+        day_start = Time.local(year, month, day) #:nodoc:
+        day_or_time(day_start, time_tokens, options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_sd_sm_sy(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sy_sm_sd(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[2], tokens[0]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sm_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(ScalarMonth).type
+      year = tokens[1].get_tag(ScalarYear).type
+      
+      if month == 12
+        next_month_year = year + 1
+        next_month_month = 1
+      else
+        next_month_year = year
+        next_month_month = month + 1
+      end
+      
+      begin
+        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    # anchors
+    
+    def handle_r(tokens, options) #:nodoc:
+      dd_tokens = dealias_and_disambiguate_times(tokens, options)
+      self.get_anchor(dd_tokens, options)
+    end
+    
+    def handle_r_g_r(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      self.handle_r(new_tokens, options)
+    end
+    
+    # arrows
+    
+    def handle_srp(tokens, span, options) #:nodoc:
+      distance = tokens[0].get_tag(Scalar).type
+      repeater = tokens[1].get_tag(Repeater)
+      pointer = tokens[2].get_tag(Pointer).type
+      
+      repeater.offset(span, distance, pointer)
+    end
+    
+    def handle_s_r_p(tokens, options) #:nodoc:
+      repeater = tokens[1].get_tag(Repeater)
+            
+      # span = 
+      # case true
+      # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
+      #   self.parse("this hour", :guess => false, :now => @now)
+      # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
+      #   self.parse("this minute", :guess => false, :now => @now)
+      # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
+      #   self.parse("this second", :guess => false, :now => @now)
+      # else
+      #   raise(ChronicPain, "Invalid repeater: #{repeater.class}")
+      # end
+      
+      span = self.parse("this second", :guess => false, :now => @now)
+      
+      self.handle_srp(tokens, span, options)
+    end
+    
+    def handle_p_s_r(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[2], tokens[0]]
+      self.handle_s_r_p(new_tokens, options)
+    end
+    
+    def handle_s_r_p_a(tokens, options) #:nodoc:
+      anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
+      self.handle_srp(tokens, anchor_span, options)
+    end
+    
+    # narrows
+    
+    def handle_orr(tokens, outer_span, options) #:nodoc:
+      repeater = tokens[1].get_tag(Repeater)
+      repeater.start = outer_span.begin - 1
+      ordinal = tokens[0].get_tag(Ordinal).type
+      span = nil
+      ordinal.times do
+        span = repeater.next(:future)
+        if span.begin > outer_span.end
+          span = nil
+          break
+        end
+      end
+      span
+    end
+    
+    def handle_o_r_s_r(tokens, options) #:nodoc:
+      outer_span = get_anchor([tokens[3]], options)
+      handle_orr(tokens[0..1], outer_span, options)
+    end
+    
+    def handle_o_r_g_r(tokens, options) #:nodoc:
+      outer_span = get_anchor(tokens[2..3], options)
+      handle_orr(tokens[0..1], outer_span, options)
+    end
+    
+    # support methods
+    
+    def get_anchor(tokens, options) #:nodoc:
+      grabber = Grabber.new(:this)
+      pointer = :future
+      
+      repeaters = self.get_repeaters(tokens)
+      repeaters.size.times { tokens.pop }
+                    
+      if tokens.first && tokens.first.get_tag(Grabber)
+        grabber = tokens.first.get_tag(Grabber)
+        tokens.pop
+      end
+      
+      head = repeaters.shift
+      head.start = @now
+                  
+      case grabber.type
+        when :last
+          outer_span = head.next(:past)
+        when :this
+          if repeaters.size > 0
+            outer_span = head.this(:none)
+          else
+            outer_span = head.this(options[:context])
+          end
+        when :next
+          outer_span = head.next(:future)
+        else raise(ChronicPain, "Invalid grabber")
+      end
+      
+      puts "--#{outer_span}" if Chronic.debug
+      anchor = find_within(repeaters, outer_span, pointer)
+    end
+    
+    def get_repeaters(tokens) #:nodoc:
+      repeaters = []
+                 tokens.each do |token|
+                   if t = token.get_tag(Repeater)
+          repeaters << t
+        end
+      end
+      repeaters.sort.reverse
+    end
+    
+    # Recursively finds repeaters within other repeaters.
+    # Returns a Span representing the innermost time span
+    # or nil if no repeater union could be found
+    def find_within(tags, span, pointer) #:nodoc:
+      puts "--#{span}" if Chronic.debug
+      return span if tags.empty?
+      
+      head, *rest = tags
+      head.start = pointer == :future ? span.begin : span.end
+      h = head.this(:none)
+            
+      if span.include?(h.begin) || span.include?(h.end)
+        return find_within(rest, h, pointer)
+      else
+        return nil
+      end
+    end
+    
+    def dealias_and_disambiguate_times(tokens, options) #:nodoc:
+      # handle aliases of am/pm
+      # 5:00 in the morning -> 5:00 am
+      # 7:00 in the evening -> 7:00 pm
+      
+      day_portion_index = nil
+      tokens.each_with_index do |t, i|
+        if t.get_tag(RepeaterDayPortion)
+          day_portion_index = i
+          break
+        end
+      end
+       
+      time_index = nil
+      tokens.each_with_index do |t, i|
+        if t.get_tag(RepeaterTime)
+          time_index = i
+          break
+        end
+      end
+      
+      if (day_portion_index && time_index)
+        t1 = tokens[day_portion_index]
+        t1tag = t1.get_tag(RepeaterDayPortion)
+      
+        if [:morning].include?(t1tag.type)
+          puts '--morning->am' if Chronic.debug
+          t1.untag(RepeaterDayPortion)
+          t1.tag(RepeaterDayPortion.new(:am))
+        elsif [:afternoon, :evening, :night].include?(t1tag.type)
+          puts "--#{t1tag.type}->pm" if Chronic.debug
+          t1.untag(RepeaterDayPortion)
+          t1.tag(RepeaterDayPortion.new(:pm))
+        end
+      end
+      
+      # tokens.each_with_index do |t0, i|
+      #   t1 = tokens[i + 1]
+      #   if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
+      #     if [:morning].include?(t1tag.type)
+      #       puts '--morning->am' if Chronic.debug
+      #       t1.untag(RepeaterDayPortion)
+      #       t1.tag(RepeaterDayPortion.new(:am))
+      #     elsif [:afternoon, :evening, :night].include?(t1tag.type)
+      #       puts "--#{t1tag.type}->pm" if Chronic.debug
+      #       t1.untag(RepeaterDayPortion)
+      #       t1.tag(RepeaterDayPortion.new(:pm))
+      #     end
+      #   end
+      # end
+            
+      # handle ambiguous times if :ambiguous_time_range is specified
+      if options[:ambiguous_time_range] != :none
+        ttokens = []
+        tokens.each_with_index do |t0, i|
+          ttokens << t0
+          t1 = tokens[i + 1]
+          if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
+            distoken = Token.new('disambiguator')
+            distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
+            ttokens << distoken
+          end
+        end
+        tokens = ttokens
+      end
+      
+      tokens
+    end
+    
+  end
+  
+  class Handler #:nodoc:
+    attr_accessor :pattern, :handler_method
+    
+    def initialize(pattern, handler_method)
+      @pattern = pattern
+      @handler_method = handler_method
+    end
+    
+    def constantize(name)
+      camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
+      ::Chronic.module_eval(camel, __FILE__, __LINE__)
+    end
+    
+    def match(tokens, definitions)
+      token_index = 0
+      @pattern.each do |element|
+        name = element.to_s
+        optional = name.reverse[0..0] == '?'
+        name = name.chop if optional
+        if element.instance_of? Symbol
+          klass = constantize(name)
+          match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
+          return false if !match && !optional
+          (token_index += 1; next) if match
+          next if !match && optional
+        elsif element.instance_of? String
+          return true if optional && token_index == tokens.size
+          sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
+          sub_handlers.each do |sub_handler|
+            return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
+          end
+          return false
+        else
+          raise(ChronicPain, "Invalid match type: #{element.class}")
+        end
+      end
+      return false if token_index != tokens.size
+      return true
+    end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php
new file mode 100644 (file)
index 0000000..45b8148
--- /dev/null
@@ -0,0 +1,40 @@
+module Chronic
+
+  class Ordinal < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
+        if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_ordinals(token)
+      if token.word =~ /^(\d*)(st|nd|rd|th)$/
+        return Ordinal.new($1.to_i)
+      end
+      return nil
+    end
+    
+    def self.scan_for_days(token)
+      if token.word =~ /^(\d*)(st|nd|rd|th)$/
+        unless $1.to_i > 31
+          return OrdinalDay.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def to_s
+      'ordinal'
+    end
+  end
+  
+  class OrdinalDay < Ordinal #:nodoc:
+    def to_s
+      super << '-day-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php
new file mode 100644 (file)
index 0000000..224efaf
--- /dev/null
@@ -0,0 +1,27 @@
+module Chronic
+
+  class Pointer < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_all(token)
+      scanner = {/\bpast\b/ => :past,
+                 /\bfuture\b/ => :future,
+                 /\bin\b/ => :future}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'pointer-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php
new file mode 100644 (file)
index 0000000..9f80daf
--- /dev/null
@@ -0,0 +1,115 @@
+class Chronic::Repeater < Chronic::Tag #:nodoc:
+  def self.scan(tokens, options)
+    # for each token
+    tokens.each_index do |i|
+      if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
+      if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
+    end
+    tokens
+  end
+  
+  def self.scan_for_month_names(token)
+    scanner = {/^jan\.?(uary)?$/ => :january,
+               /^feb\.?(ruary)?$/ => :february,
+               /^mar\.?(ch)?$/ => :march,
+               /^apr\.?(il)?$/ => :april,
+               /^may$/ => :may,
+               /^jun\.?e?$/ => :june,
+               /^jul\.?y?$/ => :july,
+               /^aug\.?(ust)?$/ => :august,
+               /^sep\.?(t\.?|tember)?$/ => :september,
+               /^oct\.?(ober)?$/ => :october,
+               /^nov\.?(ember)?$/ => :november,
+               /^dec\.?(ember)?$/ => :december}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_day_names(token)
+    scanner = {/^m[ou]n(day)?$/ => :monday,
+               /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
+               /^tue$/ => :tuesday,
+               /^we(dnes|nds|nns)day$/ => :wednesday,
+               /^wed$/ => :wednesday,
+               /^th(urs|ers)day$/ => :thursday,
+               /^thu$/ => :thursday,
+               /^fr[iy](day)?$/ => :friday,
+               /^sat(t?[ue]rday)?$/ => :saturday,
+               /^su[nm](day)?$/ => :sunday}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_day_portions(token)
+    scanner = {/^ams?$/ => :am,
+               /^pms?$/ => :pm,
+               /^mornings?$/ => :morning,
+               /^afternoons?$/ => :afternoon,
+               /^evenings?$/ => :evening,
+               /^(night|nite)s?$/ => :night}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_times(token, options)
+    if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
+      return Chronic::RepeaterTime.new(token.word, options)
+    end
+    return nil
+  end
+  
+  def self.scan_for_units(token)
+    scanner = {/^years?$/ => :year,
+               /^seasons?$/ => :season,
+               /^months?$/ => :month,
+               /^fortnights?$/ => :fortnight,
+               /^weeks?$/ => :week,
+               /^weekends?$/ => :weekend,
+               /^days?$/ => :day,
+               /^hours?$/ => :hour,
+               /^minutes?$/ => :minute,
+               /^seconds?$/ => :second}
+    scanner.keys.each do |scanner_item|
+      if scanner_item =~ token.word
+        klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
+        klass = eval(klass_name)
+        return klass.new(scanner[scanner_item]) 
+      end
+    end
+    return nil
+  end
+  
+  def <=>(other)
+    width <=> other.width
+  end
+  
+  # returns the width (in seconds or months) of this repeatable.
+  def width
+    raise("Repeatable#width must be overridden in subclasses")
+  end
+  
+  # returns the next occurance of this repeatable.
+  def next(pointer)
+    !@now.nil? || raise("Start point must be set before calling #next")
+    [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
+    #raise("Repeatable#next must be overridden in subclasses")
+  end
+  
+  def this(pointer)
+    !@now.nil? || raise("Start point must be set before calling #this")
+    [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
+  end
+  
+  def to_s
+    'repeater'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php
new file mode 100644 (file)
index 0000000..a92d83f
--- /dev/null
@@ -0,0 +1,47 @@
+class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
+  DAY_SECONDS = 86_400 # (24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_day_start
+      @current_day_start = Time.local(@now.year, @now.month, @now.day)
+    end
+    
+    direction = pointer == :future ? 1 : -1
+    @current_day_start += direction * DAY_SECONDS
+    
+    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+    when :past
+      day_begin = Time.construct(@now.year, @now.month, @now.day)
+      day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+    when :none
+      day_begin = Time.construct(@now.year, @now.month, @now.day)
+      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+    end
+    
+    Chronic::Span.new(day_begin, day_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * DAY_SECONDS
+  end
+  
+  def width
+    DAY_SECONDS
+  end
+  
+  def to_s
+    super << '-day'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php
new file mode 100644 (file)
index 0000000..0486a4d
--- /dev/null
@@ -0,0 +1,46 @@
+class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
+  DAY_SECONDS = 86400 # (24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    direction = pointer == :future ? 1 : -1
+    
+    if !@current_day_start
+      @current_day_start = Time.construct(@now.year, @now.month, @now.day)
+      @current_day_start += direction * DAY_SECONDS
+
+      day_num = symbol_to_number(@type)
+      
+      while @current_day_start.wday != day_num
+        @current_day_start += direction * DAY_SECONDS
+      end
+    else
+      @current_day_start += direction * 7 * DAY_SECONDS
+    end
+    
+    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    pointer = :future if pointer == :none
+    self.next(pointer)
+  end
+  
+  def width
+    DAY_SECONDS
+  end
+  
+  def to_s
+    super << '-dayname-' << @type.to_s
+  end
+  
+  private
+  
+  def symbol_to_number(sym)
+    lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
+    lookup[sym] || raise("Invalid symbol specified")
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php
new file mode 100644 (file)
index 0000000..c854933
--- /dev/null
@@ -0,0 +1,93 @@
+class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
+  @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
+  @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
+  @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
+  @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
+  
+  def initialize(type)
+    super
+    
+    if type.kind_of? Integer
+      @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
+    else
+      lookup = {:am => 0..(12 * 60 * 60 - 1),
+                :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
+                :morning => @@morning,
+                :afternoon => @@afternoon,
+                :evening => @@evening,
+                :night => @@night}
+      @range = lookup[type]
+      lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
+    end
+    @range || raise("Range should have been set by now")
+  end
+  
+  def next(pointer)
+    super
+    
+    full_day = 60 * 60 * 24
+    
+    if !@current_span
+      now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
+      if now_seconds < @range.begin
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+        end
+      elsif now_seconds > @range.end
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+        end
+      else
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+        end
+      end
+      
+      @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+    else
+      case pointer
+      when :future
+        @current_span += full_day
+      when :past
+        @current_span -= full_day
+      end
+    end
+  end
+  
+  def this(context = :future)
+    super
+    
+    range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+    @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+  end
+  
+  def offset(span, amount, pointer)
+    @now = span.begin
+    portion_span = self.next(pointer)
+    direction = pointer == :future ? 1 : -1
+    portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
+  end
+  
+  def width
+    @range || raise("Range has not been set")
+    return @current_span.width if @current_span
+    if @type.kind_of? Integer
+      return (12 * 60 * 60)
+    else
+      @range.end - @range.begin
+    end
+  end
+  
+  def to_s
+    super << '-dayportion-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php
new file mode 100644 (file)
index 0000000..058fbb9
--- /dev/null
@@ -0,0 +1,65 @@
+class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
+  FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_fortnight_start
+      case pointer
+      when :future
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = @now
+        next_sunday_span = sunday_repeater.next(:future)
+        @current_fortnight_start = next_sunday_span.begin
+      when :past
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        2.times { sunday_repeater.next(:past) }
+        last_sunday_span = sunday_repeater.next(:past)
+        @current_fortnight_start = last_sunday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_fortnight_start += direction * FORTNIGHT_SECONDS
+    end
+    
+    Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    pointer = :future if pointer == :none
+    
+    case pointer
+    when :future
+      this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      sunday_repeater.this(:future)
+      this_sunday_span = sunday_repeater.this(:future)
+      this_fortnight_end = this_sunday_span.begin
+      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+    when :past
+      this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_fortnight_start = last_sunday_span.begin
+      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * FORTNIGHT_SECONDS
+  end
+
+  def width
+    FORTNIGHT_SECONDS
+  end
+  
+  def to_s
+    super << '-fortnight'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php
new file mode 100644 (file)
index 0000000..f38a3f8
--- /dev/null
@@ -0,0 +1,52 @@
+class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
+  HOUR_SECONDS = 3600 # 60 * 60
+  
+  def next(pointer)
+    super
+    
+    if !@current_hour_start
+      case pointer
+      when :future
+        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+      when :past
+        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_hour_start += direction * HOUR_SECONDS
+    end
+    
+    Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+    when :past
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+    when :none
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      hour_end = hour_begin + HOUR_SECONDS
+    end
+    
+    Chronic::Span.new(hour_start, hour_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * HOUR_SECONDS
+  end
+  
+  def width
+    HOUR_SECONDS
+  end
+  
+  def to_s
+    super << '-hour'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php
new file mode 100644 (file)
index 0000000..342d3cd
--- /dev/null
@@ -0,0 +1,52 @@
+class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
+  MINUTE_SECONDS = 60
+  
+  def next(pointer = :future)
+    super
+    
+    if !@current_minute_start
+      case pointer
+      when :future
+        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+      when :past
+        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_minute_start += direction * MINUTE_SECONDS
+    end
+    
+    Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      minute_begin = @now
+      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+    when :past
+      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+      minute_end = @now
+    when :none
+      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
+    end
+    
+    Chronic::Span.new(minute_begin, minute_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * MINUTE_SECONDS
+  end
+  
+  def width
+    MINUTE_SECONDS
+  end
+  
+  def to_s
+    super << '-minute'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php
new file mode 100644 (file)
index 0000000..edd89ee
--- /dev/null
@@ -0,0 +1,61 @@
+class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
+  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+  YEAR_MONTHS = 12
+  
+  def next(pointer)
+    super
+    
+    if !@current_month_start
+      @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
+    else
+      @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
+    end
+    
+    Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      month_start = Time.construct(@now.year, @now.month, @now.day + 1)
+      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+    when :past
+      month_start = Time.construct(@now.year, @now.month)
+      month_end = Time.construct(@now.year, @now.month, @now.day)
+    when :none
+      month_start = Time.construct(@now.year, @now.month)
+      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+    end
+    
+    Chronic::Span.new(month_start, month_end)
+  end
+  
+  def offset(span, amount, pointer)      
+    Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
+  end
+  
+  def offset_by(time, amount, pointer) 
+    direction = pointer == :future ? 1 : -1
+    
+    amount_years = direction * amount / YEAR_MONTHS
+    amount_months = direction * amount % YEAR_MONTHS
+    
+    new_year = time.year + amount_years
+    new_month = time.month + amount_months
+    if new_month > YEAR_MONTHS
+      new_year += 1
+      new_month -= YEAR_MONTHS
+    end
+    Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
+  end
+  
+  def width
+    MONTH_SECONDS
+  end
+  
+  def to_s
+    super << '-month'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php
new file mode 100644 (file)
index 0000000..1f8b748
--- /dev/null
@@ -0,0 +1,93 @@
+class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
+  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+  
+  def next(pointer)
+    super
+    
+    if !@current_month_begin
+      target_month = symbol_to_number(@type)
+      case pointer
+      when :future
+        if @now.month < target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month > target_month
+          @current_month_begin = Time.construct(@now.year + 1, target_month)
+        end
+      when :none
+        if @now.month <= target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month > target_month
+          @current_month_begin = Time.construct(@now.year + 1, target_month)
+        end
+      when :past
+        if @now.month > target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month < target_month
+          @current_month_begin = Time.construct(@now.year - 1, target_month)
+        end
+      end
+      @current_month_begin || raise("Current month should be set by now")
+    else
+      case pointer
+      when :future
+        @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
+      when :past
+        @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
+      end
+    end
+    
+    cur_month_year = @current_month_begin.year
+    cur_month_month = @current_month_begin.month
+    
+    if cur_month_month == 12
+      next_month_year = cur_month_year + 1
+      next_month_month = 1
+    else
+      next_month_year = cur_month_year
+      next_month_month = cur_month_month + 1
+    end
+      
+    Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :past
+      self.next(pointer)
+    when :future, :none
+      self.next(:none)
+    end
+  end
+  
+  def width
+    MONTH_SECONDS
+  end
+  
+  def index
+    symbol_to_number(@type)
+  end
+  
+  def to_s
+    super << '-monthname-' << @type.to_s
+  end
+  
+  private
+  
+  def symbol_to_number(sym)
+    lookup = {:january => 1,
+              :february => 2,
+              :march => 3,
+              :april => 4,
+              :may => 5,
+              :june => 6,
+              :july => 7,
+              :august => 8,
+              :september => 9,
+              :october => 10,
+              :november => 11,
+              :december => 12}
+    lookup[sym] || raise("Invalid symbol specified")
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php
new file mode 100644 (file)
index 0000000..a255865
--- /dev/null
@@ -0,0 +1,23 @@
+class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
+  SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
+  
+  def next(pointer)
+    super
+    
+    raise 'Not implemented'
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    raise 'Not implemented'
+  end
+  
+  def width
+    SEASON_SECONDS
+  end
+  
+  def to_s
+    super << '-season'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php
new file mode 100644 (file)
index 0000000..adfd1f2
--- /dev/null
@@ -0,0 +1,24 @@
+class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
+  @summer = ['jul 21', 'sep 22']
+  @autumn = ['sep 23', 'dec 21']
+  @winter = ['dec 22', 'mar 19']
+  @spring = ['mar 20', 'jul 20']
+  
+  def next(pointer)
+    super
+    raise 'Not implemented'
+  end
+  
+  def this(pointer = :future)
+    super
+    raise 'Not implemented'
+  end
+  
+  def width
+    (91 * 24 * 60 * 60)
+  end
+  
+  def to_s
+    super << '-season-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php
new file mode 100644 (file)
index 0000000..6d05545
--- /dev/null
@@ -0,0 +1,36 @@
+class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
+  SECOND_SECONDS = 1 # haha, awesome
+  
+  def next(pointer = :future)
+    super
+    
+    direction = pointer == :future ? 1 : -1
+    
+    if !@second_start
+      @second_start = @now + (direction * SECOND_SECONDS)
+    else
+      @second_start += SECOND_SECONDS * direction
+    end
+    
+    Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    Chronic::Span.new(@now, @now + 1)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * SECOND_SECONDS
+  end
+  
+  def width
+    SECOND_SECONDS
+  end
+  
+  def to_s
+    super << '-second'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php
new file mode 100644 (file)
index 0000000..f856014
--- /dev/null
@@ -0,0 +1,117 @@
+class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
+  class Tick #:nodoc:
+    attr_accessor :time
+    
+    def initialize(time, ambiguous = false)
+      @time = time
+      @ambiguous = ambiguous
+    end
+    
+    def ambiguous?
+      @ambiguous
+    end
+    
+    def *(other)
+      Tick.new(@time * other, @ambiguous)
+    end
+    
+    def to_f
+      @time.to_f
+    end
+    
+    def to_s
+      @time.to_s + (@ambiguous ? '?' : '')
+    end
+  end
+  
+  def initialize(time, options = {})
+    t = time.gsub(/\:/, '')
+    @type = 
+    if (1..2) === t.size
+      hours = t.to_i
+      hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
+    elsif t.size == 3
+      Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
+    elsif t.size == 4
+      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+      hours = t[0..1].to_i
+      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
+    elsif t.size == 5
+      Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
+    elsif t.size == 6
+      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+      hours = t[0..1].to_i
+      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
+    else
+      raise("Time cannot exceed six digits")
+    end
+  end
+  
+  # Return the next past or future Span for the time that this Repeater represents
+  #   pointer - Symbol representing which temporal direction to fetch the next day
+  #             must be either :past or :future
+  def next(pointer)
+    super
+    
+    half_day = 60 * 60 * 12
+    full_day = 60 * 60 * 24
+    
+    first = false
+    
+    unless @current_time
+      first = true
+      midnight = Time.local(@now.year, @now.month, @now.day)
+      yesterday_midnight = midnight - full_day
+      tomorrow_midnight = midnight + full_day
+
+      catch :done do
+        if pointer == :future
+          if @type.ambiguous?
+            [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t >= @now
+            end
+          else
+            [midnight + @type, tomorrow_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t >= @now
+            end
+          end
+        else # pointer == :past
+          if @type.ambiguous?
+            [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
+              (@current_time = t; throw :done) if t <= @now
+            end
+          else
+            [midnight + @type, yesterday_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t <= @now
+            end
+          end
+        end
+      end
+      
+      @current_time || raise("Current time cannot be nil at this point")
+    end
+    
+    unless first
+      increment = @type.ambiguous? ? half_day : full_day
+      @current_time += pointer == :future ? increment : -increment
+    end
+    
+    Chronic::Span.new(@current_time, @current_time + width)
+  end
+  
+  def this(context = :future)
+    super
+    
+    context = :future if context == :none
+    
+    self.next(context)
+  end
+  
+  def width
+    1
+  end
+  
+  def to_s
+    super << '-time-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php
new file mode 100644 (file)
index 0000000..ec88ff1
--- /dev/null
@@ -0,0 +1,68 @@
+class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
+  WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_week_start
+      case pointer
+      when :future
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = @now
+        next_sunday_span = sunday_repeater.next(:future)
+        @current_week_start = next_sunday_span.begin
+      when :past
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        sunday_repeater.next(:past)
+        last_sunday_span = sunday_repeater.next(:past)
+        @current_week_start = last_sunday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_week_start += direction * WEEK_SECONDS
+    end
+    
+    Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      this_sunday_span = sunday_repeater.this(:future)
+      this_week_end = this_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_end)
+    when :past
+      this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_week_start = last_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_end)
+    when :none
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_week_start = last_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * WEEK_SECONDS
+  end
+  
+  def width
+    WEEK_SECONDS
+  end
+  
+  def to_s
+    super << '-week'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php
new file mode 100644 (file)
index 0000000..f012267
--- /dev/null
@@ -0,0 +1,60 @@
+class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
+  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_week_start
+      case pointer
+      when :future
+        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+        saturday_repeater.start = @now
+        next_saturday_span = saturday_repeater.next(:future)
+        @current_week_start = next_saturday_span.begin
+      when :past
+        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+        saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        last_saturday_span = saturday_repeater.next(:past)
+        @current_week_start = last_saturday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
+    end
+    
+    Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future, :none
+      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+      saturday_repeater.start = @now
+      this_saturday_span = saturday_repeater.this(:future)
+      Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
+    when :past
+      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+      saturday_repeater.start = @now
+      last_saturday_span = saturday_repeater.this(:past)
+      Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = span.begin
+    start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
+    Chronic::Span.new(start, start + (span.end - span.begin))
+  end
+  
+  def width
+    WEEKEND_SECONDS
+  end
+  
+  def to_s
+    super << '-weekend'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php
new file mode 100644 (file)
index 0000000..426371f
--- /dev/null
@@ -0,0 +1,58 @@
+class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
+  
+  def next(pointer)
+    super
+    
+    if !@current_year_start
+      case pointer
+      when :future
+        @current_year_start = Time.construct(@now.year + 1)
+      when :past
+        @current_year_start = Time.construct(@now.year - 1)
+      end
+    else
+      diff = pointer == :future ? 1 : -1
+      @current_year_start = Time.construct(@current_year_start.year + diff)
+    end
+    
+    Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
+      this_year_end = Time.construct(@now.year + 1, 1, 1)
+    when :past
+      this_year_start = Time.construct(@now.year, 1, 1)
+      this_year_end = Time.construct(@now.year, @now.month, @now.day)
+    when :none
+      this_year_start = Time.construct(@now.year, 1, 1)
+      this_year_end = Time.construct(@now.year + 1, 1, 1)
+    end
+    
+    Chronic::Span.new(this_year_start, this_year_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    
+    sb = span.begin
+    new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
+    
+    se = span.end
+    new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
+    
+    Chronic::Span.new(new_begin, new_end)
+  end
+  
+  def width
+    (365 * 24 * 60 * 60)
+  end
+  
+  def to_s
+    super << '-year'
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php
new file mode 100644 (file)
index 0000000..b08cfee
--- /dev/null
@@ -0,0 +1,74 @@
+module Chronic
+
+  class Scalar < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_scalars(token, post_token)
+      if token.word =~ /^\d*$/
+        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+          return Scalar.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_days(token, post_token)
+      if token.word =~ /^\d\d?$/
+        unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+          return ScalarDay.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_months(token, post_token)
+      if token.word =~ /^\d\d?$/
+        unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+          return ScalarMonth.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_years(token, post_token)
+      if token.word =~ /^([1-9]\d)?\d\d?$/
+        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+          return ScalarYear.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def to_s
+      'scalar'
+    end
+  end
+  
+  class ScalarDay < Scalar #:nodoc:
+    def to_s
+      super << '-day-' << @type.to_s
+    end
+  end
+  
+  class ScalarMonth < Scalar #:nodoc:
+    def to_s
+      super << '-month-' << @type.to_s
+    end
+  end
+  
+  class ScalarYear < Scalar #:nodoc:
+    def to_s
+      super << '-year-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php
new file mode 100644 (file)
index 0000000..86c56e3
--- /dev/null
@@ -0,0 +1,76 @@
+module Chronic
+
+  class Separator < Tag #:nodoc:
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+    
+    def self.scan_for_commas(token)
+      scanner = {/^,$/ => :comma}
+      scanner.keys.each do |scanner_item|
+        return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_slash_or_dash(token)
+      scanner = {/^-$/ => :dash,
+                 /^\/$/ => :slash}
+      scanner.keys.each do |scanner_item|
+        return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_at(token)
+      scanner = {/^(at|@)$/ => :at}
+      scanner.keys.each do |scanner_item|
+        return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_in(token)
+      scanner = {/^in$/ => :in}
+      scanner.keys.each do |scanner_item|
+        return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'separator'
+    end
+  end
+  
+  class SeparatorComma < Separator #:nodoc:
+    def to_s
+      super << '-comma'
+    end
+  end
+  
+  class SeparatorSlashOrDash < Separator #:nodoc:
+    def to_s
+      super << '-slashordash-' << @type.to_s
+    end
+  end
+  
+  class SeparatorAt < Separator #:nodoc:
+    def to_s
+      super << '-at'
+    end
+  end
+  
+  class SeparatorIn < Separator #:nodoc:
+    def to_s
+      super << '-in'
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php
new file mode 100644 (file)
index 0000000..41041ef
--- /dev/null
@@ -0,0 +1,22 @@
+module Chronic
+  class TimeZone < Tag #:nodoc:    
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+
+    def self.scan_for_all(token)
+      scanner = {/[PMCE][DS]T/i => :tz}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+
+    def to_s
+      'timezone'
+    end
+  end
+end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Ordinal.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Ordinal.php
deleted file mode 100644 (file)
index 45b8148..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-module Chronic
-
-  class Ordinal < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
-        if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_ordinals(token)
-      if token.word =~ /^(\d*)(st|nd|rd|th)$/
-        return Ordinal.new($1.to_i)
-      end
-      return nil
-    end
-    
-    def self.scan_for_days(token)
-      if token.word =~ /^(\d*)(st|nd|rd|th)$/
-        unless $1.to_i > 31
-          return OrdinalDay.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def to_s
-      'ordinal'
-    end
-  end
-  
-  class OrdinalDay < Ordinal #:nodoc:
-    def to_s
-      super << '-day-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Parser.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Parser.php
deleted file mode 100644 (file)
index 5e7779f..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-module Chronic
-  class << self
-    
-    # Parses a string containing a natural language date or time. If the parser
-    # can find a date or time, either a Time or Chronic::Span will be returned 
-    # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
-    # +nil+ will be returned.
-    #
-    # Options are:
-    #
-    # [<tt>:context</tt>]
-    #     <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
-    #
-    #     If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt> 
-    #     and if an ambiguous string is given, it will assume it is in the 
-    #     past. Specify <tt>:future</tt> or omit to set a future context.
-    #
-    # [<tt>:now</tt>]
-    #     Time (defaults to Time.now)
-    #
-    #     By setting <tt>:now</tt> to a Time, all computations will be based off
-    #     of that time instead of Time.now
-    #
-    # [<tt>:guess</tt>]
-    #     +true+ or +false+ (defaults to +true+)
-    #
-    #     By default, the parser will guess a single point in time for the
-    #     given date or time. If you'd rather have the entire time span returned,
-    #     set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
-    #     
-    # [<tt>:ambiguous_time_range</tt>]
-    #     Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
-    #
-    #     If an Integer is given, ambiguous times (like 5:00) will be 
-    #     assumed to be within the range of that time in the AM to that time
-    #     in the PM. For example, if you set it to <tt>7</tt>, then the parser will
-    #     look for the time between 7am and 7pm. In the case of 5:00, it would
-    #     assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
-    #     will be made, and the first matching instance of that time will 
-    #     be used.
-    def parse(text, specified_options = {})
-      # get options and set defaults if necessary
-      default_options = {:context => :future,
-                         :now => Time.now,
-                         :guess => true,
-                         :ambiguous_time_range => 6}
-      options = default_options.merge specified_options
-            
-      # ensure the specified options are valid
-      specified_options.keys.each do |key|
-        default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
-      end
-      [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
-      
-      # store now for later =)
-      @now = options[:now]
-      
-      # put the text into a normal format to ease scanning
-      text = self.pre_normalize(text)
-          
-      # get base tokens for each word
-      @tokens = self.base_tokenize(text)
-    
-      # scan the tokens with each token scanner
-      [Repeater].each do |tokenizer|
-        @tokens = tokenizer.scan(@tokens, options)
-      end
-      
-      [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
-        @tokens = tokenizer.scan(@tokens)
-      end
-      
-      # strip any non-tagged tokens
-      @tokens = @tokens.select { |token| token.tagged? }
-      
-      if Chronic.debug
-        puts "+---------------------------------------------------"
-        puts "| " + @tokens.to_s
-        puts "+---------------------------------------------------"
-      end
-      
-      # do the heavy lifting
-      begin
-        span = self.tokens_to_span(@tokens, options)
-      rescue
-        raise
-        return nil
-      end
-      
-      # guess a time within a span if required
-      if options[:guess]
-        return self.guess(span)
-      else
-        return span
-      end
-    end
-    
-    # Clean up the specified input text by stripping unwanted characters,
-    # converting idioms to their canonical form, converting number words
-    # to numbers (three => 3), and converting ordinal words to numeric
-    # ordinals (third => 3rd)
-    def pre_normalize(text) #:nodoc:
-      normalized_text = text.to_s.downcase
-      normalized_text = numericize_numbers(normalized_text)
-      normalized_text.gsub!(/['"\.]/, '')
-      normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
-      normalized_text.gsub!(/\btoday\b/, 'this day')
-      normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
-      normalized_text.gsub!(/\byesterday\b/, 'last day')
-      normalized_text.gsub!(/\bnoon\b/, '12:00')
-      normalized_text.gsub!(/\bmidnight\b/, '24:00')
-      normalized_text.gsub!(/\bbefore now\b/, 'past')
-      normalized_text.gsub!(/\bnow\b/, 'this second')
-      normalized_text.gsub!(/\b(ago|before)\b/, 'past')
-      normalized_text.gsub!(/\bthis past\b/, 'last')
-      normalized_text.gsub!(/\bthis last\b/, 'last')
-      normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
-      normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
-      normalized_text.gsub!(/\btonight\b/, 'this night')
-      normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
-      normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
-      normalized_text = numericize_ordinals(normalized_text)
-    end
-  
-    # Convert number words to numbers (three => 3)
-    def numericize_numbers(text) #:nodoc:
-      Numerizer.numerize(text)
-    end
-  
-    # Convert ordinal words to numeric ordinals (third => 3rd)
-    def numericize_ordinals(text) #:nodoc:
-      text
-    end
-  
-    # Split the text on spaces and convert each word into
-    # a Token
-    def base_tokenize(text) #:nodoc:
-      text.split(' ').map { |word| Token.new(word) }
-    end
-    
-    # Guess a specific time within the given span
-    def guess(span) #:nodoc:
-      return nil if span.nil?
-      if span.width > 1
-        span.begin + (span.width / 2)
-      else
-        span.begin
-      end
-    end
-  end
-  
-  class Token #:nodoc:
-    attr_accessor :word, :tags
-    
-    def initialize(word)
-      @word = word
-      @tags = []
-    end
-    
-    # Tag this token with the specified tag
-    def tag(new_tag)
-      @tags << new_tag
-    end
-    
-    # Remove all tags of the given class
-    def untag(tag_class)
-      @tags = @tags.select { |m| !m.kind_of? tag_class }
-    end
-    
-    # Return true if this token has any tags
-    def tagged?
-      @tags.size > 0
-    end
-    
-    # Return the Tag that matches the given class
-    def get_tag(tag_class)
-      matches = @tags.select { |m| m.kind_of? tag_class }
-      #matches.size < 2 || raise("Multiple identical tags found")
-      return matches.first
-    end
-    
-    # Print this Token in a pretty way
-    def to_s
-      @word << '(' << @tags.join(', ') << ') '
-    end
-  end
-  
-  # A Span represents a range of time. Since this class extends
-  # Range, you can use #begin and #end to get the beginning and
-  # ending times of the span (they will be of class Time)
-  class Span < Range   
-    # Returns the width of this span in seconds   
-    def width
-      (self.end - self.begin).to_i
-    end
-    
-    # Add a number of seconds to this span, returning the 
-    # resulting Span
-    def +(seconds)
-      Span.new(self.begin + seconds, self.end + seconds)
-    end
-    
-    # Subtract a number of seconds to this span, returning the 
-    # resulting Span
-    def -(seconds)
-      self + -seconds
-    end
-    
-    # Prints this span in a nice fashion
-    def to_s
-      '(' << self.begin.to_s << '..' << self.end.to_s << ')'
-    end
-  end
-
-  # Tokens are tagged with subclassed instances of this class when
-  # they match specific criteria
-  class Tag #:nodoc:
-    attr_accessor :type
-    
-    def initialize(type)
-      @type = type
-    end
-    
-    def start=(s)
-      @now = s
-    end
-  end
-  
-  # Internal exception
-  class ChronicPain < Exception #:nodoc:
-    
-  end
-  
-  # This exception is raised if an invalid argument is provided to
-  # any of Chronic's methods
-  class InvalidArgumentException < Exception
-    
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Pointer.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Pointer.php
deleted file mode 100644 (file)
index 224efaf..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-module Chronic
-
-  class Pointer < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_all(token)
-      scanner = {/\bpast\b/ => :past,
-                 /\bfuture\b/ => :future,
-                 /\bin\b/ => :future}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'pointer-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeater.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeater.php
deleted file mode 100644 (file)
index 9f80daf..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-class Chronic::Repeater < Chronic::Tag #:nodoc:
-  def self.scan(tokens, options)
-    # for each token
-    tokens.each_index do |i|
-      if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
-      if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
-    end
-    tokens
-  end
-  
-  def self.scan_for_month_names(token)
-    scanner = {/^jan\.?(uary)?$/ => :january,
-               /^feb\.?(ruary)?$/ => :february,
-               /^mar\.?(ch)?$/ => :march,
-               /^apr\.?(il)?$/ => :april,
-               /^may$/ => :may,
-               /^jun\.?e?$/ => :june,
-               /^jul\.?y?$/ => :july,
-               /^aug\.?(ust)?$/ => :august,
-               /^sep\.?(t\.?|tember)?$/ => :september,
-               /^oct\.?(ober)?$/ => :october,
-               /^nov\.?(ember)?$/ => :november,
-               /^dec\.?(ember)?$/ => :december}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_day_names(token)
-    scanner = {/^m[ou]n(day)?$/ => :monday,
-               /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
-               /^tue$/ => :tuesday,
-               /^we(dnes|nds|nns)day$/ => :wednesday,
-               /^wed$/ => :wednesday,
-               /^th(urs|ers)day$/ => :thursday,
-               /^thu$/ => :thursday,
-               /^fr[iy](day)?$/ => :friday,
-               /^sat(t?[ue]rday)?$/ => :saturday,
-               /^su[nm](day)?$/ => :sunday}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_day_portions(token)
-    scanner = {/^ams?$/ => :am,
-               /^pms?$/ => :pm,
-               /^mornings?$/ => :morning,
-               /^afternoons?$/ => :afternoon,
-               /^evenings?$/ => :evening,
-               /^(night|nite)s?$/ => :night}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_times(token, options)
-    if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
-      return Chronic::RepeaterTime.new(token.word, options)
-    end
-    return nil
-  end
-  
-  def self.scan_for_units(token)
-    scanner = {/^years?$/ => :year,
-               /^seasons?$/ => :season,
-               /^months?$/ => :month,
-               /^fortnights?$/ => :fortnight,
-               /^weeks?$/ => :week,
-               /^weekends?$/ => :weekend,
-               /^days?$/ => :day,
-               /^hours?$/ => :hour,
-               /^minutes?$/ => :minute,
-               /^seconds?$/ => :second}
-    scanner.keys.each do |scanner_item|
-      if scanner_item =~ token.word
-        klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
-        klass = eval(klass_name)
-        return klass.new(scanner[scanner_item]) 
-      end
-    end
-    return nil
-  end
-  
-  def <=>(other)
-    width <=> other.width
-  end
-  
-  # returns the width (in seconds or months) of this repeatable.
-  def width
-    raise("Repeatable#width must be overridden in subclasses")
-  end
-  
-  # returns the next occurance of this repeatable.
-  def next(pointer)
-    !@now.nil? || raise("Start point must be set before calling #next")
-    [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
-    #raise("Repeatable#next must be overridden in subclasses")
-  end
-  
-  def this(pointer)
-    !@now.nil? || raise("Start point must be set before calling #this")
-    [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
-  end
-  
-  def to_s
-    'repeater'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Day.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Day.php
deleted file mode 100644 (file)
index a92d83f..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
-  DAY_SECONDS = 86_400 # (24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_day_start
-      @current_day_start = Time.local(@now.year, @now.month, @now.day)
-    end
-    
-    direction = pointer == :future ? 1 : -1
-    @current_day_start += direction * DAY_SECONDS
-    
-    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
-    when :past
-      day_begin = Time.construct(@now.year, @now.month, @now.day)
-      day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-    when :none
-      day_begin = Time.construct(@now.year, @now.month, @now.day)
-      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
-    end
-    
-    Chronic::Span.new(day_begin, day_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * DAY_SECONDS
-  end
-  
-  def width
-    DAY_SECONDS
-  end
-  
-  def to_s
-    super << '-day'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayName.php
deleted file mode 100644 (file)
index 0486a4d..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
-  DAY_SECONDS = 86400 # (24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    direction = pointer == :future ? 1 : -1
-    
-    if !@current_day_start
-      @current_day_start = Time.construct(@now.year, @now.month, @now.day)
-      @current_day_start += direction * DAY_SECONDS
-
-      day_num = symbol_to_number(@type)
-      
-      while @current_day_start.wday != day_num
-        @current_day_start += direction * DAY_SECONDS
-      end
-    else
-      @current_day_start += direction * 7 * DAY_SECONDS
-    end
-    
-    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    pointer = :future if pointer == :none
-    self.next(pointer)
-  end
-  
-  def width
-    DAY_SECONDS
-  end
-  
-  def to_s
-    super << '-dayname-' << @type.to_s
-  end
-  
-  private
-  
-  def symbol_to_number(sym)
-    lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
-    lookup[sym] || raise("Invalid symbol specified")
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayPortion.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/DayPortion.php
deleted file mode 100644 (file)
index c854933..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
-  @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
-  @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
-  @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
-  @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
-  
-  def initialize(type)
-    super
-    
-    if type.kind_of? Integer
-      @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
-    else
-      lookup = {:am => 0..(12 * 60 * 60 - 1),
-                :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
-                :morning => @@morning,
-                :afternoon => @@afternoon,
-                :evening => @@evening,
-                :night => @@night}
-      @range = lookup[type]
-      lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
-    end
-    @range || raise("Range should have been set by now")
-  end
-  
-  def next(pointer)
-    super
-    
-    full_day = 60 * 60 * 24
-    
-    if !@current_span
-      now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
-      if now_seconds < @range.begin
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
-        end
-      elsif now_seconds > @range.end
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-        end
-      else
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
-        end
-      end
-      
-      @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
-    else
-      case pointer
-      when :future
-        @current_span += full_day
-      when :past
-        @current_span -= full_day
-      end
-    end
-  end
-  
-  def this(context = :future)
-    super
-    
-    range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-    @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
-  end
-  
-  def offset(span, amount, pointer)
-    @now = span.begin
-    portion_span = self.next(pointer)
-    direction = pointer == :future ? 1 : -1
-    portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
-  end
-  
-  def width
-    @range || raise("Range has not been set")
-    return @current_span.width if @current_span
-    if @type.kind_of? Integer
-      return (12 * 60 * 60)
-    else
-      @range.end - @range.begin
-    end
-  end
-  
-  def to_s
-    super << '-dayportion-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Fortnight.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Fortnight.php
deleted file mode 100644 (file)
index 058fbb9..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
-  FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_fortnight_start
-      case pointer
-      when :future
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = @now
-        next_sunday_span = sunday_repeater.next(:future)
-        @current_fortnight_start = next_sunday_span.begin
-      when :past
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        2.times { sunday_repeater.next(:past) }
-        last_sunday_span = sunday_repeater.next(:past)
-        @current_fortnight_start = last_sunday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_fortnight_start += direction * FORTNIGHT_SECONDS
-    end
-    
-    Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    pointer = :future if pointer == :none
-    
-    case pointer
-    when :future
-      this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      sunday_repeater.this(:future)
-      this_sunday_span = sunday_repeater.this(:future)
-      this_fortnight_end = this_sunday_span.begin
-      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
-    when :past
-      this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_fortnight_start = last_sunday_span.begin
-      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * FORTNIGHT_SECONDS
-  end
-
-  def width
-    FORTNIGHT_SECONDS
-  end
-  
-  def to_s
-    super << '-fortnight'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Hour.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Hour.php
deleted file mode 100644 (file)
index f38a3f8..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
-  HOUR_SECONDS = 3600 # 60 * 60
-  
-  def next(pointer)
-    super
-    
-    if !@current_hour_start
-      case pointer
-      when :future
-        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-      when :past
-        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_hour_start += direction * HOUR_SECONDS
-    end
-    
-    Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
-      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-    when :past
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-    when :none
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      hour_end = hour_begin + HOUR_SECONDS
-    end
-    
-    Chronic::Span.new(hour_start, hour_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * HOUR_SECONDS
-  end
-  
-  def width
-    HOUR_SECONDS
-  end
-  
-  def to_s
-    super << '-hour'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Minute.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Minute.php
deleted file mode 100644 (file)
index 342d3cd..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
-  MINUTE_SECONDS = 60
-  
-  def next(pointer = :future)
-    super
-    
-    if !@current_minute_start
-      case pointer
-      when :future
-        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
-      when :past
-        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_minute_start += direction * MINUTE_SECONDS
-    end
-    
-    Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      minute_begin = @now
-      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-    when :past
-      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-      minute_end = @now
-    when :none
-      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
-    end
-    
-    Chronic::Span.new(minute_begin, minute_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * MINUTE_SECONDS
-  end
-  
-  def width
-    MINUTE_SECONDS
-  end
-  
-  def to_s
-    super << '-minute'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Month.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Month.php
deleted file mode 100644 (file)
index edd89ee..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
-  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
-  YEAR_MONTHS = 12
-  
-  def next(pointer)
-    super
-    
-    if !@current_month_start
-      @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
-    else
-      @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
-    end
-    
-    Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      month_start = Time.construct(@now.year, @now.month, @now.day + 1)
-      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
-    when :past
-      month_start = Time.construct(@now.year, @now.month)
-      month_end = Time.construct(@now.year, @now.month, @now.day)
-    when :none
-      month_start = Time.construct(@now.year, @now.month)
-      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
-    end
-    
-    Chronic::Span.new(month_start, month_end)
-  end
-  
-  def offset(span, amount, pointer)      
-    Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
-  end
-  
-  def offset_by(time, amount, pointer) 
-    direction = pointer == :future ? 1 : -1
-    
-    amount_years = direction * amount / YEAR_MONTHS
-    amount_months = direction * amount % YEAR_MONTHS
-    
-    new_year = time.year + amount_years
-    new_month = time.month + amount_months
-    if new_month > YEAR_MONTHS
-      new_year += 1
-      new_month -= YEAR_MONTHS
-    end
-    Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
-  end
-  
-  def width
-    MONTH_SECONDS
-  end
-  
-  def to_s
-    super << '-month'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/MonthName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/MonthName.php
deleted file mode 100644 (file)
index 1f8b748..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
-  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
-  
-  def next(pointer)
-    super
-    
-    if !@current_month_begin
-      target_month = symbol_to_number(@type)
-      case pointer
-      when :future
-        if @now.month < target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month > target_month
-          @current_month_begin = Time.construct(@now.year + 1, target_month)
-        end
-      when :none
-        if @now.month <= target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month > target_month
-          @current_month_begin = Time.construct(@now.year + 1, target_month)
-        end
-      when :past
-        if @now.month > target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month < target_month
-          @current_month_begin = Time.construct(@now.year - 1, target_month)
-        end
-      end
-      @current_month_begin || raise("Current month should be set by now")
-    else
-      case pointer
-      when :future
-        @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
-      when :past
-        @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
-      end
-    end
-    
-    cur_month_year = @current_month_begin.year
-    cur_month_month = @current_month_begin.month
-    
-    if cur_month_month == 12
-      next_month_year = cur_month_year + 1
-      next_month_month = 1
-    else
-      next_month_year = cur_month_year
-      next_month_month = cur_month_month + 1
-    end
-      
-    Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :past
-      self.next(pointer)
-    when :future, :none
-      self.next(:none)
-    end
-  end
-  
-  def width
-    MONTH_SECONDS
-  end
-  
-  def index
-    symbol_to_number(@type)
-  end
-  
-  def to_s
-    super << '-monthname-' << @type.to_s
-  end
-  
-  private
-  
-  def symbol_to_number(sym)
-    lookup = {:january => 1,
-              :february => 2,
-              :march => 3,
-              :april => 4,
-              :may => 5,
-              :june => 6,
-              :july => 7,
-              :august => 8,
-              :september => 9,
-              :october => 10,
-              :november => 11,
-              :december => 12}
-    lookup[sym] || raise("Invalid symbol specified")
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Season.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Season.php
deleted file mode 100644 (file)
index a255865..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
-  SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
-  
-  def next(pointer)
-    super
-    
-    raise 'Not implemented'
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    raise 'Not implemented'
-  end
-  
-  def width
-    SEASON_SECONDS
-  end
-  
-  def to_s
-    super << '-season'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/SeasonName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/SeasonName.php
deleted file mode 100644 (file)
index adfd1f2..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
-  @summer = ['jul 21', 'sep 22']
-  @autumn = ['sep 23', 'dec 21']
-  @winter = ['dec 22', 'mar 19']
-  @spring = ['mar 20', 'jul 20']
-  
-  def next(pointer)
-    super
-    raise 'Not implemented'
-  end
-  
-  def this(pointer = :future)
-    super
-    raise 'Not implemented'
-  end
-  
-  def width
-    (91 * 24 * 60 * 60)
-  end
-  
-  def to_s
-    super << '-season-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Second.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Second.php
deleted file mode 100644 (file)
index 6d05545..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
-  SECOND_SECONDS = 1 # haha, awesome
-  
-  def next(pointer = :future)
-    super
-    
-    direction = pointer == :future ? 1 : -1
-    
-    if !@second_start
-      @second_start = @now + (direction * SECOND_SECONDS)
-    else
-      @second_start += SECOND_SECONDS * direction
-    end
-    
-    Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    Chronic::Span.new(@now, @now + 1)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * SECOND_SECONDS
-  end
-  
-  def width
-    SECOND_SECONDS
-  end
-  
-  def to_s
-    super << '-second'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Time.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Time.php
deleted file mode 100644 (file)
index f856014..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
-  class Tick #:nodoc:
-    attr_accessor :time
-    
-    def initialize(time, ambiguous = false)
-      @time = time
-      @ambiguous = ambiguous
-    end
-    
-    def ambiguous?
-      @ambiguous
-    end
-    
-    def *(other)
-      Tick.new(@time * other, @ambiguous)
-    end
-    
-    def to_f
-      @time.to_f
-    end
-    
-    def to_s
-      @time.to_s + (@ambiguous ? '?' : '')
-    end
-  end
-  
-  def initialize(time, options = {})
-    t = time.gsub(/\:/, '')
-    @type = 
-    if (1..2) === t.size
-      hours = t.to_i
-      hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
-    elsif t.size == 3
-      Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
-    elsif t.size == 4
-      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
-      hours = t[0..1].to_i
-      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
-    elsif t.size == 5
-      Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
-    elsif t.size == 6
-      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
-      hours = t[0..1].to_i
-      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
-    else
-      raise("Time cannot exceed six digits")
-    end
-  end
-  
-  # Return the next past or future Span for the time that this Repeater represents
-  #   pointer - Symbol representing which temporal direction to fetch the next day
-  #             must be either :past or :future
-  def next(pointer)
-    super
-    
-    half_day = 60 * 60 * 12
-    full_day = 60 * 60 * 24
-    
-    first = false
-    
-    unless @current_time
-      first = true
-      midnight = Time.local(@now.year, @now.month, @now.day)
-      yesterday_midnight = midnight - full_day
-      tomorrow_midnight = midnight + full_day
-
-      catch :done do
-        if pointer == :future
-          if @type.ambiguous?
-            [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t >= @now
-            end
-          else
-            [midnight + @type, tomorrow_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t >= @now
-            end
-          end
-        else # pointer == :past
-          if @type.ambiguous?
-            [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
-              (@current_time = t; throw :done) if t <= @now
-            end
-          else
-            [midnight + @type, yesterday_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t <= @now
-            end
-          end
-        end
-      end
-      
-      @current_time || raise("Current time cannot be nil at this point")
-    end
-    
-    unless first
-      increment = @type.ambiguous? ? half_day : full_day
-      @current_time += pointer == :future ? increment : -increment
-    end
-    
-    Chronic::Span.new(@current_time, @current_time + width)
-  end
-  
-  def this(context = :future)
-    super
-    
-    context = :future if context == :none
-    
-    self.next(context)
-  end
-  
-  def width
-    1
-  end
-  
-  def to_s
-    super << '-time-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Week.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Week.php
deleted file mode 100644 (file)
index ec88ff1..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
-  WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_week_start
-      case pointer
-      when :future
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = @now
-        next_sunday_span = sunday_repeater.next(:future)
-        @current_week_start = next_sunday_span.begin
-      when :past
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        sunday_repeater.next(:past)
-        last_sunday_span = sunday_repeater.next(:past)
-        @current_week_start = last_sunday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_week_start += direction * WEEK_SECONDS
-    end
-    
-    Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      this_sunday_span = sunday_repeater.this(:future)
-      this_week_end = this_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_end)
-    when :past
-      this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_week_start = last_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_end)
-    when :none
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_week_start = last_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * WEEK_SECONDS
-  end
-  
-  def width
-    WEEK_SECONDS
-  end
-  
-  def to_s
-    super << '-week'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Weekend.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Weekend.php
deleted file mode 100644 (file)
index f012267..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
-  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_week_start
-      case pointer
-      when :future
-        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-        saturday_repeater.start = @now
-        next_saturday_span = saturday_repeater.next(:future)
-        @current_week_start = next_saturday_span.begin
-      when :past
-        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-        saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        last_saturday_span = saturday_repeater.next(:past)
-        @current_week_start = last_saturday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
-    end
-    
-    Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future, :none
-      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-      saturday_repeater.start = @now
-      this_saturday_span = saturday_repeater.this(:future)
-      Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
-    when :past
-      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-      saturday_repeater.start = @now
-      last_saturday_span = saturday_repeater.this(:past)
-      Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = span.begin
-    start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
-    Chronic::Span.new(start, start + (span.end - span.begin))
-  end
-  
-  def width
-    WEEKEND_SECONDS
-  end
-  
-  def to_s
-    super << '-weekend'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Year.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Repeaters/Year.php
deleted file mode 100644 (file)
index 426371f..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
-  
-  def next(pointer)
-    super
-    
-    if !@current_year_start
-      case pointer
-      when :future
-        @current_year_start = Time.construct(@now.year + 1)
-      when :past
-        @current_year_start = Time.construct(@now.year - 1)
-      end
-    else
-      diff = pointer == :future ? 1 : -1
-      @current_year_start = Time.construct(@current_year_start.year + diff)
-    end
-    
-    Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
-      this_year_end = Time.construct(@now.year + 1, 1, 1)
-    when :past
-      this_year_start = Time.construct(@now.year, 1, 1)
-      this_year_end = Time.construct(@now.year, @now.month, @now.day)
-    when :none
-      this_year_start = Time.construct(@now.year, 1, 1)
-      this_year_end = Time.construct(@now.year + 1, 1, 1)
-    end
-    
-    Chronic::Span.new(this_year_start, this_year_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    
-    sb = span.begin
-    new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
-    
-    se = span.end
-    new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
-    
-    Chronic::Span.new(new_begin, new_end)
-  end
-  
-  def width
-    (365 * 24 * 60 * 60)
-  end
-  
-  def to_s
-    super << '-year'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Scalar.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Scalar.php
deleted file mode 100644 (file)
index b08cfee..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-module Chronic
-
-  class Scalar < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_scalars(token, post_token)
-      if token.word =~ /^\d*$/
-        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
-          return Scalar.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_days(token, post_token)
-      if token.word =~ /^\d\d?$/
-        unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
-          return ScalarDay.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_months(token, post_token)
-      if token.word =~ /^\d\d?$/
-        unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
-          return ScalarMonth.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_years(token, post_token)
-      if token.word =~ /^([1-9]\d)?\d\d?$/
-        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
-          return ScalarYear.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def to_s
-      'scalar'
-    end
-  end
-  
-  class ScalarDay < Scalar #:nodoc:
-    def to_s
-      super << '-day-' << @type.to_s
-    end
-  end
-  
-  class ScalarMonth < Scalar #:nodoc:
-    def to_s
-      super << '-month-' << @type.to_s
-    end
-  end
-  
-  class ScalarYear < Scalar #:nodoc:
-    def to_s
-      super << '-year-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Separator.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Separator.php
deleted file mode 100644 (file)
index 86c56e3..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-module Chronic
-
-  class Separator < Tag #:nodoc:
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-    
-    def self.scan_for_commas(token)
-      scanner = {/^,$/ => :comma}
-      scanner.keys.each do |scanner_item|
-        return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_slash_or_dash(token)
-      scanner = {/^-$/ => :dash,
-                 /^\/$/ => :slash}
-      scanner.keys.each do |scanner_item|
-        return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_at(token)
-      scanner = {/^(at|@)$/ => :at}
-      scanner.keys.each do |scanner_item|
-        return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_in(token)
-      scanner = {/^in$/ => :in}
-      scanner.keys.each do |scanner_item|
-        return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'separator'
-    end
-  end
-  
-  class SeparatorComma < Separator #:nodoc:
-    def to_s
-      super << '-comma'
-    end
-  end
-  
-  class SeparatorSlashOrDash < Separator #:nodoc:
-    def to_s
-      super << '-slashordash-' << @type.to_s
-    end
-  end
-  
-  class SeparatorAt < Separator #:nodoc:
-    def to_s
-      super << '-at'
-    end
-  end
-  
-  class SeparatorIn < Separator #:nodoc:
-    def to_s
-      super << '-in'
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php
new file mode 100644 (file)
index 0000000..a079640
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Tokens are tagged with subclassed instances of this class when
+ * they match specific criteria
+ */
+class Horde_Date_Parser_Tag
+{
+    public $type;
+    public $now;
+
+    public function __construct($type)
+    {
+        $this->type = $type;
+    }
+
+    public function start($s)
+    {
+        $this->now = $s;
+    }
+
+}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/TimeZone.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/TimeZone.php
deleted file mode 100644 (file)
index 41041ef..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-module Chronic
-  class TimeZone < Tag #:nodoc:    
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-
-    def self.scan_for_all(token)
-      scanner = {/[PMCE][DS]T/i => :tz}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-
-    def to_s
-      'timezone'
-    end
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php
new file mode 100644 (file)
index 0000000..16b3b70
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+class Horde_Date_Parser_Token
+{
+    public $word;
+    public $tags;
+
+    public function __construct($word)
+    {
+        $this->word = $word;
+        $this->tags = array();
+    }
+
+    /**
+     * Tag this token with the specified tag
+     */
+    public function tag($new_tag)
+    {
+        $this->tags[] = $new_tag;
+    }
+
+    /**
+     * Remove all tags of the given class
+     */
+    public function untag($tag_class)
+    {
+        $this->tags = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
+    }
+
+    /**
+     * Return true if this token has any tags
+     */
+    public function tagged()
+    {
+        return count($this->tags) > 0;
+    }
+
+    /**
+     * Return the Tag that matches the given class
+     */
+    public function getTag($tag_class)
+    {
+        $matches = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
+        return array_shift($matches);
+    }
+
+    /**
+     * Print this Token in a pretty way
+     */
+    public function __toString()
+    {
+        return '(' . implode(', ', $this->tags) . ') ';
+    }
+
+}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Span.php b/framework/Horde_Date_Parser/lib/Horde/Date/Span.php
new file mode 100644 (file)
index 0000000..d61c24c
--- /dev/null
@@ -0,0 +1,27 @@
+  # A Span represents a range of time. Since this class extends
+  # Range, you can use #begin and #end to get the beginning and
+  # ending times of the span (they will be of class Time)
+  class Span < Range
+    # Returns the width of this span in seconds
+    def width
+      (self.end - self.begin).to_i
+    end
+
+    # Add a number of seconds to this span, returning the
+    # resulting Span
+    def +(seconds)
+      Span.new(self.begin + seconds, self.end + seconds)
+    end
+
+    # Subtract a number of seconds to this span, returning the
+    # resulting Span
+    def -(seconds)
+      self + -seconds
+    end
+
+    # Prints this span in a nice fashion
+    def to_s
+      '(' << self.begin.to_s << '..' << self.end.to_s << ')'
+    end
+  end
+