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

Debug toolbar