Development

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

You must first sign up to be able to contribute.

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

Revision 6806, 20.0 kB (checked in by fabien, 7 years ago)

fixed phpdoc package/subpackage entries

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