<?php
class Horde_Date_Parser_Locale_Base
{
+ public $definitions = array();
+
/**
# 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
}
}
- 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
+ public function initDefinitions()
+ {
+ if ($this->definitions) { return; }
+
+ $this->definitions = array(
+ 'time' => array(
+ new Horde_Date_Parser_Handler(array('repeater_time', 'repeater_day_portion?'), null),
+ ),
+
+ 'date' => array(
+ new Horde_Date_Parser_Handler(array('repeater_day_name', 'repeater_month_name', 'scalar_day', 'repeater_time', 'time_zone', 'scalar_year'), 'handle_rdn_rmn_sd_t_tz_sy'),
+ new Horde_Date_Parser_Handler(array('repeater_month_name', 'scalar_day', 'scalar_year'), 'handle_rmn_sd_sy'),
+ new Horde_Date_Parser_Handler(array('repeater_month_name', 'scalar_day', 'scalar_year', 'separator_at?', 'time?'), 'handle_rmn_sd_sy'),
+ new Horde_Date_Parser_Handler(array('repeater_month_name', 'scalar_day', 'separator_at?', 'time?'), 'handle_rmn_sd'),
+ new Horde_Date_Parser_Handler(array('repeater_month_name', 'ordinal_day', 'separator_at?', 'time?'), 'handle_rmn_od'),
+ new Horde_Date_Parser_Handler(array('repeater_month_name', 'scalar_year'), 'handle_rmn_sy'),
+ new Horde_Date_Parser_Handler(array('scalar_day', 'repeater_month_name', 'scalar_year', 'separator_at?', 'time?'), 'handle_sd_rmn_sy'),
+ new Horde_Date_Parser_Handler(array('scalar_month', 'separator_slash_or_dash', 'scalar_day', 'separator_slash_or_dash', 'scalar_year', 'separator_at?', 'time?'), 'handle_sm_sd_sy'),
+ new Horde_Date_Parser_Handler(array('scalar_day', 'separator_slash_or_dash', 'scalar_month', 'separator_slash_or_dash', 'scalar_year', 'separator_at?', 'time?'), 'handle_sd_sm_sy'),
+ new Horde_Date_Parser_Handler(array('scalar_year', 'separator_slash_or_dash', 'scalar_month', 'separator_slash_or_dash', 'scalar_day', 'separator_at?', 'time?'), 'handle_sy_sm_sd'),
+ new Horde_Date_Parser_Handler(array('scalar_month', 'separator_slash_or_dash', 'scalar_year'), 'handle_sm_sy'),
+ ),
+
+ // tonight at 7pm
+ 'anchor' => array(
+ new Horde_Date_Parser_Handler(array('grabber?', 'repeater', 'separator_at?', 'repeater?', 'repeater?'), 'handle_r'),
+ new Horde_Date_Parser_Handler(array('grabber?', 'repeater', 'repeater', 'separator_at?', 'repeater?', 'repeater?'), 'handle_r'),
+ new Horde_Date_Parser_Handler(array('repeater', 'grabber', 'repeater'), 'handle_r_g_r'),
+ ),
+
+ // 3 weeks from now, in 2 months
+ 'arrow' => array(
+ new Horde_Date_Parser_Handler(array('scalar', 'repeater', 'pointer'), 'handle_s_r_p'),
+ new Horde_Date_Parser_Handler(array('pointer', 'scalar', 'repeater'), 'handle_p_s_r'),
+ new Horde_Date_Parser_Handler(array('scalar', 'repeater', 'pointer', 'anchor'), 'handle_s_r_p_a'),
+ ),
+
+ // 3rd week in march
+ 'narrow' => array(
+ new Horde_Date_Parser_Handler(array('ordinal', 'repeater', 'separator_in', 'repeater'), 'handle_o_r_s_r'),
+ new Horde_Date_Parser_Handler(array('ordinal', 'repeater', 'grabber', 'repeater'), 'handle_o_r_g_r'),
+ ),
+ );
+ }
+
+ public function tokensToSpans($tokens, $options)
+ {
+ $this->initDefinitions();
+
+ // maybe it's a specific date
+ foreach ($this->definitions['date'] as $handler) {
+ if ($handler->match($tokens, $this->definitions)) {
+ if (Horde_Date_Parser::$debug) { echo "-date\n"; }
+ $goodTokens = array_filter($tokens, create_function('$o', 'return !$o->getTag("Separator");'));
+ return call_user_func(array($this, $handler->handlerMethod), $goodTokens, $options);
+ }
+ }
+
+ // I guess it's not a specific date, maybe it's just an anchor
+ foreach ($this->definitions['anchor'] as $handler) {
+ if ($handler->match($tokens, $this->definitions)) {
+ if (Horde_Date_Parser::$debug) { echo "-anchor\n"; }
+ $goodTokens = array_filter($tokens, create_function('$o', 'return !$o->getTag("Separator");'));
+ return call_user_func(array($this, $handler->handlerMethod), $goodTokens, $options);
+ }
+ }
+
+ // not an anchor, perhaps it's an arrow
+ foreach ($this->definitions['arrow'] as $handler) {
+ if ($handler->match($tokens, $this->definitions)) {
+ if (Horde_Date_Parser::$debug) { echo "-arrow\n"; }
+ $goodTokens = array_filter($tokens, create_function('$o', 'return !$o->getTag("SeparatorAt") && !$o->getTag("SeparatorSlashOrDash") && !$o->getTag("SeparatorComma");'));
+ return call_user_func(array($this, $handler->handlerMethod), $goodTokens, $options);
+ }
+ }
+
+ // not an arrow, let's hope it's a narrow
+ foreach ($this->definitions['narrow'] as $handler) {
+ if ($handler->match($tokens, $this->definitions)) {
+ if (Horde_Date_Parser::$debug) { echo "-narrow\n"; }
+ //good_tokens = tokens.select { |o| !o.get_tag Separator }
+ return call_user_func(array($this, $handler->handlerMethod), $tokens, $options);
+ }
+ }
+
+ // I guess you're out of luck!
+ if (Horde_Date_Parser::$debug) { echo "-none\n"; }
+ return null;
+ }
+
+
+ public function dayOrTime($dayStart, $timeTokens, $options)
+ {
+ $outerSpan = new Horde_Date_Span($dayStart, $dayStart + (24 * 60 * 60));
+
+ if (!empty($timeTokens)) {
+ $this->now = $outerSpan->begin;
+ return $this->getAnchor($this->dealiasAndDisambiguateTimes($timeTokens, $options), $options);
+ } else {
+ return $outerSpan;
+ }
+ }
+
+
+ public function handle_m_d($month, $day, $timeTokens, $options)
+ {
+ $month->start = $this->now;
+ $span = $month->this($options['context']);
+
+ $dayStart = new Horde_Date(array('year' => $span->begin->year, 'month' => $span->begin->month, 'day' => $day));
+ return $this->dayOrTime($dayStart, $timeTokens, $options);
+ }
+
+ public function handle_rmn_sd($tokens, $options)
+ {
+ return $this->handle_m_d($tokens[0]->getTag('RepeaterMonthName'), $tokens[1]->getTag('ScalarDay')->type, array_slice($tokens, 2), $options);
+ }
+
+ public function handle_rmn_od($tokens, $options)
+ {
+ return $this->handle_m_d($tokens[0]->getTag('RepeaterMonthName'), $tokens[1]->getTag('OrdinalDay')->type, array_slice($tokens, 2), $options);
+ }
+
+ public function handle_rmn_sy($tokens, $options)
+ {
+ $month = $tokens[0]->getTag('RepeaterMonthName')->index;
+ $year = $tokens[1]->getTag('ScalarYear')->type;
+
+ try {
+ return new Horde_Date_Span(new Horde_Date(array('year' => $year, 'month' => $month)), new Horde_Date(array('year' => $year, 'month' => $month + 1)));
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ public function handle_rdn_rmn_sd_t_tz_sy($tokens, $options)
+ {
+ $month = $tokens[1]->getTag('RepeaterMonthName')->index;
+ $day = $tokens[2]->getTag('ScalarDay')->type;
+ $year = $tokens[5]->getTag('ScalarYear')->type;
+
+ try {
+ $dayStart = new Horde_Date(array('year' => $year, 'month' => $month, 'day' => $day));
+ return $this->dayOrTime($daystart, array($tokens[3]), $options);
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ public function handle_rmn_sd_sy($tokens, $options)
+ {
+ $month = $tokens[0]->getTag('RepeaterMonthName')->index;
+ $day = $tokens[1]->getTag('ScalarDay')->type;
+ $year = $tokens[2]->getTag('ScalarYear')->type;
+
+ $timeTokens = array_slice($tokens, 3);
+
+ try {
+ $dayStart = new Horde_Date(array('year' => $year, 'month' => $month, 'day' => $day));
+ return $this->dayOrTime($dayStart, $timeTokens, $options);
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ public function handle_sd_rmn_sy($tokens, $options)
+ {
+ $newTokens = array($tokens[1], $tokens[0], $tokens[2]);
+ $timeTokens = array_slice($tokens, 3);
+ return $this->handle_rmn_sd_sy($newTokens + $timeTokens, $options);
+ }
+
+ public function handle_sm_sd_sy($tokens, $options)
+ {
+ $month = $tokens[0]->getTag('ScalarMonth')->type;
+ $day = $tokens[1]->getTag('ScalarDay')->type;
+ $year = $tokens[2]->getTag('ScalarYear')->type;
+
+ $timeTokens = array_slice($tokens, 3);
+
+ try {
+ $dayStart = new Horde_Date(array('year' => $year, 'month' => $month, 'day' => $day));
+ return $this->dayOrTime($dayStart, $timeTokens, $options);
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ public function handle_sd_sm_sy($tokens, $options)
+ {
+ $newTokens = array($tokens[1], $tokens[0], $tokens[2]);
+ $timeTokens = array_slice($tokens, 3);
+ return $this->handle_sm_sd_sy($newTokens + $timeTokens, $options);
+ }
+
+ public function handle_sy_sm_sd($tokens, $options)
+ {
+ $newTokens = array($tokens[1], $tokens[2], $tokens[0]);
+ $timeTokens = array_slice($tokens, 3);
+ return $this->handle_sm_sd_sy($newTokens + $timeTokens, $options);
+ }
+
+ public function handle_sm_sy($tokens, $options)
+ {
+ $month = $tokens[0]->getTag('ScalarMonth')->type;
+ $year = $tokens[1]->getTag('ScalarYear')->type;
+
+ try {
+ return new Horde_Date_Span(new Horde_Date(array('year' => $year, 'month' => $month)), new Horde_Date(array('year' => $year, 'month' => $month = 1)));
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+
+ /*##########################################################################
+ # Anchors
+ ##########################################################################*/
+
+ public function handle_r($tokens, $options)
+ {
+ $ddTokens = $this->dealiasAndDisambiguateTimes($tokens, $options);
+ return $this->getAnchor($ddTokens, $options);
+ }
+
+ public function handle_r_g_r($tokens, $options)
+ {
+ $newTokens = array($tokens[1], $tokens[0], $tokens[2]);
+ return $this->handle_r($newTokens, $options);
+ }
+
+
+ /*##########################################################################
+ # Arrows
+ ##########################################################################*/
+
+ public function handle_srp($tokens, $span, $options)
+ {
+ $distance = $tokens[0]->getTag('Scalar')->type;
+ $repeater = $tokens[1]->getTag('Repeater');
+ $pointer = $tokens[2]->getTag('Pointer')->type;
+
+ return $repeater->offset($span, $distance, $pointer);
+ }
+
+ public function handle_s_r_p($tokens, $options)
+ {
+ $repeater = $tokens[1]->getTag('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 = $this->parse('this second', array('guess' => false, 'now' => $this->now));
+ return $this->handle_srp($tokens, $span, $options);
+ }
+
+ public function handle_p_s_r($tokens, $options)
+ {
+ $newTokens = array($tokens[1], $tokens[2], $tokens[0]);
+ return $this->handle_s_r_p($newTokens, $options);
+ }
+
+ public function handle_s_r_p_a($tokens, $options)
+ {
+ $anchorSpan = $this->getAnchor(array_slice($tokens, 3), $options);
+ return $this->handle_srp($tokens, $anchorSpan, $options);
+ }
+
+
+ /*##########################################################################
+ # Narrows
+ ##########################################################################*/
+
+ public function handle_orr($tokens, $outerSpan, $options)
+ {
+ $repeater = $tokens[1]->getTag('Repeater');
+ $repeater->start = $outerSpan->begin - 1;
+ $ordinal = $tokens[0]->getTag('Ordinal')->type;
+ $span = null;
+
+ for ($i = 0; $i < $ordinal; $i++) {
+ $span = $repeater->next('future');
+ if ($span->begin > $outerSpan->end) {
+ $span = null;
+ break;
+ }
+ }
+ return $span;
+ }
+
+ public function handle_o_r_s_r($tokens, $options)
+ {
+ $outerSpan = $this->getAnchor(array($tokens[3]), $options);
+ return $this->handle_orr(array($tokens[0], $tokens[1]), $outerSpan, $options);
+ }
+
+ public function handle_o_r_g_r($tokens, $options)
+ {
+ $outerSpan = $this->getAnchor(array($tokens[2], $tokens[3]), $options);
+ return $this->handle_orr(array($tokens[0], $tokens[1]), $outerSpan, $options);
+ }
+
+
+ /*##########################################################################
+ # Support Methods
+ ##########################################################################*/
+
+ public function getAnchor($tokens, $options)
+ {
+ $grabber = $this->componentFactory('Grabber', array('this'));
+ $pointer = 'future';
+
+ $repeaters = $this->getRepeaters($tokens);
+ for ($i = 0, $size = count($repeaters); $i < $size; $i++) {
+ array_pop($tokens);
+ }
+
+ if (count($tokens) && $tokens[0]->getTag('Grabber')) {
+ $grabber = $tokens[0]->getTag('Grabber');
+ array_pop($tokens);
+ }
+
+ $head = array_shift($repeaters);
+ $head->start = $this->now;
+
+ switch ($grabber->type) {
+ case 'last':
+ $outerSpan = $head->next('past');
+ break;
+
+ case 'this':
+ if (count($repeaters)) {
+ $outerSpan = $head->this('none');
+ } else {
+ $outerSpan = $head->this($options['context']);
+ }
+ break;
+
+ case 'next':
+ $outerSpan = $head->next('future');
+ break;
+
+ default:
+ throw new Horde_Date_Parser_Exception('Invalid grabber ' . $grabber->type);
+ }
+
+ if (Horde_Date_Parser::$debug) { echo "--$outerSpan\n"; }
+ return $this->findWithin($repeaters, $outerSpan, $pointer);
+ }
+
+ public function getRepeaters($tokens)
+ {
+ $repeaters = array();
+ foreach ($tokens as $token) {
+ if ($t = $token->getTag('Repeater')) {
+ $repeaters[] = $t;
+ }
+ }
+
+ rsort($repeaters);
+ return $repeaters;
+ }
+
+ /**
+ * Recursively finds repeaters within other repeaters. Returns a Span
+ * representing the innermost time span or null if no repeater union could
+ * be found
+ */
+ public function findWithin($tags, $span, $pointer)
+ {
+ if (Horde_Date_Parser::$debug) { echo "--$span\n"; }
+ if (empty($tags)) { return $span; }
+
+ $head = array_shift($tags);
+ $rest = $tags;
+ $head->start = ($pointer == 'future') ? $span->begin : $span->end;
+ $h = $head->this('none');
+
+ if ($span->include($h->begin) || $span->include($h->end)) {
+ return $this->findWithin($rest, $h, $pointer);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * handle aliases of am/pm
+ * 5:00 in the morning -> 5:00 am
+ * 7:00 in the evening -> 7:00 pm
+ */
+ public function dealiasAndDisambiguateTimes($tokens, $options)
+ {
+ $dayPortionIndex = null;
+ foreach ($tokens as $i => $t) {
+ if ($t->getTag('RepeaterDayPortion')) {
+ $dayPortionIndex = $i;
+ break;
+ }
+ }
+
+ $timeIndex = null;
+ foreach ($tokens as $i => $t) {
+ if ($t->getTag('RepeaterTime')) {
+ $timeIndex = $i;
+ break;
+ }
+ }
+
+ if ($dayPortionIndex && $timeIndex) {
+ $t1 = $tokens[$dayPortionIndex];
+ $t1tag = $t1->getTag('RepeaterDayPortion');
+
+ if ($t1tag->type == 'morning') {
+ if (Horde_Date_Parser::$debug) { echo "--morning->am\n"; }
+ $t1->untag('RepeaterDayPortion');
+ $t1->tag(new Horde_Date_Parser_Locale_Base_RepeaterDayPortion('am'));
+ } elseif (in_array($t1tag->type, array('afternoon', 'evening', 'night'))) {
+ if (Horde_Date_Parser::$debug) { echo "--{$t1tag->type}->pm\n"; }
+ $t1->untag('RepeaterDayPortion');
+ $t1->tag(new Horde_Date_Parser_Locale_Base_RepeaterDayPortion('pm'));
+ }
+ }
+
+ /*
+ # 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 :ambiguousTimeRange is specified
+ if ($options['ambiguousTimeRange'] != 'none') {
+ $ttokens = array();
+ foreach ($tokens as $i => $t0) {
+ $ttokens[] = $t0;
+ $t1 = isset($tokens[$i + 1]) ? $tokens[$i + 1] : null;
+ if ($t0->getTag('RepeaterTime') && $t0->getTag('RepeaterTime')->type->ambiguous() && (!$t1 || !$t1->getTag('RepeaterDayPortion'))) {
+ $distoken = new Horde_Date_Parser_Token('disambiguator');
+ $distoken->tag(new Horde_Date_Parser_Locale_Base_RepeaterDayPortion($options['ambiguousTimeRange']));
+ $ttokens[] = $distoken;
+ }
+ }
+
+ $tokens = $ttokens;
+ }
+
+ return $tokens;
+ }
}