DateFormatTrait.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\I18n;
  16. use Cake\Chronos\Date as ChronosDate;
  17. use Cake\Chronos\MutableDate;
  18. use IntlDateFormatter;
  19. /**
  20. * Trait for date formatting methods shared by both Time & Date.
  21. *
  22. * This trait expects that the implementing class define static::$_toStringFormat.
  23. */
  24. trait DateFormatTrait
  25. {
  26. /**
  27. * The default locale to be used for displaying formatted date strings.
  28. *
  29. * @var string
  30. * @deprecated 3.2.9 Use static::setDefaultLocale() and static::getDefaultLocale() instead.
  31. */
  32. public static $defaultLocale;
  33. /**
  34. * In-memory cache of date formatters
  35. *
  36. * @var IntlDateFormatter[]
  37. */
  38. protected static $_formatters = [];
  39. /**
  40. * The format to use when when converting this object to json
  41. *
  42. * The format should be either the formatting constants from IntlDateFormatter as
  43. * described in (http://www.php.net/manual/en/class.intldateformatter.php) or a pattern
  44. * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details)
  45. *
  46. * It is possible to provide an array of 2 constants. In this case, the first position
  47. * will be used for formatting the date part of the object and the second position
  48. * will be used to format the time part.
  49. *
  50. * @var string|array|int
  51. * @see \Cake\I18n\Time::i18nFormat()
  52. */
  53. protected static $_jsonEncodeFormat = "yyyy-MM-dd'T'HH:mm:ssxxx";
  54. /**
  55. * Caches whether or not this class is a subclass of a Date or MutableDate
  56. *
  57. * @var bool
  58. */
  59. protected static $_isDateInstance;
  60. /**
  61. * Gets the default locale.
  62. *
  63. * @return string|null The default locale string to be used or null.
  64. */
  65. public static function getDefaultLocale()
  66. {
  67. return static::$defaultLocale;
  68. }
  69. /**
  70. * Sets the default locale.
  71. *
  72. * @param string|null $locale The default locale string to be used or null.
  73. * @return void
  74. */
  75. public static function setDefaultLocale($locale = null)
  76. {
  77. static::$defaultLocale = $locale;
  78. }
  79. /**
  80. * Returns a nicely formatted date string for this object.
  81. *
  82. * The format to be used is stored in the static property `Time::niceFormat`.
  83. *
  84. * @param string|\DateTimeZone|null $timezone Timezone string or DateTimeZone object
  85. * in which the date will be displayed. The timezone stored for this object will not
  86. * be changed.
  87. * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR)
  88. * @return string Formatted date string
  89. */
  90. public function nice($timezone = null, $locale = null)
  91. {
  92. return $this->i18nFormat(static::$niceFormat, $timezone, $locale);
  93. }
  94. /**
  95. * Returns a formatted string for this time object using the preferred format and
  96. * language for the specified locale.
  97. *
  98. * It is possible to specify the desired format for the string to be displayed.
  99. * You can either pass `IntlDateFormatter` constants as the first argument of this
  100. * function, or pass a full ICU date formatting string as specified in the following
  101. * resource: http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details.
  102. *
  103. * Additional to `IntlDateFormatter` constants and date formatting string you can use
  104. * Time::UNIX_TIMESTAMP_FORMAT to get a unix timestamp
  105. *
  106. * ### Examples
  107. *
  108. * ```
  109. * $time = new Time('2014-04-20 22:10');
  110. * $time->i18nFormat(); // outputs '4/20/14, 10:10 PM' for the en-US locale
  111. * $time->i18nFormat(\IntlDateFormatter::FULL); // Use the full date and time format
  112. * $time->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); // Use full date but short time format
  113. * $time->i18nFormat('yyyy-MM-dd HH:mm:ss'); // outputs '2014-04-20 22:10'
  114. * $time->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT); // outputs '1398031800'
  115. * ```
  116. *
  117. * If you wish to control the default format to be used for this method, you can alter
  118. * the value of the static `Time::$defaultLocale` variable and set it to one of the
  119. * possible formats accepted by this function.
  120. *
  121. * You can read about the available IntlDateFormatter constants at
  122. * http://www.php.net/manual/en/class.intldateformatter.php
  123. *
  124. * If you need to display the date in a different timezone than the one being used for
  125. * this Time object without altering its internal state, you can pass a timezone
  126. * string or object as the second parameter.
  127. *
  128. * Finally, should you need to use a different locale for displaying this time object,
  129. * pass a locale string as the third parameter to this function.
  130. *
  131. * ### Examples
  132. *
  133. * ```
  134. * $time = new Time('2014-04-20 22:10');
  135. * $time->i18nFormat(null, null, 'de-DE');
  136. * $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Berlin', 'de-DE');
  137. * ```
  138. *
  139. * You can control the default locale to be used by setting the static variable
  140. * `Time::$defaultLocale` to a valid locale string. If empty, the default will be
  141. * taken from the `intl.default_locale` ini config.
  142. *
  143. * @param string|int|null $format Format string.
  144. * @param string|\DateTimeZone|null $timezone Timezone string or DateTimeZone object
  145. * in which the date will be displayed. The timezone stored for this object will not
  146. * be changed.
  147. * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR)
  148. * @return string Formatted and translated date string
  149. */
  150. public function i18nFormat($format = null, $timezone = null, $locale = null)
  151. {
  152. if ($format === Time::UNIX_TIMESTAMP_FORMAT) {
  153. return $this->getTimestamp();
  154. }
  155. $time = $this;
  156. if ($timezone) {
  157. // Handle the immutable and mutable object cases.
  158. $time = clone $this;
  159. $time = $time->timezone($timezone);
  160. }
  161. $format = $format !== null ? $format : static::$_toStringFormat;
  162. $locale = $locale ?: static::$defaultLocale;
  163. return $this->_formatObject($time, $format, $locale);
  164. }
  165. /**
  166. * Returns a translated and localized date string.
  167. * Implements what IntlDateFormatter::formatObject() is in PHP 5.5+
  168. *
  169. * @param \DateTime $date Date.
  170. * @param string|int|array $format Format.
  171. * @param string $locale The locale name in which the date should be displayed.
  172. * @return string
  173. */
  174. protected function _formatObject($date, $format, $locale)
  175. {
  176. $pattern = $dateFormat = $timeFormat = $calendar = null;
  177. if (is_array($format)) {
  178. list($dateFormat, $timeFormat) = $format;
  179. } elseif (is_numeric($format)) {
  180. $dateFormat = $format;
  181. } else {
  182. $dateFormat = $timeFormat = IntlDateFormatter::FULL;
  183. $pattern = $format;
  184. }
  185. if (preg_match('/@calendar=(japanese|buddhist|chinese|persian|indian|islamic|hebrew|coptic|ethiopic)/', $locale)) {
  186. $calendar = IntlDateFormatter::TRADITIONAL;
  187. } else {
  188. $calendar = IntlDateFormatter::GREGORIAN;
  189. }
  190. $timezone = $date->getTimezone()->getName();
  191. $key = "{$locale}.{$dateFormat}.{$timeFormat}.{$timezone}.{$calendar}.{$pattern}";
  192. if (!isset(static::$_formatters[$key])) {
  193. if ($timezone === '+00:00' || $timezone === 'Z') {
  194. $timezone = 'UTC';
  195. } elseif ($timezone[0] === '+' || $timezone[0] === '-') {
  196. $timezone = 'GMT' . $timezone;
  197. }
  198. static::$_formatters[$key] = datefmt_create(
  199. $locale,
  200. $dateFormat,
  201. $timeFormat,
  202. $timezone,
  203. $calendar,
  204. $pattern
  205. );
  206. }
  207. return static::$_formatters[$key]->format($date->format('U'));
  208. }
  209. /**
  210. * {@inheritDoc}
  211. */
  212. public function __toString()
  213. {
  214. return $this->i18nFormat();
  215. }
  216. /**
  217. * Resets the format used to the default when converting an instance of this type to
  218. * a string
  219. *
  220. * @return void
  221. */
  222. public static function resetToStringFormat()
  223. {
  224. static::setToStringFormat([IntlDateFormatter::SHORT, IntlDateFormatter::SHORT]);
  225. }
  226. /**
  227. * Sets the default format used when type converting instances of this type to string
  228. *
  229. * @param string|array|int $format Format.
  230. * @return void
  231. */
  232. public static function setToStringFormat($format)
  233. {
  234. static::$_toStringFormat = $format;
  235. }
  236. /**
  237. * Sets the default format used when converting this object to json
  238. *
  239. * @param string|array|int $format Format.
  240. * @return void
  241. */
  242. public static function setJsonEncodeFormat($format)
  243. {
  244. static::$_jsonEncodeFormat = $format;
  245. }
  246. /**
  247. * Returns a new Time object after parsing the provided time string based on
  248. * the passed or configured date time format. This method is locale dependent,
  249. * Any string that is passed to this function will be interpreted as a locale
  250. * dependent string.
  251. *
  252. * When no $format is provided, the `toString` format will be used.
  253. *
  254. * If it was impossible to parse the provided time, null will be returned.
  255. *
  256. * Example:
  257. *
  258. * ```
  259. * $time = Time::parseDateTime('10/13/2013 12:54am');
  260. * $time = Time::parseDateTime('13 Oct, 2013 13:54', 'dd MMM, y H:mm');
  261. * $time = Time::parseDateTime('10/10/2015', [IntlDateFormatter::SHORT, -1]);
  262. * ```
  263. *
  264. * @param string $time The time string to parse.
  265. * @param string|array|null $format Any format accepted by IntlDateFormatter.
  266. * @return static|null
  267. */
  268. public static function parseDateTime($time, $format = null)
  269. {
  270. $dateFormat = $format ?: static::$_toStringFormat;
  271. $timeFormat = $pattern = null;
  272. if (is_array($dateFormat)) {
  273. list($newDateFormat, $timeFormat) = $dateFormat;
  274. $dateFormat = $newDateFormat;
  275. } else {
  276. $pattern = $dateFormat;
  277. $dateFormat = null;
  278. }
  279. if (static::$_isDateInstance === null) {
  280. static::$_isDateInstance =
  281. is_subclass_of(static::class, ChronosDate::class) ||
  282. is_subclass_of(static::class, MutableDate::class);
  283. }
  284. $defaultTimezone = static::$_isDateInstance ? 'UTC' : date_default_timezone_get();
  285. $formatter = datefmt_create(
  286. static::$defaultLocale,
  287. $dateFormat,
  288. $timeFormat,
  289. $defaultTimezone,
  290. null,
  291. $pattern
  292. );
  293. $time = $formatter->parse($time);
  294. if ($time !== false) {
  295. $result = new static('@' . $time);
  296. return static::$_isDateInstance ? $result : $result->setTimezone($defaultTimezone);
  297. }
  298. return null;
  299. }
  300. /**
  301. * Returns a new Time object after parsing the provided $date string based on
  302. * the passed or configured date time format. This method is locale dependent,
  303. * Any string that is passed to this function will be interpreted as a locale
  304. * dependent string.
  305. *
  306. * When no $format is provided, the `wordFormat` format will be used.
  307. *
  308. * If it was impossible to parse the provided time, null will be returned.
  309. *
  310. * Example:
  311. *
  312. * ```
  313. * $time = Time::parseDate('10/13/2013');
  314. * $time = Time::parseDate('13 Oct, 2013', 'dd MMM, y');
  315. * $time = Time::parseDate('13 Oct, 2013', IntlDateFormatter::SHORT);
  316. * ```
  317. *
  318. * @param string $date The date string to parse.
  319. * @param string|int|null $format Any format accepted by IntlDateFormatter.
  320. * @return static|null
  321. */
  322. public static function parseDate($date, $format = null)
  323. {
  324. if (is_int($format)) {
  325. $format = [$format, -1];
  326. }
  327. $format = $format ?: static::$wordFormat;
  328. return static::parseDateTime($date, $format);
  329. }
  330. /**
  331. * Returns a new Time object after parsing the provided $time string based on
  332. * the passed or configured date time format. This method is locale dependent,
  333. * Any string that is passed to this function will be interpreted as a locale
  334. * dependent string.
  335. *
  336. * When no $format is provided, the IntlDateFormatter::SHORT format will be used.
  337. *
  338. * If it was impossible to parse the provided time, null will be returned.
  339. *
  340. * Example:
  341. *
  342. * ```
  343. * $time = Time::parseTime('11:23pm');
  344. * ```
  345. *
  346. * @param string $time The time string to parse.
  347. * @param string|int|null $format Any format accepted by IntlDateFormatter.
  348. * @return static|null
  349. */
  350. public static function parseTime($time, $format = null)
  351. {
  352. if (is_int($format)) {
  353. $format = [-1, $format];
  354. }
  355. $format = $format ?: [-1, IntlDateFormatter::SHORT];
  356. return static::parseDateTime($time, $format);
  357. }
  358. /**
  359. * Returns a string that should be serialized when converting this object to json
  360. *
  361. * @return string
  362. */
  363. public function jsonSerialize()
  364. {
  365. return $this->i18nFormat(static::$_jsonEncodeFormat);
  366. }
  367. /**
  368. * Get the difference formatter instance or overwrite the current one.
  369. *
  370. * @param \Cake\I18n\RelativeTimeFormatter|null $formatter The formatter instance when setting.
  371. * @return \Cake\I18n\RelativeTimeFormatter The formatter instance.
  372. */
  373. public static function diffFormatter($formatter = null)
  374. {
  375. if ($formatter === null) {
  376. // Use the static property defined in chronos.
  377. if (static::$diffFormatter === null) {
  378. static::$diffFormatter = new RelativeTimeFormatter();
  379. }
  380. return static::$diffFormatter;
  381. }
  382. return static::$diffFormatter = $formatter;
  383. }
  384. /**
  385. * Returns the data that should be displayed when debugging this object
  386. *
  387. * @return array
  388. */
  389. public function __debugInfo()
  390. {
  391. return [
  392. 'time' => $this->toIso8601String(),
  393. 'timezone' => $this->getTimezone()->getName(),
  394. 'fixedNowTime' => static::hasTestNow() ? static::getTestNow()->toIso8601String() : false
  395. ];
  396. }
  397. }