Development

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

You must first sign up to be able to contribute.

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

Revision 17749, 20.5 kB (checked in by fabien, 6 years ago)

[1.2, 1.3] fixed some PHPDoc (closes #6371 - patch from gimler)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Rev Date
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 (is_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 (is_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       foreach ($tokens as $token)
143       {
144         if ($matchName = $this->getFunctionName($token))
145         {
146           $pregPattern .= '(\d+)';
147           $matchNames[] = $matchName;
148         }
149         else
150         {
151           $pregPattern .= '[^\d]+';
152         }
153       }
154       preg_match('@'.$pregPattern.'@', $time, $matches);
155
156       array_shift($matches);
157
158       if (count($matchNames) == count($matches))
159       {
160         $date = array_combine($matchNames, $matches);
161         // guess the date if input with two digits
162         if (strlen($date['year']) == 2)
163         {
164           $date['year'] = date('Y', mktime(0, 0, 0, 1, 1, $date['year']));
165         }
166         $date = array_map('intval', $date);
167       }
168     }
169
170     // the last attempt has failed we fall back on the default method
171     if (!isset($date))
172     {
173       if ($isString)
174       {
175         $numericalTime = @strtotime($time);
176         if ($numericalTime === false)
177         {
178           throw new sfException(sprintf('Impossible to parse date "%s" with format "%s".', $time, $pattern));
179         }
180       }
181       else
182       {
183         $numericalTime = $time;
184       }
185       $date = @getdate($numericalTime);
186     }
187
188     // we set default values for the time
189     foreach (array('hours', 'minutes', 'seconds') as $timeDiv)
190     {
191       if (!isset($date[$timeDiv]))
192       {
193         $date[$timeDiv] = 0;
194       }
195     }
196
197     return $date;
198   }
199
200   /**
201    * Formats a date according to the pattern.
202    *
203    * @param mixed   $time           the time as integer or string in strtotime format.
204    * @param string  $pattern        the pattern
205    * @param string  $inputPattern   the input pattern
206    * @param string  $charset        the charset
207    * @return string formatted date time.
208    */
209   public function format($time, $pattern = 'F', $inputPattern = null, $charset = 'UTF-8')
210   {
211     $date = $this->getDate($time, $inputPattern);
212
213     if (is_null($pattern))
214     {
215       $pattern = 'F';
216     }
217
218     $pattern = $this->getPattern($pattern);
219     $tokens = $this->getTokens($pattern);
220
221     for ($i = 0, $max = count($tokens); $i < $max; $i++)
222     {
223       $pattern = $tokens[$i];
224       if ($pattern{0} == "'" && $pattern{strlen($pattern) - 1} == "'")
225       {
226         $tokens[$i] = str_replace('``````', '\'', preg_replace('/(^\')|(\'$)/', '', $pattern));
227       }
228       else if ($pattern == '``````')
229       {
230         $tokens[$i] = '\'';
231       }
232       else
233       {
234         $function = ucfirst($this->getFunctionName($pattern));
235         if ($function != null)
236         {
237           $fName = 'get'.$function;
238           if (in_array($fName, $this->methods))
239           {
240             $tokens[$i] = $this->$fName($date, $pattern);
241           }
242           else
243           {
244             throw new sfException(sprintf('Function %s not found.', $function));
245           }
246         }
247       }
248     }
249
250     return sfToolkit::I18N_toEncoding(implode('', $tokens), $charset);
251   }
252
253   /**
254    * For a particular token, get the corresponding function to call.
255    *
256    * @param string $token token
257    * @return mixed the function if good token, null otherwise.
258    */
259   protected function getFunctionName($token)
260   {
261     if (isset($this->tokens[$token{0}]))
262     {
263       return $this->tokens[$token{0}];
264     }
265   }
266
267   /**
268    * Gets the pattern from DateTimeFormatInfo or some predefined patterns.
269    * If the $pattern parameter is an array of 2 element, it will assume
270    * that the first element is the date, and second the time
271    * and try to find an appropriate pattern and apply
272    * DateTimeFormatInfo::formatDateTime
273    * See the tutorial documentation for futher details on the patterns.
274    *
275    * @param mixed $pattern a pattern.
276    * @return string a pattern.
277    * @see DateTimeFormatInfo::formatDateTime()
278    */
279   public function getPattern($pattern)
280   {
281     if (is_array($pattern) && count($pattern) == 2)
282     {
283       return $this->formatInfo->formatDateTime($this->getPattern($pattern[0]), $this->getPattern($pattern[1]));
284     }
285
286     switch ($pattern)
287     {
288       case 'd':
289         return $this->formatInfo->ShortDatePattern;
290         break;
291       case 'D':
292         return $this->formatInfo->LongDatePattern;
293         break;
294       case 'p':
295         return $this->formatInfo->MediumDatePattern;
296         break;
297       case 'P':
298         return $this->formatInfo->FullDatePattern;
299         break;       
300       case 't':
301         return $this->formatInfo->ShortTimePattern;
302         break;
303       case 'T':
304         return $this->formatInfo->LongTimePattern;
305         break;
306       case 'q':
307         return $this->formatInfo->MediumTimePattern;
308         break;
309       case 'Q':
310         return $this->formatInfo->FullTimePattern;
311         break;
312       case 'f':
313         return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->ShortTimePattern);
314         break;
315       case 'F':
316         return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->LongTimePattern);
317         break;
318       case 'g':
319         return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->ShortTimePattern);
320         break;
321       case 'G':
322         return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->LongTimePattern);
323         break;
324       case 'i':
325         return 'yyyy-MM-dd';
326         break;
327       case 'I':
328         return 'yyyy-MM-dd HH:mm:ss';
329         break;
330       case 'M':
331       case 'm':
332         return 'MMMM dd';
333         break;
334       case 'R':
335       case 'r':
336         return 'EEE, dd MMM yyyy HH:mm:ss';
337         break;
338       case 's':
339         return 'yyyy-MM-ddTHH:mm:ss';
340         break;
341       case 'u':
342         return 'yyyy-MM-dd HH:mm:ss z';
343         break;
344       case 'U':
345         return 'EEEE dd MMMM yyyy HH:mm:ss';
346         break;
347       case 'Y':
348       case 'y':
349         return 'yyyy MMMM';
350         break;
351       default :
352         return $pattern;
353     }
354   }
355
356   /**
357    * Returns an easy to parse input pattern
358    * yy is replaced by yyyy and h by H
359    *
360    * @param string $pattern pattern.
361    * @return string input pattern
362    */
363   public function getInputPattern($pattern)
364   {
365     $pattern = $this->getPattern($pattern);
366     
367     $pattern = strtr($pattern, array('yyyy' => 'Y', 'h'=>'H', 'z'=>'', 'a'=>''));
368     $pattern = strtr($pattern, array('yy'=>'yyyy', 'Y'=>'yyyy'));
369     
370     return trim($pattern);
371   }
372
373   /**
374    * Tokenizes the pattern. The tokens are delimited by group of
375    * similar characters, e.g. 'aabb' will form 2 tokens of 'aa' and 'bb'.
376    * Any substrings, starting and ending with a single quote (')
377    * will be treated as a single token.
378    *
379    * @param string $pattern pattern.
380    * @return array string tokens in an array.
381    */
382   protected function getTokens($pattern)
383   {
384     $char = null;
385     $tokens = array();
386     $token = null;
387
388     $text = false;
389
390     for ($i = 0, $max = strlen($pattern); $i < $max; $i++)
391     {
392       if ($char == null || $pattern{$i} == $char || $text)
393       {
394         $token .= $pattern{$i};
395       }
396       else
397       {
398         $tokens[] = str_replace("''", "'", $token);
399         $token = $pattern{$i};
400       }
401
402       if ($pattern{$i} == "'" && $text == false)
403       {
404         $text = true;
405       }
406       else if ($text && $pattern{$i} == "'" && $char == "'")
407       {
408         $text = true;
409       }
410       else if ($text && $char != "'" && $pattern{$i} == "'")
411       {
412         $text = false;
413       }
414
415       $char = $pattern{$i};
416
417     }
418     $tokens[] = $token;
419
420     return $tokens;
421   }
422  
423   // makes a unix date from our incomplete $date array
424   protected function getUnixDate($date)
425   {
426     return getdate(mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
427   }
428
429   /**
430    * Gets the year.
431    * "yy" will return the last two digits of year.
432    * "yyyy" will return the full integer year.
433    *
434    * @param array  $date    getdate format.
435    * @param string $pattern a pattern.
436    * @return string year
437    */
438   protected function getYear($date, $pattern = 'yyyy')
439   {
440     $year = $date['year'];
441     switch ($pattern)
442     {
443       case 'yy':
444         return substr($year, 2);
445       case 'yyyy':
446         return $year;
447       default:
448         throw new sfException('The pattern for year is either "yy" or "yyyy".');
449     }
450   }
451
452   /**
453    * Gets the month.
454    * "M" will return integer 1 through 12
455    * "MM" will return the narrow month name, e.g. "J"
456    * "MMM" will return the abrreviated month name, e.g. "Jan"
457    * "MMMM" will return the month name, e.g. "January"
458    *
459    * @param array   $date     getdate format.
460    * @param string  $pattern  a pattern.
461    * @return string month name
462    */
463   protected function getMon($date, $pattern = 'M')
464   {
465     $month = $date['mon'];
466
467     switch ($pattern)
468     {
469       case 'M':
470         return $month;
471       case 'MM':
472         return str_pad($month, 2, '0', STR_PAD_LEFT);
473       case 'MMM':
474         return $this->formatInfo->AbbreviatedMonthNames[$month - 1];
475         break;
476       case 'MMMM':
477         return $this->formatInfo->MonthNames[$month - 1];
478       default:
479         throw new sfException('The pattern for month is "M", "MM", "MMM", or "MMMM".');
480     }
481   }
482
483   /**
484    * Gets the day of the week.
485    * "E" will return integer 0 (for Sunday) through 6 (for Saturday).
486    * "EE" will return the narrow day of the week, e.g. "M"
487    * "EEE" will return the abrreviated day of the week, e.g. "Mon"
488    * "EEEE" will return the day of the week, e.g. "Monday"
489    *
490    * @param array   $date     getdate format.
491    * @param string  $pattern  a pattern.
492    * @return string day of the week.
493    */
494   protected function getWday($date, $pattern = 'EEEE')
495   {
496     // if the $date comes from our home-made get date
497     if (!isset($date['wday']))
498     {
499       $date = $this->getUnixDate($date);
500     }
501     $day = $date['wday'];
502
503     switch ($pattern)
504     {
505       case 'E':
506         return $day;
507         break;
508       case 'EE':
509         return $this->formatInfo->NarrowDayNames[$day];
510       case 'EEE':
511         return $this->formatInfo->AbbreviatedDayNames[$day];
512         break;
513       case 'EEEE':
514         return $this->formatInfo->DayNames[$day];
515         break;
516       default:
517         throw new sfException('The pattern for day of the week is "E", "EE", "EEE", or "EEEE".');
518     }
519   }
520
521   /**
522    * Gets the day of the month.
523    * "d" for non-padding, "dd" will always return 2 characters.
524    *
525    * @param array   $date     getdate format.
526    * @param string  $pattern  a pattern.
527    * @return string day of the month
528    */
529   protected function getMday($date, $pattern = 'd')
530   {
531     $day = $date['mday'];
532
533     switch ($pattern)
534     {
535       case 'd':
536         return $day;
537       case 'dd':
538         return str_pad($day, 2, '0', STR_PAD_LEFT);
539       case 'dddd':
540         return $this->getWday($date);
541       default:
542         throw new sfException('The pattern for day of the month is "d", "dd" or "dddd".');
543     }
544   }
545
546   /**
547    * Gets the era. i.e. in gregorian, year > 0 is AD, else BC.
548    *
549    * @todo How to support multiple Eras?, e.g. Japanese.
550    * @param array   $date     getdate format.
551    * @param string  $pattern  a pattern.
552    * @return string era
553    */
554   protected function getEra($date, $pattern = 'G')
555   {
556     if ($pattern != 'G')
557     {
558       throw new sfException('The pattern for era is "G".');
559     }
560
561     return $this->formatInfo->getEra($date['year'] > 0 ? 1 : 0);
562   }
563
564   /**
565    * Gets the hours in 24 hour format, i.e. [0-23].
566    * "H" for non-padding, "HH" will always return 2 characters.
567    *
568    * @param array   $date     getdate format.
569    * @param string  $pattern  a pattern.
570    * @return string hours in 24 hour format.
571    */
572   protected function getHours($date, $pattern = 'H')
573   {
574     $hour = $date['hours'];
575
576     switch ($pattern)
577     {
578       case 'H':
579         return $hour;
580       case 'HH':
581         return str_pad($hour, 2, '0', STR_PAD_LEFT);
582       default:
583         throw new sfException('The pattern for 24 hour format is "H" or "HH".');
584     }
585   }
586
587   /**
588    * Get the AM/PM designator, 12 noon is PM, 12 midnight is AM.
589    *
590    * @param array   $date     getdate format.
591    * @param string  $pattern  a pattern.
592    * @return string AM or PM designator
593    */
594   protected function getAMPM($date, $pattern = 'a')
595   {
596     if ($pattern != 'a')
597     {
598       throw new sfException('The pattern for AM/PM marker is "a".');
599     }
600
601     return $this->formatInfo->AMPMMarkers[intval($date['hours'] / 12)];
602   }
603
604   /**
605    * Gets the hours in 12 hour format.
606    * "h" for non-padding, "hh" will always return 2 characters.
607    *
608    * @param array   $date     getdate format.
609    * @param string  $pattern  a pattern.
610    * @return string hours in 12 hour format.
611    */
612   protected function getHour12($date, $pattern = 'h')
613   {
614     $hour = $date['hours'];
615     $hour = ($hour == 12 | $hour == 0) ? 12 : $hour % 12;
616
617     switch ($pattern)
618     {
619       case 'h':
620         return $hour;
621       case 'hh':
622         return str_pad($hour, 2, '0', STR_PAD_LEFT);
623       default:
624         throw new sfException('The pattern for 24 hour format is "H" or "HH".');
625     }
626   }
627
628   /**
629    * Gets the minutes.
630    * "m" for non-padding, "mm" will always return 2 characters.
631    *
632    * @param array   $date     getdate format.
633    * @param string  $pattern  a pattern.
634    * @return string minutes.
635    */
636   protected function getMinutes($date, $pattern = 'm')
637   {
638     $minutes = $date['minutes'];
639
640     switch ($pattern)
641     {
642       case 'm':
643         return $minutes;
644       case 'mm':
645         return str_pad($minutes, 2, '0', STR_PAD_LEFT);
646       default:
647         throw new sfException('The pattern for minutes is "m" or "mm".');
648     }
649   }
650
651   /**
652    * Gets the seconds.
653    * "s" for non-padding, "ss" will always return 2 characters.
654    *
655    * @param array   $date     getdate format.
656    * @param string  $pattern  a pattern.
657    * @return string seconds
658    */
659   protected function getSeconds($date, $pattern = 's')
660   {
661     $seconds = $date['seconds'];
662
663     switch ($pattern)
664     {
665       case 's':
666         return $seconds;
667       case 'ss':
668         return str_pad($seconds, 2, '0', STR_PAD_LEFT);
669       default:
670         throw new sfException('The pattern for seconds is "s" or "ss".');
671     }
672   }
673
674   /**
675    * Gets the timezone from the server machine.
676    *
677    * @todo How to get the timezone for a different region?
678    * @param array   $date     getdate format.
679    * @param string  $pattern  a pattern.
680    * @return string time zone
681    */
682   protected function getTimeZone($date, $pattern = 'z')
683   {
684     if ($pattern != 'z')
685     {
686       throw new sfException('The pattern for time zone is "z".');
687     }
688
689     return @date('T', @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
690   }
691
692   /**
693    * Gets the day in the year, e.g. [1-366]
694    *
695    * @param array   $date     getdate format.
696    * @param string  $pattern  a pattern.
697    * @return int hours in AM/PM format.
698    */
699   protected function getYday($date, $pattern = 'D')
700   {
701     if ($pattern != 'D')
702     {
703       throw new sfException('The pattern for day in year is "D".');
704     }
705
706     return $date['yday'];
707   }
708
709   /**
710    * Gets day in the month.
711    *
712    * @param array   $date     getdate format.
713    * @param string  $pattern  a pattern.
714    * @return int day in month
715    */
716   protected function getDayInMonth($date, $pattern = 'FF')
717   {
718     switch ($pattern)
719     {
720       case 'F':
721         return @date('j', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
722         break;
723       case 'FF':
724         return @date('d', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
725         break;
726       default:
727         throw new sfException('The pattern for day in month is "F" or "FF".');
728     }
729   }
730
731   /**
732    * Gets the week in the year.
733    *
734    * @param array   $date     getdate format.
735    * @param string  $pattern  a pattern.
736    * @return int week in year
737    */
738   protected function getWeekInYear($date, $pattern = 'w')
739   {
740     if ($pattern != 'w')
741     {
742       throw new sfException('The pattern for week in year is "w".');
743     }
744
745     return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
746   }
747
748   /**
749    * Gets week in the month.
750    *
751    * @param array   $date     getdate format.
752    * @param string  $pattern  a pattern
753    * @return int week in month
754    */
755   protected function getWeekInMonth($date, $pattern = 'W')
756   {
757     if ($pattern != 'W')
758     {
759       throw new sfException('The pattern for week in month is "W".');
760     }
761
762     return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])) - date('W', mktime(0, 0, 0, $date['mon'], 1, $date['year']));
763   }
764
765   /**
766    * Gets the hours [1-24].
767    *
768    * @param array   $date     getdate format.
769    * @param string  $pattern  a pattern.
770    * @return int hours [1-24]
771    */
772   protected function getHourInDay($date, $pattern = 'k')
773   {
774     if ($pattern != 'k')
775     {
776       throw new sfException('The pattern for hour in day is "k".');
777     }
778
779     return $date['hours'] + 1;
780   }
781
782   /**
783    * Gets the hours in AM/PM format, e.g [1-12]
784    *
785    * @param array   $date     getdate format.
786    * @param string  $pattern  a pattern.
787    * @return int hours in AM/PM format.
788    */
789   protected function getHourInAMPM($date, $pattern = 'K')
790   {
791     if ($pattern != 'K')
792     {
793       throw new sfException('The pattern for hour in AM/PM is "K".');
794     }
795
796     return ($date['hours'] + 1) % 12;
797   }
798 }
799
Note: See TracBrowser for help on using the browser.