Overview

Classes

  • Horde_Date_Parser
  • Horde_Date_Parser_Handler
  • Horde_Date_Parser_Locale_Base
  • Horde_Date_Parser_Locale_Base_Grabber
  • Horde_Date_Parser_Locale_Base_Ordinal
  • Horde_Date_Parser_Locale_Base_Pointer
  • Horde_Date_Parser_Locale_Base_Repeater
  • Horde_Date_Parser_Locale_Base_Scalar
  • Horde_Date_Parser_Locale_Base_Separator
  • Horde_Date_Parser_Locale_Base_Timezone
  • Horde_Date_Parser_Locale_De
  • Horde_Date_Parser_Locale_De_Grabber
  • Horde_Date_Parser_Locale_De_Ordinal
  • Horde_Date_Parser_Locale_De_Pointer
  • Horde_Date_Parser_Locale_De_Repeater
  • Horde_Date_Parser_Locale_De_Scalar
  • Horde_Date_Parser_Locale_De_Separator
  • Horde_Date_Parser_Locale_De_Timezone
  • Horde_Date_Parser_Locale_Pt
  • Horde_Date_Parser_Locale_Pt_Grabber
  • Horde_Date_Parser_Locale_Pt_Ordinal
  • Horde_Date_Parser_Locale_Pt_Pointer
  • Horde_Date_Parser_Locale_Pt_Repeater
  • Horde_Date_Parser_Locale_Pt_Scalar
  • Horde_Date_Parser_Locale_Pt_Separator
  • Horde_Date_Parser_Locale_Pt_Timezone
  • Horde_Date_Parser_Result
  • Horde_Date_Parser_Token

