Development

/branches/1.4/lib/i18n/sfDateFormat.class.php

You must first sign up to be able to contribute.

root/branches/1.4/lib/i18n/sfDateFormat.class.php

Revision 23810, 21.2 kB (checked in by Kris.Wallsmith, 4 years ago)

[1.3] set svn:eol-style property to native and svn:keywords property to Id on all .php files

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2 /**
3  * sfDateFormat class file.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the BSD License.
7  *
8  * Copyright(c) 2004 by Qiang Xue. All rights reserved.
9  *
10  * To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
11  * The latest version of PRADO can be obtained from:
12  * {@link http://prado.sourceforge.net/}
13  *
14  * @author     Wei Zhuo <weizhuo[at]gmail[dot]com>
15  * @version    $Id$
16  * @package    symfony
17  * @subpackage i18n
18  */
19
20 /**
21  * sfDateFormat class.
22  *
23  * The sfDateFormat class allows you to format dates and times with
24  * predefined styles in a locale-sensitive manner. Formatting times
25  * with the sfDateFormat class is similar to formatting dates.
26  *
27  * Formatting dates with the sfDateFormat class is a two-step process.
28  * First, you create a formatter with the getDateInstance method.
29  * Second, you invoke the format method, which returns a string containing
30  * the formatted date.
31  *
32  * DateTime values are formatted using standard or custom patterns stored
33  * in the properties of a DateTimeFormatInfo.
34  *
35  * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
36  * @version v1.0, last update on Sat Dec 04 14:10:49 EST 2004
37  * @package    symfony
38  * @subpackage i18n
39  */
40 class sfDateFormat
41 {
42   /**
43    * A list of tokens and their function call.
44    * @var array
45    */
46   protected $tokens = array(
47     'G' => 'Era',
48     'y' => 'year',
49     'M' => 'mon',
50     'd' => 'mday',
51     'h' => 'Hour12',
52     'H' => 'hours',
53     'm' => 'minutes',
54     's' => 'seconds',
55     'E' => 'wday',
56     'D' => 'yday',
57     'F' => 'DayInMonth',
58     'w' => 'WeekInYear',
59     'W' => 'WeekInMonth',
60     'a' => 'AMPM',
61     'k' => 'HourInDay',
62     'K' => 'HourInAMPM',
63     'z' => 'TimeZone'
64   );
65
66   /**
67    * A list of methods, to be used by the token function calls.
68    * @var array
69    */
70   protected $methods = array();
71
72   /**
73    * The sfDateTimeFormatInfo, containing culture specific patterns and names.
74    * @var sfDateTimeFormatInfo   
75    */
76   protected $formatInfo;
77
78   /**
79    * Initializes a new sfDateFormat.
80    *
81    * @param mixed $formatInfo either, null, a sfCultureInfo instance, a DateTimeFormatInfo instance, or a locale.
82    * @return sfDateFormat instance
83    */
84   function __construct($formatInfo = null)
85   {
86     if (null === $formatInfo)
87     {
88       $this->formatInfo = sfDateTimeFormatInfo::getInvariantInfo();
89     }
90     else if ($formatInfo instanceof sfCultureInfo)
91     {
92       $this->formatInfo = $formatInfo->DateTimeFormat;
93     }
94     else if ($formatInfo instanceof sfDateTimeFormatInfo)
95     {
96       $this->formatInfo = $formatInfo;
97     }
98     else
99     {
100       $this->formatInfo = sfDateTimeFormatInfo::getInstance($formatInfo);
101     }
102
103     $this->methods = get_class_methods($this);
104   }
105
106   /**
107    * Guesses a date without calling strtotime.
108    *
109    * @author Olivier Verdier <Olivier.Verdier@gmail.com>
110    * @param mixed  $time    the time as integer or string in strtotime format.
111    * @param string $pattern the input pattern; default is sql date or timestamp
112    * @return array same array as the getdate function
113    */
114   public function getDate($time, $pattern = null)
115   {
116     if (null === $time)
117     {
118       return null;
119     }
120
121     // if the type is not a php timestamp
122     $isString = (string) $time !== (string) (int) $time;
123
124     if ($isString)
125     {
126       if (!$pattern)
127       {
128         if (strlen($time) == 10)
129         {
130           $pattern = 'i';
131         }
132         else   // otherwise, default:
133         {
134           $pattern = 'I';
135         }
136       }
137
138       $pattern = $this->getPattern($pattern);
139       $tokens = $this->getTokens($pattern);
140       $pregPattern = '';
141       $matchNames = array();
142       // current regex allows any char at the end. avoids duplicating [^\d]+ pattern
143       // this could cause issues with utf character width
144       $allowsAllChars=true;
145       foreach ($tokens as $token)
146       {
147         if ($matchName = $this->getFunctionName($token))
148         {
149           $allowsAllChars = false;
150           $pregPattern .= '(\d+)';
151           $matchNames[] = $matchName;
152         }
153         else
154         {
155           if (!$allowsAllChars)
156           {
157             $allowsAllChars = true;
158             $pregPattern .= '[^\d]+';
159           }
160         }
161       }
162       preg_match('@'.$pregPattern.'@', $time, $matches);
163
164       array_shift($matches);
165
166       if (count($matchNames) == count($matches))
167       {
168         $date = array_combine($matchNames, $matches);
169         // guess the date if input with two digits
170         if (strlen($date['year']) == 2)
171         {
172           $date['year'] = date('Y', mktime(0, 0, 0, 1, 1, $date['year']));
173         }
174         $date = array_map('intval', $date);
175       }
176     }
177
178     // the last attempt has failed we fall back on the default method
179     if (!isset($date))
180     {
181       if ($isString)
182       {
183         $numericalTime = @strtotime($time);
184         if ($numericalTime === false)
185         {
186           throw new sfException(sprintf('Impossible to parse date "%s" with format "%s".', $time, $pattern));
187         }
188       }
189       else
190       {
191         $numericalTime = $time;
192       }
193       $date = @getdate($numericalTime);
194     }
195
196     // we set default values for the time
197     foreach (array('hours', 'minutes', 'seconds') as $timeDiv)
198     {
199       if (!isset($date[$timeDiv]))
200       {
201         $date[$timeDiv] = 0;
202       }
203     }
204
205     return $date;
206   }
207
208   /**
209    * Formats a date according to the pattern.
210    *
211    * @param mixed   $time           the time as integer or string in strtotime format.
212    * @param string  $pattern        the pattern
213    * @param string  $inputPattern   the input pattern
214    * @param string  $charset        the charset
215    * @return string formatted date time.
216    */
217   public function format($time, $pattern = 'F', $inputPattern = null, $charset = 'UTF-8')
218   {
219     $date = $this->getDate($time, $inputPattern);
220
221     if (null === $pattern)
222     {
223       $pattern = 'F';
224     }
225
226     $pattern = $this->getPattern($pattern);
227     $tokens = $this->getTokens($pattern);
228
229     for ($i = 0, $max = count($tokens); $i < $max; $i++)
230     {
231       $pattern = $tokens[$i];
232       if ($pattern{0} == "'" && $pattern{strlen($pattern) - 1} == "'")
233       {
234         $tokens[$i] = str_replace('``````', '\'', preg_replace('/(^\')|(\'$)/', '', $pattern));
235       }
236       else if ($pattern == '``````')
237       {
238         $tokens[$i] = '\'';
239       }
240       else
241       {
242         $function = ucfirst($this->getFunctionName($pattern));
243         if ($function != null)
244         {
245           $fName = 'get'.$function;
246           if (in_array($fName, $this->methods))
247           {
248             $tokens[$i] = $this->$fName($date, $pattern);
249           }
250           else
251           {
252             throw new sfException(sprintf('Function %s not found.', $function));
253           }
254         }
255       }
256     }
257
258     return sfToolkit::I18N_toEncoding(implode('', $tokens), $charset);
259   }
260
261   /**
262    * For a particular token, get the corresponding function to call.
263    *
264    * @param string $token token
265    * @return mixed the function if good token, null otherwise.
266    */
267   protected function getFunctionName($token)
268   {
269     if (isset($this->tokens[$token{0}]))
270     {
271       return $this->tokens[$token{0}];
272     }
273   }
274
275   /**
276    * Gets the pattern from DateTimeFormatInfo or some predefined patterns.
277    * If the $pattern parameter is an array of 2 element, it will assume
278    * that the first element is the date, and second the time
279    * and try to find an appropriate pattern and apply
280    * DateTimeFormatInfo::formatDateTime
281    * See the tutorial documentation for futher details on the patterns.
282    *
283    * @param mixed $pattern a pattern.
284    * @return string a pattern.
285    * @see DateTimeFormatInfo::formatDateTime()
286    */
287   public function getPattern($pattern)
288   {
289     if (is_array($pattern) && count($pattern) == 2)
290     {
291       return $this->formatInfo->formatDateTime($this->getPattern($pattern[0]), $this->getPattern($pattern[1]));
292     }
293
294     switch ($pattern)
295     {
296       case 'd':
297         return $this->formatInfo->ShortDatePattern;
298         break;
299       case 'D':
300         return $this->formatInfo->LongDatePattern;
301         break;
302       case 'p':
303         return $this->formatInfo->MediumDatePattern;
304         break;
305       case 'P':
306         return $this->formatInfo->FullDatePattern;
307         break;       
308       case 't':
309         return $this->formatInfo->ShortTimePattern;
310         break;
311       case 'T':
312         return $this->formatInfo->LongTimePattern;
313         break;
314       case 'q':
315         return $this->formatInfo->MediumTimePattern;
316         break;
317       case 'Q':
318         return $this->formatInfo->FullTimePattern;
319         break;
320       case 'f':
321         return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->ShortTimePattern);
322         break;
323       case 'F':
324         return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->LongTimePattern);
325         break;
326       case 'g':
327         return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->ShortTimePattern);
328         break;
329       case 'G':
330         return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->LongTimePattern);
331         break;
332       case 'i':
333         return 'yyyy-MM-dd';
334         break;
335       case 'I':
336         return 'yyyy-MM-dd HH:mm:ss';
337         break;
338       case 'M':
339       case 'm':
340         return 'MMMM dd';
341         break;
342       case 'R':
343       case 'r':
344         return 'EEE, dd MMM yyyy HH:mm:ss';
345         break;
346       case 's':
347         return 'yyyy-MM-ddTHH:mm:ss';
348         break;
349       case 'u':
350         return 'yyyy-MM-dd HH:mm:ss z';
351         break;
352       case 'U':
353         return 'EEEE dd MMMM yyyy HH:mm:ss';
354         break;
355       case 'Y':
356       case 'y':
357         return 'yyyy MMMM';
358         break;
359       default :
360         return $pattern;
361     }
362   }
363
364   /**
365    * Returns an easy to parse input pattern
366    * yy is replaced by yyyy and h by H
367    *
368    * @param string $pattern pattern.
369    * @return string input pattern
370    */
371   public function getInputPattern($pattern)
372   {
373     $pattern = $this->getPattern($pattern);
374     
375     $pattern = strtr($pattern, array('yyyy' => 'Y', 'h'=>'H', 'z'=>'', 'a'=>''));
376     $pattern = strtr($pattern, array('yy'=>'yyyy', 'Y'=>'yyyy'));
377     
378     return trim($pattern);
379   }
380
381   /**
382    * Tokenizes the pattern. The tokens are delimited by group of
383    * similar characters, e.g. 'aabb' will form 2 tokens of 'aa' and 'bb'.
384    * Any substrings, starting and ending with a single quote (')
385    * will be treated as a single token.
386    *
387    * @param string $pattern pattern.
388    * @return array string tokens in an array.
389    */
390   protected function getTokens($pattern)
391   {
392     $char = null;
393     $tokens = array();
394     $token = null;
395
396     $text = false;
397
398     for ($i = 0, $max = strlen($pattern); $i < $max; $i++)
399     {
400       if ($char == null || $pattern{$i} == $char || $text)
401       {
402         $token .= $pattern{$i};
403       }
404       else
405       {
406         $tokens[] = str_replace("''", "'", $token);
407         $token = $pattern{$i};
408       }
409
410       if ($pattern{$i} == "'" && $text == false)
411       {
412         $text = true;
413       }
414       else if ($text && $pattern{$i} == "'" && $char == "'")
415       {
416         $text = true;
417       }
418       else if ($text && $char != "'" && $pattern{$i} == "'")
419       {
420         $text = false;
421       }
422
423       $char = $pattern{$i};
424
425     }
426     $tokens[] = $token;
427
428     return $tokens;
429   }
430  
431   // makes a unix date from our incomplete $date array
432   protected function getUnixDate($date)
433   {
434     return getdate(mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
435   }
436
437   /**
438    * Gets the year.
439    * "yy" will return the last two digits of year.
440    * "y", "yyy" and "yyyy" will return the full integer year.
441    *
442    * @param array  $date    getdate format.
443    * @param string $pattern a pattern.
444    * @return string year
445    */
446   protected function getYear($date, $pattern = 'yyyy')
447   {
448     $year = $date['year'];
449     switch ($pattern)
450     {
451       case 'yy':
452         return substr($year, 2);
453       case 'y':
454       case 'yyy':
455       case 'yyyy':
456         return $year;
457       default:
458         throw new sfException('The pattern for year is either "y", "yy", "yyy" or "yyyy".');
459     }
460   }
461
462   /**
463    * Gets the month.
464    * "M" will return integer 1 through 12
465    * "MM" will return integer 1 through 12 padded with 0 to two characters width
466    * "MMM" will return the abrreviated month name, e.g. "Jan"
467    * "MMMM" will return the month name, e.g. "January"
468    * "MMMMM" will return the narrow month name, e.g. "J"
469    *
470    * @param array   $date     getdate format.
471    * @param string  $pattern  a pattern.
472    * @return string month name
473    */
474   protected function getMon($date, $pattern = 'M')
475   {
476     $month = $date['mon'];
477
478     switch ($pattern)
479     {
480       case 'M':
481         return $month;
482       case 'MM':
483         return str_pad($month, 2, '0', STR_PAD_LEFT);
484       case 'MMM':
485         return $this->formatInfo->AbbreviatedMonthNames[$month - 1];
486       case 'MMMM':
487         return $this->formatInfo->MonthNames[$month - 1];
488       case 'MMMMM':
489         return $this->formatInfo->NarrowMonthNames[$month - 1];
490       default:
491         throw new sfException('The pattern for month is "M", "MM", "MMM", "MMMM", "MMMMM".');
492     }
493   }
494
495   /**
496    * Gets the day of the week.
497    * "E" will return integer 0 (for Sunday) through 6 (for Saturday).
498    * "EE" will return the narrow day of the week, e.g. "M"
499    * "EEE" will return the abrreviated day of the week, e.g. "Mon"
500    * "EEEE" will return the day of the week, e.g. "Monday"
501    *
502    * @param array   $date     getdate format.
503    * @param string  $pattern  a pattern.
504    * @return string day of the week.
505    */
506   protected function getWday($date, $pattern = 'EEEE')
507   {
508     // if the $date comes from our home-made get date
509     if (!isset($date['wday']))
510     {
511       $date = $this->getUnixDate($date);
512     }
513     $day = $date['wday'];
514
515     switch ($pattern)
516     {
517       case 'E':
518         return $day;
519         break;
520       case 'EE':
521         return $this->formatInfo->NarrowDayNames[$day];
522       case 'EEE':
523         return $this->formatInfo->AbbreviatedDayNames[$day];
524         break;
525       case 'EEEE':
526         return $this->formatInfo->DayNames[$day];
527         break;
528       default:
529         throw new sfException('The pattern for day of the week is "E", "EE", "EEE", or "EEEE".');
530     }
531   }
532
533   /**
534    * Gets the day of the month.
535    * "d" for non-padding, "dd" will always return 2 characters.
536    *
537    * @param array   $date     getdate format.
538    * @param string  $pattern  a pattern.
539    * @return string day of the month
540    */
541   protected function getMday($date, $pattern = 'd')
542   {
543     $day = $date['mday'];
544
545     switch ($pattern)
546     {
547       case 'd':
548         return $day;
549       case 'dd':
550         return str_pad($day, 2, '0', STR_PAD_LEFT);
551       case 'dddd':
552         return $this->getWday($date);
553       default:
554         throw new sfException('The pattern for day of the month is "d", "dd" or "dddd".');
555     }
556   }
557
558   /**
559    * Gets the era. i.e. in gregorian, year > 0 is AD, else BC.
560    *
561    * @todo How to support multiple Eras?, e.g. Japanese.
562    * @param array   $date     getdate format.
563    * @param string  $pattern  a pattern.
564    * @return string era
565    */
566   protected function getEra($date, $pattern = 'G')
567   {
568     if ($pattern != 'G')
569     {
570       throw new sfException('The pattern for era is "G".');
571     }
572
573     return $this->formatInfo->getEra($date['year'] > 0 ? 1 : 0);
574   }
575
576   /**
577    * Gets the hours in 24 hour format, i.e. [0-23].
578    * "H" for non-padding, "HH" will always return 2 characters.
579    *
580    * @param array   $date     getdate format.
581    * @param string  $pattern  a pattern.
582    * @return string hours in 24 hour format.
583    */
584   protected function getHours($date, $pattern = 'H')
585   {
586     $hour = $date['hours'];
587
588     switch ($pattern)
589     {
590       case 'H':
591         return $hour;
592       case 'HH':
593         return str_pad($hour, 2, '0', STR_PAD_LEFT);
594       default:
595         throw new sfException('The pattern for 24 hour format is "H" or "HH".');
596     }
597   }
598
599   /**
600    * Get the AM/PM designator, 12 noon is PM, 12 midnight is AM.
601    *
602    * @param array   $date     getdate format.
603    * @param string  $pattern  a pattern.
604    * @return string AM or PM designator
605    */
606   protected function getAMPM($date, $pattern = 'a')
607   {
608     if ($pattern != 'a')
609     {
610       throw new sfException('The pattern for AM/PM marker is "a".');
611     }
612
613     return $this->formatInfo->AMPMMarkers[intval($date['hours'] / 12)];
614   }
615
616   /**
617    * Gets the hours in 12 hour format.
618    * "h" for non-padding, "hh" will always return 2 characters.
619    *
620    * @param array   $date     getdate format.
621    * @param string  $pattern  a pattern.
622    * @return string hours in 12 hour format.
623    */
624   protected function getHour12($date, $pattern = 'h')
625   {
626     $hour = $date['hours'];
627     $hour = ($hour == 12 | $hour == 0) ? 12 : $hour % 12;
628
629     switch ($pattern)
630     {
631       case 'h':
632         return $hour;
633       case 'hh':
634         return str_pad($hour, 2, '0', STR_PAD_LEFT);
635       default:
636         throw new sfException('The pattern for 24 hour format is "H" or "HH".');
637     }
638   }
639
640   /**
641    * Gets the minutes.
642    * "m" for non-padding, "mm" will always return 2 characters.
643    *
644    * @param array   $date     getdate format.
645    * @param string  $pattern  a pattern.
646    * @return string minutes.
647    */
648   protected function getMinutes($date, $pattern = 'm')
649   {
650     $minutes = $date['minutes'];
651
652     switch ($pattern)
653     {
654       case 'm':
655         return $minutes;
656       case 'mm':
657         return str_pad($minutes, 2, '0', STR_PAD_LEFT);
658       default:
659         throw new sfException('The pattern for minutes is "m" or "mm".');
660     }
661   }
662
663   /**
664    * Gets the seconds.
665    * "s" for non-padding, "ss" will always return 2 characters.
666    *
667    * @param array   $date     getdate format.
668    * @param string  $pattern  a pattern.
669    * @return string seconds
670    */
671   protected function getSeconds($date, $pattern = 's')
672   {
673     $seconds = $date['seconds'];
674
675     switch ($pattern)
676     {
677       case 's':
678         return $seconds;
679       case 'ss':
680         return str_pad($seconds, 2, '0', STR_PAD_LEFT);
681       default:
682         throw new sfException('The pattern for seconds is "s" or "ss".');
683     }
684   }
685
686   /**
687    * Gets the timezone from the server machine.
688    *
689    * @todo How to get the timezone for a different region?
690    * @param array   $date     getdate format.
691    * @param string  $pattern  a pattern.
692    * @return string time zone
693    */
694   protected function getTimeZone($date, $pattern = 'z')
695   {
696     //mapping to PHP pattern symbols
697     switch ($pattern)
698     {
699       case 'z':
700         $pattern = 'T';
701         break;
702       case 'Z':
703         $pattern = 'O';
704       default:
705         throw new sfException('The pattern for time zone is "z" or "Z".');
706     }
707
708     return @date($pattern, @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
709   }
710
711   /**
712    * Gets the day in the year, e.g. [1-366]
713    *
714    * @param array   $date     getdate format.
715    * @param string  $pattern  a pattern.
716    * @return int hours in AM/PM format.
717    */
718   protected function getYday($date, $pattern = 'D')
719   {
720     if ($pattern != 'D')
721     {
722       throw new sfException('The pattern for day in year is "D".');
723     }
724
725     return $date['yday'];
726   }
727
728   /**
729    * Gets day in the month.
730    *
731    * @param array   $date     getdate format.
732    * @param string  $pattern  a pattern.
733    * @return int day in month
734    */
735   protected function getDayInMonth($date, $pattern = 'FF')
736   {
737     switch ($pattern)
738     {
739       case 'F':
740         return @date('j', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
741         break;
742       case 'FF':
743         return @date('d', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
744         break;
745       default:
746         throw new sfException('The pattern for day in month is "F" or "FF".');
747     }
748   }
749
750   /**
751    * Gets the week in the year.
752    *
753    * @param array   $date     getdate format.
754    * @param string  $pattern  a pattern.
755    * @return int week in year
756    */
757   protected function getWeekInYear($date, $pattern = 'w')
758   {
759     if ($pattern != 'w')
760     {
761       throw new sfException('The pattern for week in year is "w".');
762     }
763
764     return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
765   }
766
767   /**
768    * Gets week in the month.
769    *
770    * @param array   $date     getdate format.
771    * @param string  $pattern  a pattern
772    * @return int week in month
773    */
774   protected function getWeekInMonth($date, $pattern = 'W')
775   {
776     if ($pattern != 'W')
777     {
778       throw new sfException('The pattern for week in month is "W".');
779     }
780
781     return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])) - date('W', mktime(0, 0, 0, $date['mon'], 1, $date['year']));
782   }
783
784   /**
785    * Gets the hours [1-24].
786    *
787    * @param array   $date     getdate format.
788    * @param string  $pattern  a pattern.
789    * @return int hours [1-24]
790    */
791   protected function getHourInDay($date, $pattern = 'k')
792   {
793     if ($pattern != 'k')
794     {
795       throw new sfException('The pattern for hour in day is "k".');
796     }
797
798     return $date['hours'] + 1;
799   }
800
801   /**
802    * Gets the hours in AM/PM format, e.g [1-12]
803    *
804    * @param array   $date     getdate format.
805    * @param string  $pattern  a pattern.
806    * @return int hours in AM/PM format.
807    */
808   protected function getHourInAMPM($date, $pattern = 'K')
809   {
810     if ($pattern != 'K')
811     {
812       throw new sfException('The pattern for hour in AM/PM is "K".');
813     }
814
815     return ($date['hours'] + 1) % 12;
816   }
817 }
818
Note: See TracBrowser for help on using the browser.