TimeTest.php 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  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. use DateTimeInterface;
  22. /**
  23. * TimeTest class
  24. */
  25. class TimeTest extends TestCase
  26. {
  27. /**
  28. * setUp method
  29. *
  30. * @return void
  31. */
  32. public function setUp()
  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()
  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->assertEquals('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. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  254. $time = new $class('+8 years +4 months +2 weeks +3 days');
  255. $result = $time->timeAgoInWords([
  256. 'accuracy' => ['year' => 'year'],
  257. 'end' => '+10 years',
  258. ]);
  259. $expected = '8 years';
  260. $this->assertEquals($expected, $result);
  261. $time = new $class('+8 years +4 months +2 weeks +3 days');
  262. $result = $time->timeAgoInWords([
  263. 'accuracy' => ['year' => 'month'],
  264. 'end' => '+10 years',
  265. ]);
  266. $expected = '8 years, 4 months';
  267. $this->assertEquals($expected, $result);
  268. $time = new $class('+8 years +4 months +2 weeks +3 days');
  269. $result = $time->timeAgoInWords([
  270. 'accuracy' => ['year' => 'week'],
  271. 'end' => '+10 years',
  272. ]);
  273. $expected = '8 years, 4 months, 2 weeks';
  274. $this->assertEquals($expected, $result);
  275. $time = new $class('+8 years +4 months +2 weeks +3 days');
  276. $result = $time->timeAgoInWords([
  277. 'accuracy' => ['year' => 'day'],
  278. 'end' => '+10 years',
  279. ]);
  280. $expected = '8 years, 4 months, 2 weeks, 3 days';
  281. $this->assertEquals($expected, $result);
  282. $time = new $class('+1 years +5 weeks');
  283. $result = $time->timeAgoInWords([
  284. 'accuracy' => ['year' => 'year'],
  285. 'end' => '+10 years',
  286. ]);
  287. $expected = '1 year';
  288. $this->assertEquals($expected, $result);
  289. $time = new $class('+58 minutes');
  290. $result = $time->timeAgoInWords([
  291. 'accuracy' => 'hour',
  292. ]);
  293. $expected = 'in about an hour';
  294. $this->assertEquals($expected, $result);
  295. $time = new $class('+23 hours');
  296. $result = $time->timeAgoInWords([
  297. 'accuracy' => 'day',
  298. ]);
  299. $expected = 'in about a day';
  300. $this->assertEquals($expected, $result);
  301. $time = new $class('+20 days');
  302. $result = $time->timeAgoInWords(['accuracy' => 'month']);
  303. $this->assertEquals('in about a month', $result);
  304. }
  305. /**
  306. * Test the format option of timeAgoInWords()
  307. *
  308. * @dataProvider classNameProvider
  309. * @return void
  310. */
  311. public function testTimeAgoInWordsWithFormat($class)
  312. {
  313. $time = new $class('2007-9-25');
  314. $result = $time->timeAgoInWords(['format' => 'yyyy-MM-dd']);
  315. $this->assertEquals('on 2007-09-25', $result);
  316. $time = new $class('+2 weeks +2 days');
  317. $result = $time->timeAgoInWords(['format' => 'yyyy-MM-dd']);
  318. $this->assertRegExp('/^2 weeks, [1|2] day(s)?$/', $result);
  319. $time = new $class('+2 months +2 days');
  320. $result = $time->timeAgoInWords(['end' => '1 month', 'format' => 'yyyy-MM-dd']);
  321. $this->assertEquals('on ' . date('Y-m-d', strtotime('+2 months +2 days')), $result);
  322. }
  323. /**
  324. * test timeAgoInWords() with negative values.
  325. *
  326. * @dataProvider classNameProvider
  327. * @return void
  328. */
  329. public function testTimeAgoInWordsNegativeValues($class)
  330. {
  331. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  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' => '3 month']);
  337. $this->assertEquals('2 months, 2 days ago', $result);
  338. $time = new $class('-2 months -2 days');
  339. $result = $time->timeAgoInWords(['end' => '1 month', 'format' => 'yyyy-MM-dd']);
  340. $this->assertEquals('on ' . date('Y-m-d', strtotime('-2 months -2 days')), $result);
  341. $time = new $class('-2 years -5 months -2 days');
  342. $result = $time->timeAgoInWords(['end' => '3 years']);
  343. $this->assertEquals('2 years, 5 months, 2 days ago', $result);
  344. $time = new $class('-2 weeks -2 days');
  345. $result = $time->timeAgoInWords(['format' => 'yyyy-MM-dd']);
  346. $this->assertEquals('2 weeks, 2 days ago', $result);
  347. $time = new $class('-3 years -12 months');
  348. $result = $time->timeAgoInWords();
  349. $expected = 'on ' . $time->format('n/j/y');
  350. $this->assertEquals($expected, $result);
  351. $time = new $class('-1 month -1 week -6 days');
  352. $result = $time->timeAgoInWords(
  353. ['end' => '1 year', 'accuracy' => ['month' => 'month']]
  354. );
  355. $this->assertEquals('1 month ago', $result);
  356. $time = new $class('-1 years -2 weeks -3 days');
  357. $result = $time->timeAgoInWords(
  358. ['accuracy' => ['year' => 'year']]
  359. );
  360. $expected = 'on ' . $time->format('n/j/y');
  361. $this->assertEquals($expected, $result);
  362. $time = new $class('-13 months -5 days');
  363. $result = $time->timeAgoInWords(['end' => '2 years']);
  364. $this->assertEquals('1 year, 1 month, 5 days ago', $result);
  365. $time = new $class('-58 minutes');
  366. $result = $time->timeAgoInWords(['accuracy' => 'hour']);
  367. $this->assertEquals('about an hour ago', $result);
  368. $time = new $class('-23 hours');
  369. $result = $time->timeAgoInWords(['accuracy' => 'day']);
  370. $this->assertEquals('about a day ago', $result);
  371. $time = new $class('-20 days');
  372. $result = $time->timeAgoInWords(['accuracy' => 'month']);
  373. $this->assertEquals('about a month ago', $result);
  374. }
  375. /**
  376. * testNice method
  377. *
  378. * @dataProvider classNameProvider
  379. * @return void
  380. */
  381. public function testNice($class)
  382. {
  383. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  384. $time = new $class('2014-04-20 20:00', 'UTC');
  385. $this->assertTimeFormat('Apr 20, 2014, 8:00 PM', $time->nice());
  386. $result = $time->nice('America/New_York');
  387. $this->assertTimeFormat('Apr 20, 2014, 4:00 PM', $result);
  388. $this->assertEquals('UTC', $time->getTimezone()->getName());
  389. $this->assertTimeFormat('20 avr. 2014 20:00', $time->nice(null, 'fr-FR'));
  390. $this->assertTimeFormat('20 avr. 2014 16:00', $time->nice('America/New_York', 'fr-FR'));
  391. }
  392. /**
  393. * test formatting dates taking in account preferred i18n locale file
  394. *
  395. * @dataProvider classNameProvider
  396. * @return void
  397. */
  398. public function testI18nFormat($class)
  399. {
  400. $time = new $class('Thu Jan 14 13:59:28 2010');
  401. $result = $time->i18nFormat();
  402. $expected = '1/14/10, 1:59 PM';
  403. $this->assertTimeFormat($expected, $result);
  404. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'es-ES');
  405. $expected = 'jueves, 14 de enero de 2010, 13:59:28 (GMT)';
  406. $this->assertTimeFormat($expected, $result);
  407. $format = [\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT];
  408. $result = $time->i18nFormat($format);
  409. $expected = '1:59 PM';
  410. $this->assertTimeFormat($expected, $result);
  411. $result = $time->i18nFormat('HH:mm:ss', 'Australia/Sydney');
  412. $expected = '00:59:28';
  413. $this->assertTimeFormat($expected, $result);
  414. $class::setDefaultLocale('fr-FR');
  415. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  416. $expected = 'jeudi 14 janvier 2010 13:59:28 UTC';
  417. $this->assertTimeFormat($expected, $result);
  418. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'es-ES');
  419. $expected = 'jueves, 14 de enero de 2010, 13:59:28 (GMT)';
  420. $this->assertTimeFormat($expected, $result, 'Default locale should not be used');
  421. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'fa-SA');
  422. $expected = 'پنجشنبه ۱۴ ژانویهٔ ۲۰۱۰، ساعت ۱۳:۵۹:۲۸ GMT';
  423. $this->assertTimeFormat($expected, $result, 'fa-SA locale should be used');
  424. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-IR@calendar=persian');
  425. $expected = 'Thursday, Dey 24, 1388 at 1:59:28 PM GMT';
  426. $this->assertTimeFormat($expected, $result);
  427. $result = $time->i18nFormat(\IntlDateFormatter::SHORT, null, 'fa-IR@calendar=persian');
  428. $expected = '۱۳۸۸/۱۰/۲۴،‏ ۱۳:۵۹:۲۸ GMT';
  429. $this->assertTimeFormat($expected, $result);
  430. $result = $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-KW@calendar=islamic');
  431. $expected = 'Thursday, Muharram 29, 1431 at 1:59:28 PM GMT';
  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. $result = $time->i18nFormat(\IntlDateFormatter::FULL, 'Asia/Tokyo', 'ja-JP@calendar=japanese');
  437. $expected = '平成22年1月14日木曜日 22時59分28秒 日本標準時';
  438. $this->assertTimeFormat($expected, $result);
  439. }
  440. /**
  441. * testI18nFormatUsingSystemLocale
  442. *
  443. * @return void
  444. */
  445. public function testI18nFormatUsingSystemLocale()
  446. {
  447. // Unset default locale for the Time class to ensure system's locale is used.
  448. Time::setDefaultLocale();
  449. $locale = I18n::getLocale();
  450. $time = new Time(1556864870);
  451. I18n::setLocale('ar');
  452. $this->assertEquals('٢٠١٩-٠٥-٠٣', $time->i18nFormat('yyyy-MM-dd'));
  453. I18n::setLocale('en');
  454. $this->assertEquals('2019-05-03', $time->i18nFormat('yyyy-MM-dd'));
  455. I18n::setLocale($locale);
  456. }
  457. /**
  458. * test formatting dates with offset style timezone
  459. *
  460. * @dataProvider classNameProvider
  461. * @see https://github.com/facebook/hhvm/issues/3637
  462. * @return void
  463. */
  464. public function testI18nFormatWithOffsetTimezone($class)
  465. {
  466. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  467. $time = new $class('2014-01-01T00:00:00+00');
  468. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  469. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT';
  470. $this->assertTimeFormat($expected, $result);
  471. $time = new $class('2014-01-01T00:00:00+09');
  472. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  473. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT+09:00';
  474. $this->assertTimeFormat($expected, $result);
  475. $time = new $class('2014-01-01T00:00:00-01:30');
  476. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  477. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT-01:30';
  478. $this->assertTimeFormat($expected, $result);
  479. $time = new $class('2014-01-01T00:00Z');
  480. $result = $time->i18nFormat(\IntlDateFormatter::FULL);
  481. $expected = 'Wednesday January 1 2014 12:00:00 AM GMT';
  482. $this->assertTimeFormat($expected, $result);
  483. }
  484. /**
  485. * testListTimezones
  486. *
  487. * @dataProvider classNameProvider
  488. * @return void
  489. */
  490. public function testListTimezones($class)
  491. {
  492. $return = $class::listTimezones();
  493. $this->assertTrue(isset($return['Asia']['Asia/Bangkok']));
  494. $this->assertEquals('Bangkok', $return['Asia']['Asia/Bangkok']);
  495. $this->assertTrue(isset($return['America']['America/Argentina/Buenos_Aires']));
  496. $this->assertEquals('Argentina/Buenos_Aires', $return['America']['America/Argentina/Buenos_Aires']);
  497. $this->assertTrue(isset($return['UTC']['UTC']));
  498. $this->assertArrayNotHasKey('Cuba', $return);
  499. $this->assertArrayNotHasKey('US', $return);
  500. $return = $class::listTimezones('#^Asia/#');
  501. $this->assertTrue(isset($return['Asia']['Asia/Bangkok']));
  502. $this->assertArrayNotHasKey('Pacific', $return);
  503. $return = $class::listTimezones(null, null, ['abbr' => true]);
  504. $this->assertTrue(isset($return['Asia']['Asia/Jakarta']));
  505. $this->assertEquals('Jakarta - WIB', $return['Asia']['Asia/Jakarta']);
  506. $this->assertEquals('Regina - CST', $return['America']['America/Regina']);
  507. $return = $class::listTimezones(null, null, [
  508. 'abbr' => true,
  509. 'before' => ' (',
  510. 'after' => ')',
  511. ]);
  512. $this->assertEquals('Jayapura (WIT)', $return['Asia']['Asia/Jayapura']);
  513. $this->assertEquals('Regina (CST)', $return['America']['America/Regina']);
  514. $return = $class::listTimezones('#^(America|Pacific)/#', null, false);
  515. $this->assertArrayHasKey('America/Argentina/Buenos_Aires', $return);
  516. $this->assertArrayHasKey('Pacific/Tahiti', $return);
  517. $return = $class::listTimezones(\DateTimeZone::ASIA);
  518. $this->assertTrue(isset($return['Asia']['Asia/Bangkok']));
  519. $this->assertArrayNotHasKey('Pacific', $return);
  520. $return = $class::listTimezones(\DateTimeZone::PER_COUNTRY, 'US', false);
  521. $this->assertArrayHasKey('Pacific/Honolulu', $return);
  522. $this->assertArrayNotHasKey('Asia/Bangkok', $return);
  523. }
  524. /**
  525. * Tests that __toString uses the i18n formatter
  526. *
  527. * @dataProvider classNameProvider
  528. * @return void
  529. */
  530. public function testToString($class)
  531. {
  532. $time = new $class('2014-04-20 22:10');
  533. $class::setDefaultLocale('fr-FR');
  534. $class::setToStringFormat(\IntlDateFormatter::FULL);
  535. $this->assertTimeFormat('dimanche 20 avril 2014 22:10:00 UTC', (string)$time);
  536. }
  537. /**
  538. * Data provider for invalid values.
  539. *
  540. * @return array
  541. */
  542. public function invalidDataProvider()
  543. {
  544. return [
  545. [null],
  546. [false],
  547. [''],
  548. ];
  549. }
  550. /**
  551. * Test that invalid datetime values do not trigger errors.
  552. *
  553. * @dataProvider invalidDataProvider
  554. * @return void
  555. */
  556. public function testToStringInvalid($value)
  557. {
  558. $time = new Time($value);
  559. $this->assertInternalType('string', (string)$time);
  560. $this->assertNotEmpty((string)$time);
  561. }
  562. /**
  563. * Test that invalid datetime values do not trigger errors.
  564. *
  565. * @dataProvider invalidDataProvider
  566. * @return void
  567. */
  568. public function testToStringInvalidFrozen($value)
  569. {
  570. $time = new FrozenTime($value);
  571. $this->assertInternalType('string', (string)$time);
  572. $this->assertNotEmpty((string)$time);
  573. }
  574. /**
  575. * These invalid values are not invalid on windows :(
  576. *
  577. * @dataProvider classNameProvider
  578. * @return void
  579. */
  580. public function testToStringInvalidZeros($class)
  581. {
  582. $this->skipIf(DS === '\\', 'All zeros are valid on windows.');
  583. $this->skipIf(PHP_INT_SIZE === 4, 'IntlDateFormatter throws exceptions on 32-bit systems');
  584. $time = new $class('0000-00-00');
  585. $this->assertInternalType('string', (string)$time);
  586. $this->assertNotEmpty((string)$time);
  587. $time = new $class('0000-00-00 00:00:00');
  588. $this->assertInternalType('string', (string)$time);
  589. $this->assertNotEmpty((string)$time);
  590. }
  591. /**
  592. * Tests diffForHumans
  593. *
  594. * @dataProvider classNameProvider
  595. * @return void
  596. */
  597. public function testDiffForHumans($class)
  598. {
  599. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  600. $time = new $class('2014-04-20 10:10:10');
  601. $other = new $class('2014-04-27 10:10:10');
  602. $this->assertEquals('1 week before', $time->diffForHumans($other));
  603. $other = new $class('2014-04-21 09:10:10');
  604. $this->assertEquals('23 hours before', $time->diffForHumans($other));
  605. $other = new $class('2014-04-13 09:10:10');
  606. $this->assertEquals('1 week after', $time->diffForHumans($other));
  607. $other = new $class('2014-04-06 09:10:10');
  608. $this->assertEquals('2 weeks after', $time->diffForHumans($other));
  609. $other = new $class('2014-04-21 10:10:10');
  610. $this->assertEquals('1 day before', $time->diffForHumans($other));
  611. $other = new $class('2014-04-22 10:10:10');
  612. $this->assertEquals('2 days before', $time->diffForHumans($other));
  613. $other = new $class('2014-04-20 10:11:10');
  614. $this->assertEquals('1 minute before', $time->diffForHumans($other));
  615. $other = new $class('2014-04-20 10:12:10');
  616. $this->assertEquals('2 minutes before', $time->diffForHumans($other));
  617. $other = new $class('2014-04-20 10:10:09');
  618. $this->assertEquals('1 second after', $time->diffForHumans($other));
  619. $other = new $class('2014-04-20 10:10:08');
  620. $this->assertEquals('2 seconds after', $time->diffForHumans($other));
  621. }
  622. /**
  623. * Tests diffForHumans absolute
  624. *
  625. * @dataProvider classNameProvider
  626. * @return void
  627. */
  628. public function testDiffForHumansAbsolute($class)
  629. {
  630. Time::setTestNow(new $class('2015-12-12 10:10:10'));
  631. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  632. $time = new $class('2014-04-20 10:10:10');
  633. $this->assertEquals('1 year', $time->diffForHumans(null, ['absolute' => true]));
  634. $other = new $class('2014-04-27 10:10:10');
  635. $this->assertEquals('1 week', $time->diffForHumans($other, ['absolute' => true]));
  636. $time = new $class('2016-04-20 10:10:10');
  637. $this->assertEquals('4 months', $time->diffForHumans(null, ['absolute' => true]));
  638. }
  639. /**
  640. * Tests diffForHumans with now
  641. *
  642. * @dataProvider classNameProvider
  643. * @return void
  644. */
  645. public function testDiffForHumansNow($class)
  646. {
  647. Time::setTestNow(new $class('2015-12-12 10:10:10'));
  648. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  649. $time = new $class('2014-04-20 10:10:10');
  650. $this->assertEquals('1 year ago', $time->diffForHumans());
  651. $time = new $class('2016-04-20 10:10:10');
  652. $this->assertEquals('4 months from now', $time->diffForHumans());
  653. }
  654. /**
  655. * Tests encoding a Time object as json
  656. *
  657. * @dataProvider classNameProvider
  658. * @return void
  659. */
  660. public function testJsonEncode($class)
  661. {
  662. if (version_compare(INTL_ICU_VERSION, '50.0', '<')) {
  663. $this->markTestSkipped('ICU 5x is needed');
  664. }
  665. $time = new $class('2014-04-20 10:10:10');
  666. $this->assertEquals('"2014-04-20T10:10:10+00:00"', json_encode($time));
  667. $class::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss');
  668. $this->assertEquals('"2014-04-20 10:10:10"', json_encode($time));
  669. $class::setJsonEncodeFormat($class::UNIX_TIMESTAMP_FORMAT);
  670. $this->assertEquals('1397988610', json_encode($time));
  671. }
  672. /**
  673. * Test jsonSerialize no side-effects
  674. *
  675. * @dataProvider classNameProvider
  676. * @return void
  677. */
  678. public function testJsonEncodeSideEffectFree($class)
  679. {
  680. if (version_compare(INTL_ICU_VERSION, '50.0', '<')) {
  681. $this->markTestSkipped('ICU 5x is needed');
  682. }
  683. $date = new \Cake\I18n\FrozenTime('2016-11-29 09:00:00');
  684. $this->assertInstanceOf('DateTimeZone', $date->timezone);
  685. $result = json_encode($date);
  686. $this->assertEquals('"2016-11-29T09:00:00+00:00"', $result);
  687. $this->assertInstanceOf('DateTimeZone', $date->getTimezone());
  688. }
  689. /**
  690. * Tests change json encoding format
  691. *
  692. * @dataProvider classNameProvider
  693. * @return void
  694. */
  695. public function testSetJsonEncodeFormat($class)
  696. {
  697. $time = new $class('2014-04-20 10:10:10');
  698. $class::setJsonEncodeFormat(static function (DateTimeInterface $t) {
  699. return $t->format(DATE_ATOM);
  700. });
  701. $this->assertEquals('"2014-04-20T10:10:10+00:00"', json_encode($time));
  702. $class::setJsonEncodeFormat("yyyy-MM-dd'T'HH':'mm':'ssZZZZZ");
  703. $this->assertEquals('"2014-04-20T10:10:10Z"', json_encode($time));
  704. }
  705. /**
  706. * Tests debugInfo
  707. *
  708. * @dataProvider classNameProvider
  709. * @return void
  710. */
  711. public function testDebugInfo($class)
  712. {
  713. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  714. $time = new $class('2014-04-20 10:10:10');
  715. $expected = [
  716. 'time' => '2014-04-20 10:10:10.000000+00:00',
  717. 'timezone' => 'UTC',
  718. 'fixedNowTime' => $class::getTestNow()->format('Y-m-d\TH:i:s.uP'),
  719. ];
  720. $this->assertEquals($expected, $time->__debugInfo());
  721. }
  722. /**
  723. * Tests parsing a string into a Time object based on the locale format.
  724. *
  725. * @dataProvider classNameProvider
  726. * @return void
  727. */
  728. public function testParseDateTime($class)
  729. {
  730. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  731. $time = $class::parseDateTime('01/01/1970 00:00am');
  732. $this->assertNotNull($time);
  733. $this->assertEquals('1970-01-01 00:00', $time->format('Y-m-d H:i'));
  734. $time = $class::parseDateTime('10/13/2013 12:54am');
  735. $this->assertNotNull($time);
  736. $this->assertEquals('2013-10-13 00:54', $time->format('Y-m-d H:i'));
  737. $class::setDefaultLocale('fr-FR');
  738. $time = $class::parseDateTime('13 10, 2013 12:54');
  739. $this->assertNotNull($time);
  740. $this->assertEquals('2013-10-13 12:54', $time->format('Y-m-d H:i'));
  741. $time = $class::parseDateTime('13 foo 10 2013 12:54');
  742. $this->assertNull($time);
  743. }
  744. /**
  745. * Tests parsing a string into a Time object based on the locale format.
  746. *
  747. * @dataProvider classNameProvider
  748. * @return void
  749. */
  750. public function testParseDate($class)
  751. {
  752. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  753. $time = $class::parseDate('10/13/2013 12:54am');
  754. $this->assertNotNull($time);
  755. $this->assertEquals('2013-10-13 00:00', $time->format('Y-m-d H:i'));
  756. $time = $class::parseDate('10/13/2013');
  757. $this->assertNotNull($time);
  758. $this->assertEquals('2013-10-13 00:00', $time->format('Y-m-d H:i'));
  759. $class::setDefaultLocale('fr-FR');
  760. $time = $class::parseDate('13 10, 2013 12:54');
  761. $this->assertNotNull($time);
  762. $this->assertEquals('2013-10-13 00:00', $time->format('Y-m-d H:i'));
  763. $time = $class::parseDate('13 foo 10 2013 12:54');
  764. $this->assertNull($time);
  765. $time = $class::parseDate('13 10, 2013', 'dd M, y');
  766. $this->assertNotNull($time);
  767. $this->assertEquals('2013-10-13', $time->format('Y-m-d'));
  768. }
  769. /**
  770. * Tests parsing times using the parseTime function
  771. *
  772. * @dataProvider classNameProvider
  773. * @return void
  774. */
  775. public function testParseTime($class)
  776. {
  777. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  778. $time = $class::parseTime('12:54am');
  779. $this->assertNotNull($time);
  780. $this->assertEquals('00:54:00', $time->format('H:i:s'));
  781. $class::setDefaultLocale('fr-FR');
  782. $time = $class::parseTime('23:54');
  783. $this->assertNotNull($time);
  784. $this->assertEquals('23:54:00', $time->format('H:i:s'));
  785. $time = $class::parseTime('31c2:54');
  786. $this->assertNull($time);
  787. }
  788. /**
  789. * Tests disabling leniency when parsing locale format.
  790. *
  791. * @dataProvider classNameProvider
  792. * @return void
  793. */
  794. public function testLenientParseDate($class)
  795. {
  796. $class::setDefaultLocale('pt_BR');
  797. $class::disableLenientParsing();
  798. $time = $class::parseDate('04/21/2013');
  799. $this->assertSame(null, $time);
  800. $class::enableLenientParsing();
  801. $time = $class::parseDate('04/21/2013');
  802. $this->assertSame('2014-09-04', $time->format('Y-m-d'));
  803. }
  804. /**
  805. * Tests that timeAgoInWords when using a russian locale does not break things
  806. *
  807. * @dataProvider classNameProvider
  808. * @return void
  809. */
  810. public function testRussianTimeAgoInWords($class)
  811. {
  812. I18n::setLocale('ru_RU');
  813. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $time */
  814. $time = new $class('5 days ago');
  815. $result = $time->timeAgoInWords();
  816. $this->assertEquals('5 days ago', $result);
  817. }
  818. /**
  819. * Tests that parsing a date respects de default timezone in PHP.
  820. *
  821. * @dataProvider classNameProvider
  822. * @return void
  823. */
  824. public function testParseDateDifferentTimezone($class)
  825. {
  826. date_default_timezone_set('Europe/Paris');
  827. $class::setDefaultLocale('fr-FR');
  828. $result = $class::parseDate('12/03/2015');
  829. $this->assertEquals('2015-03-12', $result->format('Y-m-d'));
  830. $this->assertEquals(new \DateTimeZone('Europe/Paris'), $result->tz);
  831. }
  832. /**
  833. * Tests the default locale setter.
  834. *
  835. * @dataProvider classNameProvider
  836. * @return void
  837. */
  838. public function testGetSetDefaultLocale($class)
  839. {
  840. $class::setDefaultLocale('fr-FR');
  841. $this->assertSame('fr-FR', $class::getDefaultLocale());
  842. }
  843. /**
  844. * Tests the default locale setter.
  845. *
  846. * @dataProvider classNameProvider
  847. * @return void
  848. */
  849. public function testDefaultLocaleEffectsFormatting($class)
  850. {
  851. /** @var \Cake\I18n\Time|\Cake\I18n\FrozenTime $result */
  852. $result = $class::parseDate('12/03/2015');
  853. $this->assertRegExp('/Dec 3, 2015[ ,]+12:00 AM/', $result->nice());
  854. $class::setDefaultLocale('fr-FR');
  855. $result = $class::parseDate('12/03/2015');
  856. $this->assertRegexp('/12 mars 2015 (?:à )?00:00/', $result->nice());
  857. $expected = 'Y-m-d';
  858. $result = $class::parseDate('12/03/2015');
  859. $this->assertEquals('2015-03-12', $result->format($expected));
  860. }
  861. /**
  862. * Custom assert to allow for variation in the version of the intl library, where
  863. * some translations contain a few extra commas.
  864. *
  865. * @param string $expected
  866. * @param string $result
  867. * @return void
  868. */
  869. public function assertTimeFormat($expected, $result, $message = '')
  870. {
  871. $expected = str_replace([',', '(', ')', ' at', ' م.', ' ه‍.ش.', ' AP', ' AH', ' SAKA', 'à '], '', $expected);
  872. $expected = str_replace([' '], ' ', $expected);
  873. $result = str_replace('Temps universel coordonné', 'UTC', $result);
  874. $result = str_replace('tiempo universal coordinado', 'GMT', $result);
  875. $result = str_replace('Coordinated Universal Time', 'GMT', $result);
  876. $result = str_replace([',', '(', ')', ' at', ' م.', ' ه‍.ش.', ' AP', ' AH', ' SAKA', 'à '], '', $result);
  877. $result = str_replace(['گرینویچ'], 'GMT', $result);
  878. $result = str_replace('زمان هماهنگ جهانی', 'GMT', $result);
  879. $result = str_replace('همغږۍ نړیواله موده', 'GMT', $result);
  880. $result = str_replace([' '], ' ', $result);
  881. $this->assertSame($expected, $result, $message);
  882. }
  883. }