Exceptions

  • Horde_Date_Parser_Exception
  • Overview
  • Class
  • Tree
  1: <?php
  2: /**
  3:  */
  4: class Horde_Date_Parser_Locale_Base
  5: {
  6:     public $definitions = array();
  7:     public $args = array();
  8:     public $now;
  9: 
 10:     public function __construct($args)
 11:     {
 12:         $this->args = $args;
 13:     }
 14: 
 15:     /**
 16:     # Parses a string containing a natural language date or time. If the parser
 17:     # can find a date or time, either a Horde_Date or Horde_Date_Span will be returned
 18:     # (depending on the value of <tt>:return</tt>). If no date or time can be found,
 19:     # +nil+ will be returned.
 20:     #
 21:     # Options are:
 22:     #
 23:     # [<tt>:context</tt>]
 24:     #     <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
 25:     #
 26:     #     If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
 27:     #     and if an ambiguous string is given, it will assume it is in the
 28:     #     past. Specify <tt>:future</tt> or omit to set a future context.
 29:     #
 30:     # [<tt>:now</tt>]
 31:     #     Time (defaults to time())
 32:     #
 33:     #     By setting <tt>:now</tt> to a Horde_Date, all computations will be based off
 34:     #     of that time instead of time().
 35:     #
 36:     # [<tt>:return</tt>]
 37:     #     'result', 'span', or 'date' (defaults to 'date')
 38:     #
 39:     #     By default, the parser will guess a single point in time for the
 40:     #     given date or time. If you'd rather have the entire time span returned,
 41:     #     set <tt>:return</tt> to 'span' and a Horde_Date_Span will be returned.
 42:     #     If you want the entire result, including tokens (for retrieving the text
 43:     #     that was or was not tagged, for example), set <tt>:return</tt> to 'result'
 44:     #     and you will get a result object.
 45:     #
 46:     # [<tt>:ambiguousTimeRange</tt>]
 47:     #     Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
 48:     #
 49:     #     If an Integer is given, ambiguous times (like 5:00) will be
 50:     #     assumed to be within the range of that time in the AM to that time
 51:     #     in the PM. For example, if you set it to <tt>7</tt>, then the parser will
 52:     #     look for the time between 7am and 7pm. In the case of 5:00, it would
 53:     #     assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
 54:     #     will be made, and the first matching instance of that time will
 55:     #     be used.
 56:     */
 57:     public function parse($text, $specifiedOptions = array())
 58:     {
 59:         // get options and set defaults if necessary
 60:         $defaultOptions = array(
 61:             'context' => 'future',
 62:             'now' => new Horde_Date(time()),
 63:             'return' => 'date',
 64:             'ambiguousTimeRange' => 6,
 65:         );
 66:         $options = array_merge($defaultOptions, $this->args, $specifiedOptions);
 67: 
 68:         // ensure the specified options are valid
 69:         foreach (array_keys($specifiedOptions) as $key) {
 70:             if (!isset($defaultOptions[$key])) {
 71:                 throw new InvalidArgumentException("$key is not a valid option key");
 72:             }
 73:         }
 74: 
 75:         if (!in_array($options['context'], array('past', 'future', 'none'))) {
 76:             throw new InvalidArgumentException("Invalid value " . $options['context'] . " for 'context' specified. Valid values are 'past', 'future', and 'none'");
 77:         }
 78: 
 79:         // store now for later =)
 80:         $this->now = $options['now'];
 81: 
 82:         // put the text into a normal format to ease scanning
 83:         $text = $this->preNormalize($text);
 84: 
 85:         // get base tokens for each word
 86:         $tokens = $this->preTokenize($text);
 87: 
 88:         // scan the tokens with each token scanner
 89:         foreach (array('Repeater') as $tokenizer) {
 90:             $tokenizer = $this->componentFactory($tokenizer);
 91:             $tokens = $tokenizer->scan($tokens, $options);
 92:         }
 93: 
 94:         foreach (array('Grabber', 'Pointer', 'Scalar', 'Ordinal', 'Separator', 'Timezone') as $tokenizer) {
 95:             $tokenizer = $this->componentFactory($tokenizer);
 96:             $tokens = $tokenizer->scan($tokens);
 97:         }
 98: 
 99:         // strip any non-tagged tokens
100:         $taggedTokens = array_values(array_filter($tokens, create_function('$t', 'return $t->tagged();')));
101: 
102:         // Remove tokens we know we don't want - for example, if the first token
103:         // is a separator, drop it.
104:         $taggedTokens = $this->postTokenize($taggedTokens);
105: 
106:         // do the heavy lifting
107:         $span = $this->tokensToSpan($taggedTokens, $options);
108: 
109:         // generate the result and return it, the span, or a guessed time within the span
110:         $result = new Horde_Date_Parser_Result($span, $tokens);
111:         switch ($options['return']) {
112:         case 'result':
113:             return $result;
114:         case 'span':
115:             return $result->span;
116:         case 'date':
117:             return $result->guess();
118:         }
119:     }
120: 
121:     public function componentFactory($component, $args = null)
122:     {
123:         $locale = isset($this->args['locale']) ? $this->args['locale'] : null;
124:         if ($locale && strtolower($locale) != 'base') {
125:             $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
126:             $class = 'Horde_Date_Parser_Locale_' . $locale . '_' . $component;
127:             if (class_exists($class)) {
128:                 return new $class($args);
129:             }
130: 
131:             $language = array_shift(explode('_', $locale));
132:             if ($language != $locale) {
133:                 $class = 'Horde_Date_Parser_Locale_' . $language . '_' . $component;
134:                 if (class_exists($class)) {
135:                     return new $class($args);
136:                 }
137:             }
138:         }
139: 
140:         $class = 'Horde_Date_Parser_Locale_Base_' . $component;
141:         return new $class($args);
142:     }
143: 
144:     /**
145:     # Clean up the specified input text by stripping unwanted characters,
146:     # converting idioms to their canonical form, converting number words
147:     # to numbers (three => 3), and converting ordinal words to numeric
148:     # ordinals (third => 3rd)
149:     */
150:     public function preNormalize($text)
151:     {
152:         $text = strtolower($text);
153:         $text = $this->numericizeNumbers($text);
154:         $text = preg_replace('/[\'"\.]/', '', $text);
155:         $text = preg_replace('/([\/\-\,\@])/', ' \1 ', $text);
156:         $text = preg_replace('/\btoday\b/', 'this day', $text);
157:         $text = preg_replace('/\btomm?orr?ow\b/', 'next day', $text);
158:         $text = preg_replace('/\byesterday\b/', 'last day', $text);
159:         $text = preg_replace('/\bnoon\b/', '12:00', $text);
160:         $text = preg_replace('/\bmidnight\b/', '24:00', $text);
161:         $text = preg_replace('/\bbefore now\b/', 'past', $text);
162:         $text = preg_replace('/\bnow\b/', 'this second', $text);
163:         $text = preg_replace('/\b(ago|before)\b/', 'past', $text);
164:         $text = preg_replace('/\bthis past\b/', 'last', $text);
165:         $text = preg_replace('/\bthis last\b/', 'last', $text);
166:         $text = preg_replace('/\b(?:in|during) the (morning)\b/', '\1', $text);
167:         $text = preg_replace('/\b(?:in the|during the|at) (afternoon|evening|night)\b/', '\1', $text);
168:         $text = preg_replace('/\btonight\b/', 'this night', $text);
169:         $text = preg_replace('/(?=\w)([ap]m|oclock)\b/', ' \1', $text);
170:         $text = preg_replace('/\b(hence|after|from)\b/', 'future', $text);;
171:         $text = $this->numericizeOrdinals($text);
172: 
173:         return $text;
174:     }
175: 
176:     /**
177:      * Convert number words to numbers (three => 3)
178:      */
179:     public function numericizeNumbers($text)
180:     {
181:         return Horde_Support_Numerizer::numerize($text, $this->args);
182:     }
183: 
184:     /**
185:      * Convert ordinal words to numeric ordinals (third => 3rd)
186:      */
187:     public function numericizeOrdinals($text)
188:     {
189:         return $text;
190:     }
191: 
192:     /**
193:      * Split the text on spaces and convert each word into a Token.
194:      *
195:      * @param string $text  Text to tokenize
196:      *
197:      * @return array  Array of Horde_Date_Parser_Tokens.
198:      */
199:     public function preTokenize($text)
200:     {
201:         return array_map(create_function('$w', 'return new Horde_Date_Parser_Token($w);'), preg_split('/\s+/', $text));
202:     }
203: 
204:     /**
205:      * Remove tokens that don't fit our definitions.
206:      *
207:      * @param array $tokens Array of tagged tokens.
208:      *
209:      * @return array  Filtered tagged tokens.
210:      */
211:     public function postTokenize($tokens)
212:     {
213:         if (!count($tokens)) {
214:             return $tokens;
215:         }
216: 
217:         // First rule: if the first token is a separator, remove it from the
218:         // list of tokens we consider in tokensToSpan().
219:         $first = clone($tokens[0]);
220:         $first->untag('separator_at');
221:         $first->untag('separator_comma');
222:         $first->untag('separator_in');
223:         $first->untag('separator_slash_or_dash');
224:         if (!$first->tagged()) {
225:             array_shift($tokens);
226:         }
227: 
228:         // Second rule: if the last token is a separator, remove it from the
229:         // list of tokens we consider in tokensToSpan().
230:         $last = clone($tokens[count($tokens) - 1]);
231:         $last->untag('separator_at');
232:         $last->untag('separator_comma');
233:         $last->untag('separator_in');
234:         $last->untag('separator_slash_or_dash');
235:         if (!$last->tagged()) {
236:             array_pop($tokens);
237:         }
238: 
239:         return $tokens;
240:     }
241: 
242:     public function initDefinitions()
243:     {
244:         if ($this->definitions) {
245:             return;
246:         }
247: 
248:         $this->definitions = array(
249:             'time' => array(
250:                 new Horde_Date_Parser_Handler(array(':repeater_time', ':repeater_day_portion?'), null),
251:             ),
252: 
253:             'date' => array(
254:                 new Horde_Date_Parser_Handler(array(':repeater_day_name', ':repeater_month_name', ':scalar_day', ':repeater_time', ':timezone', ':scalar_year'), 'handle_rdn_rmn_sd_t_tz_sy'),
255:                 new Horde_Date_Parser_Handler(array(':repeater_month_name', ':scalar_day', ':scalar_year'), 'handle_rmn_sd_sy'),
256:                 new Horde_Date_Parser_Handler(array(':repeater_month_name', ':scalar_day', ':scalar_year', ':separator_at?', 'time?'), 'handle_rmn_sd_sy'),
257:                 new Horde_Date_Parser_Handler(array(':repeater_month_name', ':scalar_day', ':separator_at?', 'time?'), 'handle_rmn_sd'),
258:                 new Horde_Date_Parser_Handler(array(':repeater_month_name', ':ordinal_day', ':separator_at?', 'time?'), 'handle_rmn_od'),
259:                 new Horde_Date_Parser_Handler(array(':repeater_month_name', ':scalar_year'), 'handle_rmn_sy'),
260:                 new Horde_Date_Parser_Handler(array(':scalar_day', ':repeater_month_name', ':scalar_year', ':separator_at?', 'time?'), 'handle_sd_rmn_sy'),
261:                 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'),
262:                 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'),
263:                 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'),
264:                 new Horde_Date_Parser_Handler(array(':scalar_month', ':separator_slash_or_dash', ':scalar_year'), 'handle_sm_sy'),
265:             ),
266: 
267:             // tonight at 7pm
268:             'anchor' => array(
269:                 new Horde_Date_Parser_Handler(array(':grabber?', ':repeater', ':separator_at?', ':repeater?', ':repeater?'), 'handle_r'),
270:                 new Horde_Date_Parser_Handler(array(':grabber?', ':repeater', ':repeater', ':separator_at?', ':repeater?', ':repeater?'), 'handle_r'),
271:                 new Horde_Date_Parser_Handler(array(':repeater', ':grabber', ':repeater'), 'handle_r_g_r'),
272:             ),
273: 
274:             // 3 weeks from now, in 2 months
275:             'arrow' => array(
276:                 new Horde_Date_Parser_Handler(array(':scalar', ':repeater', ':pointer'), 'handle_s_r_p'),
277:                 new Horde_Date_Parser_Handler(array(':pointer', ':scalar', ':repeater'), 'handle_p_s_r'),
278:                 new Horde_Date_Parser_Handler(array(':scalar', ':repeater', ':pointer', 'anchor'), 'handle_s_r_p_a'),
279:             ),
280: 
281:             // 3rd week in march
282:             'narrow' => array(
283:                 new Horde_Date_Parser_Handler(array(':ordinal', ':repeater', ':separator_in', ':repeater'), 'handle_o_r_s_r'),
284:                 new Horde_Date_Parser_Handler(array(':ordinal', ':repeater', ':grabber', ':repeater'), 'handle_o_r_g_r'),
285:             ),
286:         );
287:     }
288: 
289:     public function tokensToSpan($tokens, $options)
290:     {
291:         $this->initDefinitions();
292: 
293:         // maybe it's a specific date
294:         foreach ($this->definitions['date'] as $handler) {
295:             if ($handler->match($tokens, $this->definitions)) {
296:                 $goodTokens = array_values(array_filter($tokens, create_function('$o', 'return !$o->getTag("separator");')));
297:                 $this->debug($handler->handlerMethod, $goodTokens, $options);
298:                 return call_user_func(array($this, $handler->handlerMethod), $goodTokens, $options);
299:             }
300:         }
301: 
302:         // I guess it's not a specific date, maybe it's just an anchor
303:         foreach ($this->definitions['anchor'] as $handler) {
304:             if ($handler->match($tokens, $this->definitions)) {
305:                 $goodTokens = array_values(array_filter($tokens, create_function('$o', 'return !$o->getTag("separator");')));
306:                 $this->debug($handler->handlerMethod, $goodTokens, $options);
307:                 return call_user_func(array($this, $handler->handlerMethod), $goodTokens, $options);
308:             }
309:         }
310: 
311:         // not an anchor, perhaps it's an arrow
312:         foreach ($this->definitions['arrow'] as $handler) {
313:             if ($handler->match($tokens, $this->definitions)) {
314:                 $goodTokens = array_values(array_filter($tokens, create_function('$o', 'return !$o->getTag("separator_at") && !$o->getTag("separator_slash_or_dash") && !$o->getTag("separator_comma");')));
315:                 $this->debug($handler->handlerMethod, $goodTokens, $options);
316:                 return call_user_func(array($this, $handler->handlerMethod), $goodTokens, $options);
317:             }
318:         }
319: 
320:         // not an arrow, let's hope it's a narrow
321:         foreach ($this->definitions['narrow'] as $handler) {
322:             if ($handler->match($tokens, $this->definitions)) {
323:                 //good_tokens = tokens.select { |o| !o.get_tag Separator }
324:                 $this->debug($handler->handlerMethod, $tokens, $options);
325:                 return call_user_func(array($this, $handler->handlerMethod), $tokens, $options);
326:             }
327:         }
328: 
329:         return null;
330:     }
331: 
332:     public function dayOrTime($dayStart, $timeTokens, $options)
333:     {
334:         $outerSpan = new Horde_Date_Span($dayStart, $dayStart->add(array('day' => 1)));
335: 
336:         if (!empty($timeTokens)) {
337:             $this->now = $outerSpan->begin;
338:             return $this->getAnchor($this->dealiasAndDisambiguateTimes($timeTokens, $options), $options);
339:         } else {
340:             return $outerSpan;
341:         }
342:     }
343: 
344: 
345:     public function handle_m_d($month, $day, $timeTokens, $options)
346:     {
347:         $month->now = $this->now;
348:         $span = $month->this($options['context']);
349: 
350:         $dayStart = new Horde_Date($span->begin->year, $span->begin->month, $day);
351:         return $this->dayOrTime($dayStart, $timeTokens, $options);
352:     }
353: 
354:     public function handle_rmn_sd($tokens, $options)
355:     {
356:         return $this->handle_m_d($tokens[0]->getTag('repeater_month_name'), $tokens[1]->getTag('scalar_day'), array_slice($tokens, 2), $options);
357:     }
358: 
359:     public function handle_rmn_od($tokens, $options)
360:     {
361:         return $this->handle_m_d($tokens[0]->getTag('repeater_month_name'), $tokens[1]->getTag('ordinal_day'), array_slice($tokens, 2), $options);
362:     }
363: 
364:     public function handle_rmn_sy($tokens, $options)
365:     {
366:         $month = $tokens[0]->getTag('repeater_month_name')->index();
367:         $year = $tokens[1]->getTag('scalar_year');
368: 
369:         try {
370:             return new Horde_Date_Span(new Horde_Date($year, $month, 1), new Horde_Date($year, $month + 1, 1));
371:         } catch (Exception $e) {
372:             return null;
373:         }
374:     }
375: 
376:     public function handle_rdn_rmn_sd_t_tz_sy($tokens, $options)
377:     {
378:         $month = $tokens[1]->getTag('repeater_month_name')->index();
379:         $day = $tokens[2]->getTag('scalar_day');
380:         $year = $tokens[5]->getTag('scalar_year');
381: 
382:         try {
383:             $dayStart = new Horde_Date($year, $month, $day);
384:             return $this->dayOrTime($dayStart, array($tokens[3]), $options);
385:         } catch (Exception $e) {
386:             return null;
387:         }
388:     }
389: 
390:     public function handle_rmn_sd_sy($tokens, $options)
391:     {
392:         $month = $tokens[0]->getTag('repeater_month_name')->index();
393:         $day = $tokens[1]->getTag('scalar_day');
394:         $year = $tokens[2]->getTag('scalar_year');
395: 
396:         $timeTokens = array_slice($tokens, 3);
397: 
398:         try {
399:             $dayStart = new Horde_Date($year, $month, $day);
400:             return $this->dayOrTime($dayStart, $timeTokens, $options);
401:         } catch (Exception $e) {
402:             return null;
403:         }
404:     }
405: 
406:     public function handle_sd_rmn_sy($tokens, $options)
407:     {
408:         $newTokens = array($tokens[1], $tokens[0], $tokens[2]);
409:         $timeTokens = array_slice($tokens, 3);
410:         return $this->handle_rmn_sd_sy(array_merge($newTokens, $timeTokens), $options);
411:     }
412: 
413:     public function handle_sm_sd_sy($tokens, $options)
414:     {
415:         $month = $tokens[0]->getTag('scalar_month');
416:         $day = $tokens[1]->getTag('scalar_day');
417:         $year = $tokens[2]->getTag('scalar_year');
418: 
419:         $timeTokens = array_slice($tokens, 3);
420: 
421:         try {
422:             $dayStart = new Horde_Date($year, $month, $day);
423:             return $this->dayOrTime($dayStart, $timeTokens, $options);
424:         } catch (Exception $e) {
425:             return null;
426:         }
427:     }
428: 
429:     public function handle_sd_sm_sy($tokens, $options)
430:     {
431:         $newTokens = array($tokens[1], $tokens[0], $tokens[2]);
432:         $timeTokens = array_slice($tokens, 3);
433:         return $this->handle_sm_sd_sy(array_merge($newTokens, $timeTokens), $options);
434:     }
435: 
436:     public function handle_sy_sm_sd($tokens, $options)
437:     {
438:         $newTokens = array($tokens[1], $tokens[2], $tokens[0]);
439:         $timeTokens = array_slice($tokens, 3);
440:         return $this->handle_sm_sd_sy(array_merge($newTokens, $timeTokens), $options);
441:     }
442: 
443:     public function handle_sm_sy($tokens, $options)
444:     {
445:         $month = $tokens[0]->getTag('scalar_month');
446:         $year = $tokens[1]->getTag('scalar_year');
447: 
448:         try {
449:             return new Horde_Date_Span(new Horde_Date($year, $month, 1), new Horde_Date($year, $month + 1, 1));
450:         } catch (Exception $e) {
451:             return null;
452:         }
453:     }
454: 
455: 
456:     /*##########################################################################
457:     # Anchors
458:     ##########################################################################*/
459: 
460:     public function handle_r($tokens, $options)
461:     {
462:         $ddTokens = $this->dealiasAndDisambiguateTimes($tokens, $options);
463:         return $this->getAnchor($ddTokens, $options);
464:     }
465: 
466:     public function handle_r_g_r($tokens, $options)
467:     {
468:         $newTokens = array($tokens[1], $tokens[0], $tokens[2]);
469:         return $this->handle_r($newTokens, $options);
470:     }
471: 
472: 
473:     /*##########################################################################
474:     # Arrows
475:     ##########################################################################*/
476: 
477:     public function handle_srp($tokens, $span, $options)
478:     {
479:         $distance = $tokens[0]->getTag('scalar');
480:         $repeater = $tokens[1]->getTag('repeater');
481:         $pointer = $tokens[2]->getTag('pointer');
482: 
483:         return $repeater->offset($span, $distance, $pointer);
484:     }
485: 
486:     public function handle_s_r_p($tokens, $options)
487:     {
488:         $span = new Horde_Date_Span($this->now, $this->now->add(1));
489:         return $this->handle_srp($tokens, $span, $options);
490:     }
491: 
492:     public function handle_p_s_r($tokens, $options)
493:     {
494:         $newTokens = array($tokens[1], $tokens[2], $tokens[0]);
495:         return $this->handle_s_r_p($newTokens, $options);
496:     }
497: 
498:     public function handle_s_r_p_a($tokens, $options)
499:     {
500:         $anchorSpan = $this->getAnchor(array_slice($tokens, 3), $options);
501:         return $this->handle_srp($tokens, $anchorSpan, $options);
502:     }
503: 
504: 
505:     /*##########################################################################
506:     # Narrows
507:     ##########################################################################*/
508: 
509:     public function handle_orr($tokens, $outerSpan, $options)
510:     {
511:         $repeater = $tokens[1]->getTag('repeater');
512:         $repeater->now = $outerSpan->begin->sub(1);
513:         $ordinal = $tokens[0]->getTag('ordinal');
514:         $span = null;
515: 
516:         for ($i = 0; $i < $ordinal; $i++) {
517:             $span = $repeater->next('future');
518:             if ($span->begin->after($outerSpan->end)) {
519:                 $span = null;
520:                 break;
521:             }
522:         }
523:         return $span;
524:     }
525: 
526:     public function handle_o_r_s_r($tokens, $options)
527:     {
528:         $outerSpan = $this->getAnchor(array($tokens[3]), $options);
529:         return $this->handle_orr(array($tokens[0], $tokens[1]), $outerSpan, $options);
530:     }
531: 
532:     public function handle_o_r_g_r($tokens, $options)
533:     {
534:         $outerSpan = $this->getAnchor(array($tokens[2], $tokens[3]), $options);
535:         return $this->handle_orr(array($tokens[0], $tokens[1]), $outerSpan, $options);
536:     }
537: 
538: 
539:     /*##########################################################################
540:     # Logging Methods
541:     ##########################################################################*/
542: 
543:     public function debug($method, $args)
544:     {
545:         $args = func_get_args();
546:         $method = array_shift($args);
547:         // echo "$method\n";
548:     }
549: 
550: 
551:     /*##########################################################################
552:     # Support Methods
553:     ##########################################################################*/
554: 
555:     public function getAnchor($tokens, $options)
556:     {
557:         $grabber = 'this';
558:         $pointer = 'future';
559: 
560:         $repeaters = $this->getRepeaters($tokens);
561:         for ($i = 0, $size = count($repeaters); $i < $size; $i++) {
562:             array_pop($tokens);
563:         }
564: 
565:         if (count($tokens) && $tokens[0]->getTag('grabber')) {
566:             $grabber = $tokens[0]->getTag('grabber');
567:             array_pop($tokens);
568:         }
569: 
570:         $head = array_shift($repeaters);
571:         $head->now = $this->now;
572: 
573:         switch ($grabber) {
574:         case 'last':
575:             $outerSpan = $head->next('past');
576:             break;
577: 
578:         case 'this':
579:             if (count($repeaters)) {
580:                 $outerSpan = $head->this('none');
581:             } else {
582:                 $outerSpan = $head->this($options['context']);
583:             }
584:             break;
585: 
586:         case 'next':
587:             $outerSpan = $head->next('future');
588:             break;
589: 
590:         default:
591:             throw new Horde_Date_Parser_Exception('Invalid grabber ' . $grabber);
592:         }
593: 
594:         return $this->findWithin($repeaters, $outerSpan, $pointer);
595:     }
596: 
597:     public function getRepeaters($tokens)
598:     {
599:         $repeaters = array();
600:         foreach ($tokens as $token) {
601:             if ($t = $token->getTag('repeater')) {
602:                 $repeaters[] = $t;
603:             }
604:         }
605: 
606:         // Return repeaters in order from widest (years) to smallest (seconds)
607:         usort($repeaters, create_function('$a, $b', 'return $b->width() > $a->width();'));
608:         return $repeaters;
609:     }
610: 
611:     /**
612:      * Recursively finds repeaters within other repeaters.  Returns a Span
613:      * representing the innermost time span or null if no repeater union could
614:      * be found
615:      */
616:     public function findWithin($tags, $span, $pointer)
617:     {
618:         if (empty($tags)) { return $span; }
619: 
620:         $head = array_shift($tags);
621:         $rest = $tags;
622:         $head->now = ($pointer == 'future') ? $span->begin : $span->end;
623:         $h = $head->this('none');
624: 
625:         if ($span->includes($h->begin) || $span->includes($h->end)) {
626:             return $this->findWithin($rest, $h, $pointer);
627:         } else {
628:             return null;
629:         }
630:     }
631: 
632:     /**
633:      * handle aliases of am/pm
634:      * 5:00 in the morning -> 5:00 am
635:      * 7:00 in the evening -> 7:00 pm
636:      */
637:     public function dealiasAndDisambiguateTimes($tokens, $options)
638:     {
639:         $dayPortionIndex = null;
640:         foreach ($tokens as $i => $t) {
641:             if ($t->getTag('repeater_day_portion')) {
642:                 $dayPortionIndex = $i;
643:                 break;
644:             }
645:         }
646: 
647:         $timeIndex = null;
648:         foreach ($tokens as $i => $t) {
649:             if ($t->getTag('repeater_time')) {
650:                 $timeIndex = $i;
651:                 break;
652:             }
653:         }
654: 
655:         if ($dayPortionIndex !== null && $timeIndex !== null) {
656:             $t1 = $tokens[$dayPortionIndex];
657:             $t1tag = $t1->getTag('repeater_day_portion');
658: 
659:             if ($t1tag->type == 'morning') {
660:                 $t1->untag('repeater_day_portion');
661:                 $t1->tag('repeater_day_portion', new Horde_Date_Repeater_DayPortion('am'));
662:             } elseif (in_array($t1tag->type, array('afternoon', 'evening', 'night'))) {
663:                 $t1->untag('repeater_day_portion');
664:                 $t1->tag('repeater_day_portion', new Horde_Date_Repeater_DayPortion('pm'));
665:             }
666:         }
667: 
668:         // handle ambiguous times if ambiguousTimeRange is specified
669:         if (!isset($options['ambiguousTimeRange']) || $options['ambiguousTimeRange'] != 'none') {
670:             $ttokens = array();
671:             foreach ($tokens as $i => $t0) {
672:                 $ttokens[] = $t0;
673:                 $t1 = isset($tokens[$i + 1]) ? $tokens[$i + 1] : null;
674:                 if ($t0->getTag('repeater_time') && $t0->getTag('repeater_time')->ambiguous && (!$t1 || !$t1->getTag('repeater_day_portion'))) {
675:                     $distoken = new Horde_Date_Parser_Token('disambiguator');
676:                     $distoken->tag('repeater_day_portion', new Horde_Date_Repeater_DayPortion($options['ambiguousTimeRange']));
677:                     $ttokens[] = $distoken;
678:                 }
679:             }
680: 
681:             $tokens = $ttokens;
682:         }
683: 
684:         return $tokens;
685:     }
686: }
687: 
API documentation generated by ApiGen