TimeTest.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\I18n;
  16. use Cake\Chronos\Chronos;
  17. use Cake\I18n\FrozenTime;
  18. use Cake\I18n\I18n;
  19. use Cake\I18n\Time;
  20. use Cake\TestSuite\TestCase;
  21. /**
  22. * TimeTest class
  23. */
  24. class TimeTest extends TestCase
  25. {
  26. /**
  27. * setUp method
  28. *
  29. * @return void
  30. */
  31. public function setUp()
  32. {
  33. parent::setUp();
  34. $this->now = Time::getTestNow();
  35. $this->locale = Time::getDefaultLocale();
  36. Time::setDefaultLocale('en_US');
  37. FrozenTime::setDefaultLocale('en_US');
  38. }
  39. /**
  40. * tearDown method
  41. *
  42. * @return void
  43. */
  44. public function tearDown()
  45. {
  46. parent::tearDown();
  47. Time::setTestNow($this->now);
  48. Time::setDefaultLocale($this->locale);
  49. Time::resetToStringFormat();
  50. Time::setJsonEncodeFormat("yyyy-MM-dd'T'HH':'mm':'ssxxx");
  51. FrozenTime::setDefaultLocale($this->locale);
  52. FrozenTime::resetToStringFormat();
  53. FrozenTime::setJsonEncodeFormat("yyyy-MM-dd'T'HH':'mm':'ssxxx");
  54. date_default_timezone_set('UTC');
  55. I18n::setLocale(I18n::DEFAULT_LOCALE);
  56. }
  57. /**
  58. * Restored the original system timezone
  59. *
  60. * @return void
  61. */
  62. protected function _restoreSystemTimezone()
  63. {
  64. date_default_timezone_set($this->_systemTimezoneIdentifier);
  65. }
  66. /**
  67. * Provider for ensuring that Time and FrozenTime work the same way.
  68. *
  69. * @return array
  70. */
  71. public static function classNameProvider()
  72. {
  73. return ['mutable' => ['Cake\I18n\Time'], 'immutable' => ['Cake\I18n\FrozenTime']];
  74. }
  75. /**
  76. * Ensure that instances can be built from other objects.
  77. *
  78. * @dataProvider classNameProvider
  79. * @return void
  80. */
  81. public function testConstructFromAnotherInstance($class)
  82. {
  83. $time = '2015-01-22 10:33:44.123456';
  84. $frozen = new FrozenTime($time, 'America/Chicago');
  85. $subject = new $class($frozen);
  86. $this->assertEquals($time, $subject->format('Y-m-d H:i:s.u'), 'frozen time construction');
  87. $mut = new Time($time, 'America/Chicago');
  88. $subject = new $class($mut);
  89. $this->assertEquals($time, $subject->format('Y-m-d H:i:s.u'), 'mutable time construction');
  90. $mut = new Chronos($time, 'America/Chicago');
  91. $subject = new $class($mut);
  92. $this->assertEquals($time, $subject->format('Y-m-d H:i:s.u'), 'mutable time construction');
  93. $mut = new \DateTime($time, new \DateTimeZone('America/Chicago'));
  94. $subject = new $class($mut);
  95. $this->assertEquals($time, $subject->format('Y-m-d H:i:s.u'), 'mutable time construction');
  96. }
  97. /**
  98. * provider for timeAgoInWords() tests
  99. *
  100. * @return array
  101. */
  102. public static function timeAgoProvider()
  103. {
  104. return [
  105. ['-12 seconds', '12 seconds ago'],
  106. ['-12 minutes', '12 minutes ago'],
  107. ['-2 hours', '2 hours ago'],
  108. ['-1 day', '1 day ago'],
  109. ['-2 days', '2 days ago'],
  110. ['-2 days -3 hours', '2 days, 3 hours ago'],
  111. ['-1 week', '1 week ago'],
  112. ['-2 weeks -2 days', '2 weeks, 2 days ago'],
  113. ['+1 week', '1 week'],
  114. ['+1 week 1 day', '1 week, 1 day'],
  115. ['+2 weeks 2 day', '2 weeks, 2 days'],
  116. ['2007-9-24', 'on 9/24/07'],
  117. ['now', 'just now'],
  118. ];
  119. }
  120. /**
  121. * testTimeAgoInWords method
  122. *
  123. * @dataProvider timeAgoProvider
  124. * @return void
  125. */
  126. public function testTimeAgoInWords($input, $expected)
  127. {
  128. $time = new Time($input);
  129. $result = $time->timeAgoInWords();
  130. $this->assertEquals($expected, $result);
  131. }
  132. /**
  133. * testTimeAgoInWords method
  134. *
  135. * @dataProvider timeAgoProvider
  136. * @return void
  137. */
  138. public function testTimeAgoInWordsFrozenTime($input, $expected)
  139. {
  140. $time = new FrozenTime($input);
  141. $result = $time->timeAgoInWords();
  142. $this->assertEquals($expected, $result);
  143. }
  144. /**
  145. * provider for timeAgo with an end date.
  146. *
  147. * @return array
  148. */
  149. public function timeAgoEndProvider()
  150. {
  151. return [
  152. [
  153. '+4 months +2 weeks +3 days',
  154. '4 months, 2 weeks, 3 days',
  155. '8 years',
  156. ],
  157. [
  158. '+4 months +2 weeks +1 day',
  159. '4 months, 2 weeks, 1 day',
  160. '8 years',
  161. ],
  162. [
  163. '+3 months +2 weeks',
  164. '3 months, 2 weeks',
  165. '8 years',
  166. ],
  167. [
  168. '+3 months +2 weeks +1 day',
  169. '3 months, 2 weeks, 1 day',
  170. '8 years',
  171. ],
  172. [
  173. '+1 months +1 week +1 day',
  174. '1 month, 1 week, 1 day',
  175. '8 years',
  176. ],
  177. [
  178. '+2 months +2 days',
  179. '2 months, 2 days',
  180. '+2 months +2 days',
  181. ],
  182. [
  183. '+2 months +12 days',
  184. '2 months, 1 week, 5 days',
  185. '3 months',
  186. ],
  187. ];
  188. }
  189. /**
  190. * test the timezone option for timeAgoInWords
  191. *
  192. * @dataProvider classNameProvider
  193. * @return void
  194. */
  195. public function testTimeAgoInWordsTimezone($class)
  196. {
  197. $time = new FrozenTime('1990-07-31 20:33:00 UTC');
  198. $result = $time->timeAgoInWords(
  199. [
  200. 'timezone' => 'America/Vancouver',
  201. 'end' => '+1month',
  202. 'format' => 'dd-MM-YYYY HH:mm:ss',
  203. ]
  204. );
  205. $this->assertEquals('on 31-07-1990 13:33:00', $result);
  206. }
  207. /**
  208. * test the end option for timeAgoInWords
  209. *
  210. * @dataProvider timeAgoEndProvider
  211. * @return void
  212. */
  213. public function testTimeAgoInWordsEnd($input, $expected, $end)
  214. {
  215. $time = new Time($input);
  216. $result = $time->timeAgoInWords(['end' => $end]);
  217. $this->assertEquals($expected, $result);
  218. }
  219. /**
  220. * test the custom string options for timeAgoInWords
  221. *
  222. * @dataProvider classNameProvider
  223. * @return void
  224. */
  225. public function testTimeAgoInWordsCustomStrings($class)
  226. {
  227. $time = new $class('-8 years -4 months -2 weeks -3 days');
  228. $result = $time->timeAgoInWords([
  229. 'relativeString' => 'at least %s ago',
  230. 'accuracy' => ['year' => 'year'],
  231. 'end' => '+10 years',
  232. ]);
  233. $expected = 'at least 8 years ago';
  234. $this->assertEquals($expected, $result);
  235. $time = new $class('+4 months +2 weeks +3 days');
  236. $result = $time->timeAgoInWords([
  237. 'absoluteString' => 'exactly on %s',
  238. 'accuracy' => ['year' => 'year'],
  239. 'end' => '+2 months',
  240. ]);
  241. $expected = 'exactly on ' . date('n/j/y', strtotime('+4 months +2 weeks +3 days'));
  242. $this->assertEquals($expected, $result);
  243. }
  244. /**
  245. * Test the accuracy option for timeAgoInWords()
  246. *
  247. * @dataProvider classNameProvider
  248. * @return void
  249. */
  250. public function testTimeAgoInWordsAccuracy($class)
  251. {
  252. $time = new $class('+8 years +4 months +2 weeks +3 days');
  253. $result = $time->timeAgoInWords([
  254. 'accuracy' => ['year' => 'year'],
  255. 'end' => '+10 years',
  256. ]);
  257. $expected = '8 years';
  258. $this->assertEquals($expected, $result);
  259. $time = new $class('+8 years +4 months +2 weeks +3 days');
  260. $result = $time->timeAgoInWords([
  261. 'accuracy' => ['year' => 'month'],
  262. 'end' => '+10 years',
  263. ]);
  264. $expected = '8 years, 4 months';
  265. $this->assertEquals($expected, $result);
  266. $time = new $class('+8 years +4 months +2 weeks +3 days');
  267. $result = $time->timeAgoInWords([
  268. 'accuracy' => ['year' => 'week'],
  269. 'end' => '+10 years',
  270. ]);
  271. $expected = '8 years, 4 months, 2 weeks';
  272. $this->assertEquals($expected, $result);
  273. $time = new $class('+8 years +4 months +2 weeks +3 days');
  274. $result = $time->timeAgoInWords([
  275. 'accuracy' => ['year' => 'day'],
  276. 'end' => '+10 years',
  277. ]);
  278. $expected = '8 years, 4 months, 2 weeks, 3 days';
  279. $this->assertEquals($expected, $result);
  280. $time = new $class('+1 years +5 weeks');
  281. $result = $time->timeAgoInWords([
  282. 'accuracy' => ['year' => 'year'],
  283. 'end' => '+10 years',
  284. ]);
  285. $expected = '1 year';
  286. $this->assertEquals($expected, $result);
  287. $time = new $class('+58 minutes');
  288. $result = $time->timeAgoInWords([
  289. 'accuracy' => 'hour',
  290. ]);
  291. $expected = 'in about an hour';
  292. $this->assertEquals($expected, $result);
  293. $time = new $class('+23 hours');
  294. $result = $time->timeAgoInWords([
  295. 'accuracy' => 'day',
  296. ]);
  297. $expected = 'in about a day';
  298. $this->assertEquals($expected, $result);
  299. $time = new $class('+20 days');
  300. $result = $time->timeAgoInWords(['accuracy' => 'month']);
  301. $this->assertEquals('in about a month', $result);
  302. }
  303. /**
  304. * Test the format option of timeAgoInWords()
  305. *
  306. * @dataProvider classNameProvider
  307. * @return void
  308. */
  309. public function testTimeAgoInWordsWithFormat($class)
  310. {
  311. $time = new $class('2007-9-25');
  312. $result = $time->timeAgoInWords(['format' => 'yyyy-MM-dd']);
  313. $this->assertEquals('on 2007-09-25', $result);
  314. $time = new $class('+2 weeks +2 days');
  315. $result = $time->timeAgoInWords(['format' => 'yyyy-MM-dd']);
  316. $this->assertRegExp('/^2 weeks, [1|2] day(s)?$/', $result);
  317. $time = new $class('+2 months +2 days');
  318. $result = $time->timeAgoInWords(['end' => '1 month', 'format' => 'yyyy-MM-dd']);
  319. $this->assertEquals('on ' . date('Y-m-d', strtotime('+2 months +2 days')), $result);
  320. }
  321. /**
  322. * test timeAgoInWords() with negative values.
  323. *
  324. * @dataProvider classNameProvider
  325. * @return void
  326. */
  327. public function testTimeAgoInWordsNegativeValues($class)
  328. {
  329. $time = new $class('-2 months -2 days');
  330. $result = $time->timeAgoInWords(['end' => '3 month']);
  331. $this->assertEquals('2 months, 2 days ago', $result);
  332. $time = new $class('-2 months -2 days');
  333. $result = $time->timeAgoInWords(['end' => '3 month']);
  334. $this->assertEquals('2 months, 2 days ago', $result);
  335. $time = new $class('-2 months -2 days');
  336. $result = $time->timeAgoInWords(['end' => '1 month', 'format' => 'yyyy-MM-dd']);
  337. $this->assertEquals('on ' . date('Y-m-d', strtotime('-2 months -2 days')), $result);
  338. $time = new $class('-2 years -5 months -2 days');
  339. $result = $time->timeAgoInWords(['end' => '3 years']);
  340. $this->assertEquals('2 years, 5 months, 2 days ago', $result);
  341. $time = new $class('-2 weeks -2 days');
  342. $result = $time->timeAgoInWords(['format' => 'yyyy-MM-dd']);
  343. $this->assertEquals('2 weeks, 2 days ago', $result);
  344. $time = new $class('-3 years -12 months');
  345. $result = $time->timeAgoInWords();
  346. $expected = 'on ' . $time->format('n/j/y');
  347. $this->assertEquals($expected, $result);
  348. $time = new $class('-1 month -1 week -6 days');
  349. $result = $time->timeAgoInWords(
  350. ['end' => '1 year', 'accuracy' => ['month' => 'month']]
  351. );
  352. $this->assertEquals('1 month ago', $result);
  353. $time = new $class('-1 years -2 weeks -3 days');
  354. $result = $time->timeAgoInWords(
  355. ['accuracy' => ['year' => 'year']]
  356. );
  357. $expected = 'on ' . $time->format('n/j/y');
  358. $this->assertEquals($expected, $result);
  359. $time = new $class('-13 months -5 days');
  360. $result = $time->timeAgoInWords(['end' => '2 years']);
  361. $this->assertEquals('1 year, 1 month, 5 days ago', $result);
  362. $time = new $class('-58 minutes');
  363. $result = $time->timeAgoInWords(['accuracy' => 'hour']);
  364. $this->assertEquals('about an hour ago', $result);
  365. $time = new $class('-23 hours');
  366. $result = $time->timeAgoInWords(['accuracy' => 'day']);
  367. $this->assertEquals('about a day ago', $result);
  368. $time = new $class('-20 days');
  369. $result = $time->timeAgoInWords(['accuracy' => 'month']);
  370. $this->assertEquals('about a month ago', $result);
  371. }
  372. /**
  373. * testNice method
  374. *
  375. * @dataProvider classNameProvider
  376. * @return void
  377. */
  378. public function testNice($class)
  379. {
  380. $time = new $class('2014-04-20 20:00', 'UTC');
  381. $this->assertTimeFormat('Apr 20, 2014, 8:00 PM', $time->nice());
  382. $result = $time->nice('America/New_York');
  383. $this->assertTimeFormat('Apr 20, 2014, 4:00 PM', $result);
  384. $this->assertEquals('UTC', $time->getTimezone()->getName());
  385. $this->assertTimeFormat('20 avr. 2014 20:00', $time->nice(null, 'fr-FR'));
  386. $this->assertTimeFormat('20 avr. 2014 16:00', $time->nice('America/New_York', 'fr-FR'));
  387. }
  388. /**
  389. * test formatting dates taking in account preferred i18n locale file
  390. *
  391. * @dataProvider classNameProvider
  392. * @return void
  393. */
  394. public function testI18nFormat($class)
  395. {
  396. $time = new $class('Thu Jan 14 13:59:28 2010');
  397. $result = $time->i18nFormat();
  398. $expected = '1/14/10, 1:59 PM';
  399. $this->assertTimeFormat($expected, $result);
  400. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'es-ES');
  401. $expected = 'jueves, 14 de enero de 2010, 13:59:28 (GMT)';
  402. $this->assertTimeFormat($expected, $result);
  403. $format = [\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT];
  404. $result = $time->i18nFormat($format);
  405. $expected = '1:59 PM';
  406. $this->assertTimeFormat($expected, $result);
  407. $result = $time->i18nFormat('HH:mm:ss', 'Australia/Sydney');
  408. $expected = '00:59:28';
  409. $this->assertTimeFormat($expected, $result);
  410. $class::setDefaultLocale('fr-FR');
  411. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  412. $expected = 'jeudi 14 janvier 2010 13:59:28 UTC';
  413. $this->assertTimeFormat($expected, $result);
  414. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'es-ES');
  415. $expected = 'jueves, 14 de enero de 2010, 13:59:28 (GMT)';
  416. $this->assertTimeFormat($expected, $result, 'Default locale should not be used');
  417. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'fa-SA');
  418. $expected = 'پنجشنبه ۱۴ ژانویهٔ ۲۰۱۰، ساعت ۱۳:۵۹:۲۸ GMT';
  419. $this->assertTimeFormat($expected, $result, 'fa-SA locale should be used');
  420. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-IR@calendar=persian');
  421. $expected = 'Thursday, Dey 24, 1388 at 1:59:28 PM GMT';
  422. $this->assertTimeFormat($expected, $result);
  423. $result = $time->i18nFormat(\IntlDateFormatter::SHORT, null, 'fa-IR@calendar=persian');
  424. $expected = '۱۳۸۸/۱۰/۲۴،‏ ۱۳:۵۹:۲۸ GMT';
  425. $this->assertTimeFormat($expected, $result);
  426. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-KW@calendar=islamic');
  427. $expected = 'Thursday, Muharram 29, 1431 at 1:59:28 PM GMT';
  428. $this->assertTimeFormat($expected, $result);
  429. $result = $time->i18nFormat(\IntlDateFormatter::FULL, 'Asia/Tokyo', 'ja-JP@calendar=japanese');
  430. $expected = '平成22年1月14日木曜日 22時59分28秒 日本標準時';
  431. $this->assertTimeFormat($expected, $result);
  432. $result = $time->i18nFormat(\IntlDateFormatter::FULL, 'Asia/Tokyo', 'ja-JP@calendar=japanese');
  433. $expected = '平成22年1月14日木曜日 22時59分28秒 日本標準時';
  434. $this->assertTimeFormat($expected, $result);
  435. }
  436. /**
  437. * testI18nFormatUsingSystemLocale
  438. *
  439. * @return void
  440. */
  441. public function testI18nFormatUsingSystemLocale()
  442. {
  443. // Unset default locale for the Time class to ensure system's locale is used.
  444. Time::setDefaultLocale();
  445. $locale = I18n::getLocale();
  446. $time = new Time(1556864870);
  447. I18n::setLocale('ar');
  448. $this->assertEquals('٢٠١٩-٠٥-٠٣', $time->i18nFormat('yyyy-MM-dd'));
  449. I18n::setLocale('en');
  450. $this->assertEquals('2019-05-03', $time->i18nFormat('yyyy-MM-dd'));
  451. I18n::setLocale($locale);
  452. }
  453. /**
  454. * test formatting dates with offset style timezone
  455. *
  456. * @dataProvider classNameProvider
  457. * @see https://github.com/facebook/hhvm/issues/3637
  458. * @return void
  459. */
  460. public function testI18nFormatWithOffsetTimezone($class)
  461. {
  462. $time = new $class('2014-01-01T00:00:00+00');
  463. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  464. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT';
  465. $this->assertTimeFormat($expected, $result);
  466. $time = new $class('2014-01-01T00:00:00+09');
  467. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  468. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT+09:00';
  469. $this->assertTimeFormat($expected, $result);
  470. $time = new $class('2014-01-01T00:00:00-01:30');
  471. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  472. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT-01:30';
  473. $this->assertTimeFormat($expected, $result);
  474. $time = new $class('2014-01-01T00:00Z');
  475. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  476. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT';
  477. $this->assertTimeFormat($expected, $result);
  478. }
  479. /**
  480. * testListTimezones
  481. *
  482. * @dataProvider classNameProvider
  483. * @return void
  484. */
  485. public function testListTimezones($class)
  486. {
  487. $return = $class::listTimezones();
  488. $this->assertTrue(isset($return['Asia']['Asia/Bangkok']));
  489. $this->assertEquals('Bangkok', $return['Asia']['Asia/Bangkok']);
  490. $this->assertTrue(isset($return['America']['America/Argentina/Buenos_Aires']));
  491. $this->assertEquals('Argentina/Buenos_Aires', $return['America']['America/Argentina/Buenos_Aires']);
  492. $this->assertTrue(isset($return['UTC']['UTC']));
  493. $this->assertArrayNotHasKey('Cuba', $return);
  494. $this->assertArrayNotHasKey('US', $return);
  495. $return = $class::listTimezones('#^Asia/#');
  496. $this->assertTrue(isset($return['Asia']['Asia/Bangkok']));
  497. $this->assertArrayNotHasKey('Pacific', $return);
  498. $return = $class::listTimezones(null, null, ['abbr' => true]);
  499. $this->assertTrue(isset($return['Asia']['Asia/Jakarta']));
  500. $this->assertEquals('Jakarta - WIB', $return['Asia']['Asia/Jakarta']);
  501. $this->assertEquals('Regina - CST', $return['America']['America/Regina']);
  502. $return = $class::listTimezones(null, null, [
  503. 'abbr' => true,
  504. 'before' => ' (',
  505. 'after' => ')',
  506. ]);
  507. $this->assertEquals('Jayapura (WIT)', $return['Asia']['Asia/Jayapura']);
  508. $this->assertEquals('Regina (CST)', $return['America']['America/Regina']);
  509. $return = $class::listTimezones('#^(America|Pacific)/#', null, false);
  510. $this->assertArrayHasKey('America/Argentina/Buenos_Aires', $return);
  511. $this->assertArrayHasKey('Pacific/Tahiti', $return);
  512. $return = $class::listTimezones(\DateTimeZone::ASIA);
  513. $this->assertTrue(isset($return['Asia']['Asia/Bangkok']));
  514. $this->assertArrayNotHasKey('Pacific', $return);
  515. $return = $class::listTimezones(\DateTimeZone::PER_COUNTRY, 'US', false);
  516. $this->assertArrayHasKey('Pacific/Honolulu', $return);
  517. $this->assertArrayNotHasKey('Asia/Bangkok', $return);
  518. }
  519. /**
  520. * Tests that __toString uses the i18n formatter
  521. *
  522. * @dataProvider classNameProvider
  523. * @return void
  524. */
  525. public function testToString($class)
  526. {
  527. $time = new $class('2014-04-20 22:10');
  528. $class::setDefaultLocale('fr-FR');
  529. $class::setToStringFormat(\IntlDateFormatter::FULL);
  530. $this->assertTimeFormat('dimanche 20 avril 2014 22:10:00 UTC', (string)$time);
  531. }
  532. /**
  533. * Data provider for invalid values.
  534. *
  535. * @return array
  536. */
  537. public function invalidDataProvider()
  538. {
  539. return [
  540. [null],
  541. [false],
  542. [''],
  543. ];
  544. }
  545. /**
  546. * Test that invalid datetime values do not trigger errors.
  547. *
  548. * @dataProvider invalidDataProvider
  549. * @return void
  550. */
  551. public function testToStringInvalid($value)
  552. {
  553. $time = new Time($value);
  554. $this->assertInternalType('string', (string)$time);
  555. $this->assertNotEmpty((string)$time);
  556. }
  557. /**
  558. * Test that invalid datetime values do not trigger errors.
  559. *
  560. * @dataProvider invalidDataProvider
  561. * @return void
  562. */
  563. public function testToStringInvalidFrozen($value)
  564. {
  565. $time = new FrozenTime($value);
  566. $this->assertInternalType('string', (string)$time);
  567. $this->assertNotEmpty((string)$time);
  568. }
  569. /**
  570. * These invalid values are not invalid on windows :(
  571. *
  572. * @dataProvider classNameProvider
  573. * @return void
  574. */
  575. public function testToStringInvalidZeros($class)
  576. {
  577. $this->skipIf(DS === '\\', 'All zeros are valid on windows.');
  578. $this->skipIf(PHP_INT_SIZE === 4, 'IntlDateFormatter throws exceptions on 32-bit systems');
  579. $time = new $class('0000-00-00');
  580. $this->assertInternalType('string', (string)$time);
  581. $this->assertNotEmpty((string)$time);
  582. $time = new $class('0000-00-00 00:00:00');
  583. $this->assertInternalType('string', (string)$time);
  584. $this->assertNotEmpty((string)$time);
  585. }
  586. /**
  587. * Tests diffForHumans
  588. *
  589. * @dataProvider classNameProvider
  590. * @return void
  591. */
  592. public function testDiffForHumans($class)
  593. {
  594. $time = new $class('2014-04-20 10:10:10');
  595. $other = new $class('2014-04-27 10:10:10');
  596. $this->assertEquals('1 week before', $time->diffForHumans($other));
  597. $other = new $class('2014-04-21 09:10:10');
  598. $this->assertEquals('23 hours before', $time->diffForHumans($other));
  599. $other = new $class('2014-04-13 09:10:10');
  600. $this->assertEquals('1 week after', $time->diffForHumans($other));
  601. $other = new $class('2014-04-06 09:10:10');
  602. $this->assertEquals('2 weeks after', $time->diffForHumans($other));
  603. $other = new $class('2014-04-21 10:10:10');
  604. $this->assertEquals('1 day before', $time->diffForHumans($other));
  605. $other = new $class('2014-04-22 10:10:10');
  606. $this->assertEquals('2 days before', $time->diffForHumans($other));
  607. $other = new $class('2014-04-20 10:11:10');
  608. $this->assertEquals('1 minute before', $time->diffForHumans($other));
  609. $other = new $class('2014-04-20 10:12:10');
  610. $this->assertEquals('2 minutes before', $time->diffForHumans($other));
  611. $other = new $class('2014-04-20 10:10:09');
  612. $this->assertEquals('1 second after', $time->diffForHumans($other));
  613. $other = new $class('2014-04-20 10:10:08');
  614. $this->assertEquals('2 seconds after', $time->diffForHumans($other));
  615. }
  616. /**
  617. * Tests diffForHumans absolute
  618. *
  619. * @dataProvider classNameProvider
  620. * @return void
  621. */
  622. public function testDiffForHumansAbsolute($class)
  623. {
  624. Time::setTestNow(new $class('2015-12-12 10:10:10'));
  625. $time = new $class('2014-04-20 10:10:10');
  626. $this->assertEquals('1 year', $time->diffForHumans(null, ['absolute' => true]));
  627. $other = new $class('2014-04-27 10:10:10');
  628. $this->assertEquals('1 week', $time->diffForHumans($other, ['absolute' => true]));
  629. $time = new $class('2016-04-20 10:10:10');
  630. $this->assertEquals('4 months', $time->diffForHumans(null, ['absolute' => true]));
  631. }
  632. /**
  633. * Tests diffForHumans with now
  634. *
  635. * @dataProvider classNameProvider
  636. * @return void
  637. */
  638. public function testDiffForHumansNow($class)
  639. {
  640. Time::setTestNow(new $class('2015-12-12 10:10:10'));
  641. $time = new $class('2014-04-20 10:10:10');
  642. $this->assertEquals('1 year ago', $time->diffForHumans());
  643. $time = new $class('2016-04-20 10:10:10');
  644. $this->assertEquals('4 months from now', $time->diffForHumans());
  645. }
  646. /**
  647. * Tests encoding a Time object as json
  648. *
  649. * @dataProvider classNameProvider
  650. * @return void
  651. */
  652. public function testJsonEncode($class)
  653. {
  654. if (version_compare(INTL_ICU_VERSION, '50.0', '<')) {
  655. $this->markTestSkipped('ICU 5x is needed');
  656. }
  657. $time = new $class('2014-04-20 10:10:10');
  658. $this->assertEquals('"2014-04-20T10:10:10+00:00"', json_encode($time));
  659. $class::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss');
  660. $this->assertEquals('"2014-04-20 10:10:10"', json_encode($time));
  661. $class::setJsonEncodeFormat($class::UNIX_TIMESTAMP_FORMAT);
  662. $this->assertEquals('1397988610', json_encode($time));
  663. }
  664. /**
  665. * Test jsonSerialize no side-effects
  666. *
  667. * @dataProvider classNameProvider
  668. * @return void
  669. */
  670. public function testJsonEncodeSideEffectFree($class)
  671. {
  672. if (version_compare(INTL_ICU_VERSION, '50.0', '<')) {
  673. $this->markTestSkipped('ICU 5x is needed');
  674. }
  675. $date = new \Cake\I18n\FrozenTime('2016-11-29 09:00:00');
  676. $this->assertInstanceOf('DateTimeZone', $date->timezone);
  677. $result = json_encode($date);
  678. $this->assertEquals('"2016-11-29T09:00:00+00:00"', $result);
  679. $this->assertInstanceOf('DateTimeZone', $date->getTimezone());
  680. }
  681. /**
  682. * Tests change json encoding format
  683. *
  684. * @dataProvider classNameProvider
  685. * @return void
  686. */
  687. public function testSetJsonEncodeFormat($class)
  688. {
  689. $time = new $class('2014-04-20 10:10:10');
  690. $class::setJsonEncodeFormat(static function ($t) {
  691. return $t->format(DATE_ATOM);
  692. });
  693. $this->assertEquals('"2014-04-20T10:10:10+00:00"', json_encode($time));
  694. $class::setJsonEncodeFormat("yyyy-MM-dd'T'HH':'mm':'ssZZZZZ");
  695. $this->assertEquals('"2014-04-20T10:10:10Z"', json_encode($time));
  696. }
  697. /**
  698. * Tests debugInfo
  699. *
  700. * @dataProvider classNameProvider
  701. * @return void
  702. */
  703. public function testDebugInfo($class)
  704. {
  705. $time = new $class('2014-04-20 10:10:10');
  706. $expected = [
  707. 'time' => '2014-04-20 10:10:10.000000+00:00',
  708. 'timezone' => 'UTC',
  709. 'fixedNowTime' => $class::getTestNow()->format('Y-m-d H:i:s.uP'),
  710. ];
  711. $this->assertEquals($expected, $time->__debugInfo());
  712. }
  713. /**
  714. * Tests parsing a string into a Time object based on the locale format.
  715. *
  716. * @dataProvider classNameProvider
  717. * @return void
  718. */
  719. public function testParseDateTime($class)
  720. {
  721. $time = $class::parseDateTime('01/01/1970 00:00am');
  722. $this->assertNotNull($time);
  723. $this->assertEquals('1970-01-01 00:00', $time->format('Y-m-d H:i'));
  724. $time = $class::parseDateTime('10/13/2013 12:54am');
  725. $this->assertNotNull($time);
  726. $this->assertEquals('2013-10-13 00:54', $time->format('Y-m-d H:i'));
  727. $class::setDefaultLocale('fr-FR');
  728. $time = $class::parseDateTime('13 10, 2013 12:54');
  729. $this->assertNotNull($time);
  730. $this->assertEquals('2013-10-13 12:54', $time->format('Y-m-d H:i'));
  731. $time = $class::parseDateTime('13 foo 10 2013 12:54');
  732. $this->assertNull($time);
  733. }
  734. /**
  735. * Tests parsing a string into a Time object based on the locale format.
  736. *
  737. * @dataProvider classNameProvider
  738. * @return void
  739. */
  740. public function testParseDate($class)
  741. {
  742. $time = $class::parseDate('10/13/2013 12:54am');
  743. $this->assertNotNull($time);
  744. $this->assertEquals('2013-10-13 00:00', $time->format('Y-m-d H:i'));
  745. $time = $class::parseDate('10/13/2013');
  746. $this->assertNotNull($time);
  747. $this->assertEquals('2013-10-13 00:00', $time->format('Y-m-d H:i'));
  748. $class::setDefaultLocale('fr-FR');
  749. $time = $class::parseDate('13 10, 2013 12:54');
  750. $this->assertNotNull($time);
  751. $this->assertEquals('2013-10-13 00:00', $time->format('Y-m-d H:i'));
  752. $time = $class::parseDate('13 foo 10 2013 12:54');
  753. $this->assertNull($time);
  754. $time = $class::parseDate('13 10, 2013', 'dd M, y');
  755. $this->assertNotNull($time);
  756. $this->assertEquals('2013-10-13', $time->format('Y-m-d'));
  757. }
  758. /**
  759. * Tests parsing times using the parseTime function
  760. *
  761. * @dataProvider classNameProvider
  762. * @return void
  763. */
  764. public function testParseTime($class)
  765. {
  766. $time = $class::parseTime('12:54am');
  767. $this->assertNotNull($time);
  768. $this->assertEquals('00:54:00', $time->format('H:i:s'));
  769. $class::setDefaultLocale('fr-FR');
  770. $time = $class::parseTime('23:54');
  771. $this->assertNotNull($time);
  772. $this->assertEquals('23:54:00', $time->format('H:i:s'));
  773. $time = $class::parseTime('31c2:54');
  774. $this->assertNull($time);
  775. }
  776. /**
  777. * Tests disabling leniency when parsing locale format.
  778. *
  779. * @dataProvider classNameProvider
  780. * @return void
  781. */
  782. public function testLenientParseDate($class)
  783. {
  784. $class::setDefaultLocale('pt_BR');
  785. $class::disableLenientParsing();
  786. $time = $class::parseDate('04/21/2013');
  787. $this->assertSame(null, $time);
  788. $class::enableLenientParsing();
  789. $time = $class::parseDate('04/21/2013');
  790. $this->assertSame('2014-09-04', $time->format('Y-m-d'));
  791. }
  792. /**
  793. * Tests that timeAgoInWords when using a russian locale does not break things
  794. *
  795. * @dataProvider classNameProvider
  796. * @return void
  797. */
  798. public function testRussianTimeAgoInWords($class)
  799. {
  800. I18n::setLocale('ru_RU');
  801. $time = new $class('5 days ago');
  802. $result = $time->timeAgoInWords();
  803. $this->assertEquals('5 days ago', $result);
  804. }
  805. /**
  806. * Tests that parsing a date respects de default timezone in PHP.
  807. *
  808. * @dataProvider classNameProvider
  809. * @return void
  810. */
  811. public function testParseDateDifferentTimezone($class)
  812. {
  813. date_default_timezone_set('Europe/Paris');
  814. $class::setDefaultLocale('fr-FR');
  815. $result = $class::parseDate('12/03/2015');
  816. $this->assertEquals('2015-03-12', $result->format('Y-m-d'));
  817. $this->assertEquals(new \DateTimeZone('Europe/Paris'), $result->tz);
  818. }
  819. /**
  820. * Tests the default locale setter.
  821. *
  822. * @dataProvider classNameProvider
  823. * @return void
  824. */
  825. public function testGetSetDefaultLocale($class)
  826. {
  827. $class::setDefaultLocale('fr-FR');
  828. $this->assertSame('fr-FR', $class::getDefaultLocale());
  829. }
  830. /**
  831. * Tests the default locale setter.
  832. *
  833. * @dataProvider classNameProvider
  834. * @return void
  835. */
  836. public function testDefaultLocaleEffectsFormatting($class)
  837. {
  838. $result = $class::parseDate('12/03/2015');
  839. $this->assertRegExp('/Dec 3, 2015[ ,]+12:00 AM/', $result->nice());
  840. $class::setDefaultLocale('fr-FR');
  841. $result = $class::parseDate('12/03/2015');
  842. $this->assertRegexp('/12 mars 2015 (?:à )?00:00/', $result->nice());
  843. $expected = 'Y-m-d';
  844. $result = $class::parseDate('12/03/2015');
  845. $this->assertEquals('2015-03-12', $result->format($expected));
  846. }
  847. /**
  848. * Custom assert to allow for variation in the version of the intl library, where
  849. * some translations contain a few extra commas.
  850. *
  851. * @param string $expected
  852. * @param string $result
  853. * @return void
  854. */
  855. public function assertTimeFormat($expected, $result, $message = '')
  856. {
  857. $expected = str_replace([',', '(', ')', ' at', ' م.', ' ه‍.ش.', ' AP', ' AH', ' SAKA', 'à '], '', $expected);
  858. $expected = str_replace([' '], ' ', $expected);
  859. $result = str_replace('Temps universel coordonné', 'UTC', $result);
  860. $result = str_replace('tiempo universal coordinado', 'GMT', $result);
  861. $result = str_replace('Coordinated Universal Time', 'GMT', $result);
  862. $result = str_replace([',', '(', ')', ' at', ' م.', ' ه‍.ش.', ' AP', ' AH', ' SAKA', 'à '], '', $result);
  863. $result = str_replace(['گرینویچ'], 'GMT', $result);
  864. $result = str_replace('زمان هماهنگ جهانی', 'GMT', $result);
  865. $result = str_replace('همغږۍ نړیواله موده', 'GMT', $result);
  866. $result = str_replace([' '], ' ', $result);
  867. $this->assertSame($expected, $result, $message);
  868. }
  869. }