CDateFormatter.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. <?php
  2. /**
  3. * CDateFormatter class file.
  4. *
  5. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  6. * @author Qiang Xue <qiang.xue@gmail.com>
  7. * @link http://www.yiiframework.com/
  8. * @copyright 2008-2013 Yii Software LLC
  9. * @license http://www.yiiframework.com/license/
  10. */
  11. /**
  12. * CDateFormatter provides date/time localization functionalities.
  13. *
  14. * CDateFormatter allows you to format dates and times in a locale-sensitive manner.
  15. * Patterns are interpreted in the locale that the CDateFormatter instance
  16. * is associated with. For example, month names and weekday names may vary
  17. * under different locales, which yields different formatting results.
  18. * The patterns that CDateFormatter recognizes are as defined in
  19. * {@link http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns CLDR}.
  20. *
  21. * CDateFormatter supports predefined patterns as well as customized ones:
  22. * <ul>
  23. * <li>The method {@link formatDateTime()} formats date or time or both using
  24. * predefined patterns which include 'full', 'long', 'medium' (default) and 'short'.</li>
  25. * <li>The method {@link format()} formats datetime using the specified pattern.
  26. * See {@link http://www.unicode.org/reports/tr35/#Date_Format_Patterns} for
  27. * details about the recognized pattern characters.</li>
  28. * </ul>
  29. *
  30. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  31. * @author Qiang Xue <qiang.xue@gmail.com>
  32. * @package system.i18n
  33. * @since 1.0
  34. */
  35. class CDateFormatter extends CComponent
  36. {
  37. /**
  38. * @var array pattern characters mapping to the corresponding translator methods
  39. */
  40. private static $_formatters=array(
  41. 'G'=>'formatEra',
  42. 'y'=>'formatYear',
  43. 'M'=>'formatMonth',
  44. 'L'=>'formatMonth',
  45. 'd'=>'formatDay',
  46. 'h'=>'formatHour12',
  47. 'H'=>'formatHour24',
  48. 'm'=>'formatMinutes',
  49. 's'=>'formatSeconds',
  50. 'E'=>'formatDayInWeek',
  51. 'c'=>'formatDayInWeek',
  52. 'e'=>'formatDayInWeek',
  53. 'D'=>'formatDayInYear',
  54. 'F'=>'formatDayInMonth',
  55. 'w'=>'formatWeekInYear',
  56. 'W'=>'formatWeekInMonth',
  57. 'a'=>'formatPeriod',
  58. 'k'=>'formatHourInDay',
  59. 'K'=>'formatHourInPeriod',
  60. 'z'=>'formatTimeZone',
  61. 'Z'=>'formatTimeZone',
  62. 'v'=>'formatTimeZone',
  63. );
  64. private $_locale;
  65. /**
  66. * Constructor.
  67. * @param mixed $locale locale ID (string) or CLocale instance
  68. */
  69. public function __construct($locale)
  70. {
  71. if(is_string($locale))
  72. $this->_locale=CLocale::getInstance($locale);
  73. else
  74. $this->_locale=$locale;
  75. }
  76. /**
  77. * Formats a date according to a customized pattern.
  78. * @param string $pattern the pattern (See {@link http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns})
  79. * @param mixed $time UNIX timestamp or a string in strtotime format
  80. * @return string formatted date time. Null if $time is null. (the null value check is available since Yii 1.1.11)
  81. */
  82. public function format($pattern,$time)
  83. {
  84. if($time===null)
  85. return null;
  86. if(is_string($time))
  87. {
  88. if(ctype_digit($time) || ($time{0}=='-' && ctype_digit(substr($time, 1))))
  89. $time=(int)$time;
  90. else
  91. $time=strtotime($time);
  92. }
  93. $date=CTimestamp::getDate($time,false,false);
  94. $tokens=$this->parseFormat($pattern);
  95. foreach($tokens as &$token)
  96. {
  97. if(is_array($token)) // a callback: method name, sub-pattern
  98. $token=$this->{$token[0]}($token[1],$date);
  99. }
  100. return implode('',$tokens);
  101. }
  102. /**
  103. * Formats a date according to a predefined pattern.
  104. * The predefined pattern is determined based on the date pattern width and time pattern width.
  105. * @param mixed $timestamp UNIX timestamp or a string in strtotime format
  106. * @param string $dateWidth width of the date pattern. It can be 'full', 'long', 'medium' and 'short'.
  107. * If null, it means the date portion will NOT appear in the formatting result
  108. * @param string $timeWidth width of the time pattern. It can be 'full', 'long', 'medium' and 'short'.
  109. * If null, it means the time portion will NOT appear in the formatting result
  110. * @return string formatted date time.
  111. */
  112. public function formatDateTime($timestamp,$dateWidth='medium',$timeWidth='medium')
  113. {
  114. if(!empty($dateWidth))
  115. $date=$this->format($this->_locale->getDateFormat($dateWidth),$timestamp);
  116. if(!empty($timeWidth))
  117. $time=$this->format($this->_locale->getTimeFormat($timeWidth),$timestamp);
  118. if(isset($date) && isset($time))
  119. {
  120. $dateTimePattern=$this->_locale->getDateTimeFormat();
  121. return strtr($dateTimePattern,array('{0}'=>$time,'{1}'=>$date));
  122. }
  123. elseif(isset($date))
  124. return $date;
  125. elseif(isset($time))
  126. return $time;
  127. }
  128. /**
  129. * Parses the datetime format pattern.
  130. * @param string $pattern the pattern to be parsed
  131. * @return array tokenized parsing result
  132. */
  133. protected function parseFormat($pattern)
  134. {
  135. static $formats=array(); // cache
  136. if(isset($formats[$pattern]))
  137. return $formats[$pattern];
  138. $tokens=array();
  139. $n=strlen($pattern);
  140. $isLiteral=false;
  141. $literal='';
  142. for($i=0;$i<$n;++$i)
  143. {
  144. $c=$pattern[$i];
  145. if($c==="'")
  146. {
  147. if($i<$n-1 && $pattern[$i+1]==="'")
  148. {
  149. $tokens[]="'";
  150. $i++;
  151. }
  152. elseif($isLiteral)
  153. {
  154. $tokens[]=$literal;
  155. $literal='';
  156. $isLiteral=false;
  157. }
  158. else
  159. {
  160. $isLiteral=true;
  161. $literal='';
  162. }
  163. }
  164. elseif($isLiteral)
  165. $literal.=$c;
  166. else
  167. {
  168. for($j=$i+1;$j<$n;++$j)
  169. {
  170. if($pattern[$j]!==$c)
  171. break;
  172. }
  173. $p=str_repeat($c,$j-$i);
  174. if(isset(self::$_formatters[$c]))
  175. $tokens[]=array(self::$_formatters[$c],$p);
  176. else
  177. $tokens[]=$p;
  178. $i=$j-1;
  179. }
  180. }
  181. if($literal!=='')
  182. $tokens[]=$literal;
  183. return $formats[$pattern]=$tokens;
  184. }
  185. /**
  186. * Get the year.
  187. * "yy" will return the last two digits of year.
  188. * "y...y" will pad the year with 0 in the front, e.g. "yyyyy" will generate "02008" for year 2008.
  189. * @param string $pattern a pattern.
  190. * @param array $date result of {@link CTimestamp::getdate}.
  191. * @return string formatted year
  192. */
  193. protected function formatYear($pattern,$date)
  194. {
  195. $year=$date['year'];
  196. if($pattern==='yy')
  197. return str_pad($year%100,2,'0',STR_PAD_LEFT);
  198. else
  199. return str_pad($year,strlen($pattern),'0',STR_PAD_LEFT);
  200. }
  201. /**
  202. * Get the month.
  203. * "M" will return integer 1 through 12;
  204. * "MM" will return two digits month number with necessary zero padding, e.g. 05;
  205. * "MMM" will return the abrreviated month name, e.g. "Jan";
  206. * "MMMM" will return the full month name, e.g. "January";
  207. * "MMMMM" will return the narrow month name, e.g. "J";
  208. * @param string $pattern a pattern.
  209. * @param array $date result of {@link CTimestamp::getdate}.
  210. * @throws CException if "month" pattern is unknown
  211. * @return string month name
  212. */
  213. protected function formatMonth($pattern,$date)
  214. {
  215. $month=$date['mon'];
  216. switch($pattern)
  217. {
  218. case 'M':
  219. return $month;
  220. case 'MM':
  221. return str_pad($month,2,'0',STR_PAD_LEFT);
  222. case 'MMM':
  223. return $this->_locale->getMonthName($month,'abbreviated');
  224. case 'MMMM':
  225. return $this->_locale->getMonthName($month,'wide');
  226. case 'MMMMM':
  227. return $this->_locale->getMonthName($month,'narrow');
  228. case 'L':
  229. return $month;
  230. case 'LL':
  231. return str_pad($month,2,'0',STR_PAD_LEFT);
  232. case 'LLL':
  233. return $this->_locale->getMonthName($month,'abbreviated', true);
  234. case 'LLLL':
  235. return $this->_locale->getMonthName($month,'wide', true);
  236. case 'LLLLL':
  237. return $this->_locale->getMonthName($month,'narrow', true);
  238. default:
  239. throw new CException(Yii::t('yii','The pattern for month must be "M", "MM", "MMM", "MMMM", "L", "LL", "LLL" or "LLLL".'));
  240. }
  241. }
  242. /**
  243. * Get the day of the month.
  244. * "d" for non-padding, "dd" will always return 2 digits day numbers, e.g. 05.
  245. * @param string $pattern a pattern.
  246. * @param array $date result of {@link CTimestamp::getdate}.
  247. * @throws CException if "day" pattern is unknown
  248. * @return string day of the month
  249. */
  250. protected function formatDay($pattern,$date)
  251. {
  252. $day=$date['mday'];
  253. if($pattern==='d')
  254. return $day;
  255. elseif($pattern==='dd')
  256. return str_pad($day,2,'0',STR_PAD_LEFT);
  257. else
  258. throw new CException(Yii::t('yii','The pattern for day of the month must be "d" or "dd".'));
  259. }
  260. /**
  261. * Get the day in the year, e.g. [1-366]
  262. * @param string $pattern a pattern.
  263. * @param array $date result of {@link CTimestamp::getdate}.
  264. * @throws CException is "dayInYear" pattern is unknown
  265. * @return integer hours in AM/PM format.
  266. */
  267. protected function formatDayInYear($pattern,$date)
  268. {
  269. $day=$date['yday'];
  270. if(($n=strlen($pattern))<=3)
  271. return str_pad($day,$n,'0',STR_PAD_LEFT);
  272. else
  273. throw new CException(Yii::t('yii','The pattern for day in year must be "D", "DD" or "DDD".'));
  274. }
  275. /**
  276. * Get day of week in the month, e.g. 2nd Wed in July.
  277. * @param string $pattern a pattern.
  278. * @param array $date result of {@link CTimestamp::getdate}.
  279. * @throws CException if "dayInMonth" pattern is unknown
  280. * @return integer day in month
  281. * @see http://www.unicode.org/reports/tr35/#Date_Format_Patterns
  282. */
  283. protected function formatDayInMonth($pattern,$date)
  284. {
  285. if($pattern==='F')
  286. return (int)(($date['mday']+6)/7);
  287. else
  288. throw new CException(Yii::t('yii','The pattern for day in month must be "F".'));
  289. }
  290. /**
  291. * Get the day of the week.
  292. * "E", "EE", "EEE" will return abbreviated week day name, e.g. "Tues";
  293. * "EEEE" will return full week day name;
  294. * "EEEEE" will return the narrow week day name, e.g. "T";
  295. * @param string $pattern a pattern.
  296. * @param array $date result of {@link CTimestamp::getdate}.
  297. * @throws CException if "dayInWeek" pattern is unknown
  298. * @return string day of the week.
  299. * @see http://www.unicode.org/reports/tr35/#Date_Format_Patterns
  300. */
  301. protected function formatDayInWeek($pattern,$date)
  302. {
  303. $day=$date['wday'];
  304. switch($pattern)
  305. {
  306. case 'E':
  307. case 'EE':
  308. case 'EEE':
  309. case 'eee':
  310. return $this->_locale->getWeekDayName($day,'abbreviated');
  311. case 'EEEE':
  312. case 'eeee':
  313. return $this->_locale->getWeekDayName($day,'wide');
  314. case 'EEEEE':
  315. case 'eeeee':
  316. return $this->_locale->getWeekDayName($day,'narrow');
  317. case 'e':
  318. case 'ee':
  319. case 'c':
  320. return $day ? $day : 7;
  321. case 'ccc':
  322. return $this->_locale->getWeekDayName($day,'abbreviated',true);
  323. case 'cccc':
  324. return $this->_locale->getWeekDayName($day,'wide',true);
  325. case 'ccccc':
  326. return $this->_locale->getWeekDayName($day,'narrow',true);
  327. default:
  328. throw new CException(Yii::t('yii','The pattern for day of the week must be "E", "EE", "EEE", "EEEE", "EEEEE", "e", "ee", "eee", "eeee", "eeeee", "c", "cccc" or "ccccc".'));
  329. }
  330. }
  331. /**
  332. * Get the AM/PM designator, 12 noon is PM, 12 midnight is AM.
  333. * @param string $pattern a pattern.
  334. * @param array $date result of {@link CTimestamp::getdate}.
  335. * @throws CException if "period" pattern is unknown
  336. * @return string AM or PM designator
  337. */
  338. protected function formatPeriod($pattern,$date)
  339. {
  340. if($pattern==='a')
  341. {
  342. if(intval($date['hours']/12))
  343. return $this->_locale->getPMName();
  344. else
  345. return $this->_locale->getAMName();
  346. }
  347. else
  348. throw new CException(Yii::t('yii','The pattern for AM/PM marker must be "a".'));
  349. }
  350. /**
  351. * Get the hours in 24 hour format, i.e. [0-23].
  352. * "H" for non-padding, "HH" will always return 2 characters.
  353. * @param string $pattern a pattern.
  354. * @param array $date result of {@link CTimestamp::getdate}.
  355. * @throws CException if "hour24" pattern is unknown
  356. * @return string hours in 24 hour format.
  357. */
  358. protected function formatHour24($pattern,$date)
  359. {
  360. $hour=$date['hours'];
  361. if($pattern==='H')
  362. return $hour;
  363. elseif($pattern==='HH')
  364. return str_pad($hour,2,'0',STR_PAD_LEFT);
  365. else
  366. throw new CException(Yii::t('yii','The pattern for 24 hour format must be "H" or "HH".'));
  367. }
  368. /**
  369. * Get the hours in 12 hour format, i.e., [1-12]
  370. * "h" for non-padding, "hh" will always return 2 characters.
  371. * @param string $pattern a pattern.
  372. * @param array $date result of {@link CTimestamp::getdate}.
  373. * @throws CException if "hour12" pattern is unknown
  374. * @return string hours in 12 hour format.
  375. */
  376. protected function formatHour12($pattern,$date)
  377. {
  378. $hour=$date['hours'];
  379. $hour=($hour==12|$hour==0)?12:($hour)%12;
  380. if($pattern==='h')
  381. return $hour;
  382. elseif($pattern==='hh')
  383. return str_pad($hour,2,'0',STR_PAD_LEFT);
  384. else
  385. throw new CException(Yii::t('yii','The pattern for 12 hour format must be "h" or "hh".'));
  386. }
  387. /**
  388. * Get the hours [1-24].
  389. * 'k' for non-padding, and 'kk' with 2 characters padding.
  390. * @param string $pattern a pattern.
  391. * @param array $date result of {@link CTimestamp::getdate}.
  392. * @throws CException if "hourInDay" pattern is unknown
  393. * @return integer hours [1-24]
  394. */
  395. protected function formatHourInDay($pattern,$date)
  396. {
  397. $hour=$date['hours']==0?24:$date['hours'];
  398. if($pattern==='k')
  399. return $hour;
  400. elseif($pattern==='kk')
  401. return str_pad($hour,2,'0',STR_PAD_LEFT);
  402. else
  403. throw new CException(Yii::t('yii','The pattern for hour in day must be "k" or "kk".'));
  404. }
  405. /**
  406. * Get the hours in AM/PM format, e.g [0-11]
  407. * "K" for non-padding, "KK" will always return 2 characters.
  408. * @param string $pattern a pattern.
  409. * @param array $date result of {@link CTimestamp::getdate}.
  410. * @throws CException if "hourInPeriod" pattern is unknown
  411. * @return integer hours in AM/PM format.
  412. */
  413. protected function formatHourInPeriod($pattern,$date)
  414. {
  415. $hour=$date['hours']%12;
  416. if($pattern==='K')
  417. return $hour;
  418. elseif($pattern==='KK')
  419. return str_pad($hour,2,'0',STR_PAD_LEFT);
  420. else
  421. throw new CException(Yii::t('yii','The pattern for hour in AM/PM must be "K" or "KK".'));
  422. }
  423. /**
  424. * Get the minutes.
  425. * "m" for non-padding, "mm" will always return 2 characters.
  426. * @param string $pattern a pattern.
  427. * @param array $date result of {@link CTimestamp::getdate}.
  428. * @throws CException if "minutes" pattern is unknown
  429. * @return string minutes.
  430. */
  431. protected function formatMinutes($pattern,$date)
  432. {
  433. $minutes=$date['minutes'];
  434. if($pattern==='m')
  435. return $minutes;
  436. elseif($pattern==='mm')
  437. return str_pad($minutes,2,'0',STR_PAD_LEFT);
  438. else
  439. throw new CException(Yii::t('yii','The pattern for minutes must be "m" or "mm".'));
  440. }
  441. /**
  442. * Get the seconds.
  443. * "s" for non-padding, "ss" will always return 2 characters.
  444. * @param string $pattern a pattern.
  445. * @param array $date result of {@link CTimestamp::getdate}.
  446. * @throws CException if "seconds" pattern is unknown
  447. * @return string seconds
  448. */
  449. protected function formatSeconds($pattern,$date)
  450. {
  451. $seconds=$date['seconds'];
  452. if($pattern==='s')
  453. return $seconds;
  454. elseif($pattern==='ss')
  455. return str_pad($seconds,2,'0',STR_PAD_LEFT);
  456. else
  457. throw new CException(Yii::t('yii','The pattern for seconds must be "s" or "ss".'));
  458. }
  459. /**
  460. * Get the week in the year.
  461. * @param string $pattern a pattern.
  462. * @param array $date result of {@link CTimestamp::getdate}.
  463. * @throws CException if "weekInYear" pattern is unknown
  464. * @return integer week in year
  465. */
  466. protected function formatWeekInYear($pattern,$date)
  467. {
  468. if($pattern==='w')
  469. return @date('W',@mktime(0,0,0,$date['mon'],$date['mday'],$date['year']));
  470. else
  471. throw new CException(Yii::t('yii','The pattern for week in year must be "w".'));
  472. }
  473. /**
  474. * Get week in the month.
  475. * @param array $pattern result of {@link CTimestamp::getdate}.
  476. * @param string $date a pattern.
  477. * @throws CException if "weekInMonth" pattern is unknown
  478. * @return integer week in month
  479. */
  480. protected function formatWeekInMonth($pattern,$date)
  481. {
  482. if($pattern==='W')
  483. {
  484. $weekDay=date('N',mktime(0,0,0,$date['mon'],1,$date['year']));
  485. return floor(($weekDay+$date['mday']-2)/7)+1;
  486. }
  487. else
  488. throw new CException(Yii::t('yii','The pattern for week in month must be "W".'));
  489. }
  490. /**
  491. * Get the timezone of the server machine.
  492. * @param string $pattern a pattern.
  493. * @param array $date result of {@link CTimestamp::getdate}.
  494. * @throws CException if "timeZone" pattern is unknown
  495. * @return string time zone
  496. * @todo How to get the timezone for a different region?
  497. */
  498. protected function formatTimeZone($pattern,$date)
  499. {
  500. if($pattern[0]==='z' || $pattern[0]==='v')
  501. return @date('T', @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
  502. elseif($pattern[0]==='Z')
  503. return @date('P', @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
  504. else
  505. throw new CException(Yii::t('yii','The pattern for time zone must be "z" or "v".'));
  506. }
  507. /**
  508. * Get the era. i.e. in gregorian, year > 0 is AD, else BC.
  509. * @param string $pattern a pattern.
  510. * @param array $date result of {@link CTimestamp::getdate}.
  511. * @throws CException if "era" pattern is unknown
  512. * @return string era
  513. * @todo How to support multiple Eras?, e.g. Japanese.
  514. */
  515. protected function formatEra($pattern,$date)
  516. {
  517. $era=$date['year']>0 ? 1 : 0;
  518. switch($pattern)
  519. {
  520. case 'G':
  521. case 'GG':
  522. case 'GGG':
  523. return $this->_locale->getEraName($era,'abbreviated');
  524. case 'GGGG':
  525. return $this->_locale->getEraName($era,'wide');
  526. case 'GGGGG':
  527. return $this->_locale->getEraName($era,'narrow');
  528. default:
  529. throw new CException(Yii::t('yii','The pattern for era must be "G", "GG", "GGG", "GGGG" or "GGGGG".'));
  530. }
  531. }
  532. }