TimeTest.php 32 KB

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