1<?php defined('SYSPATH') OR die('Kohana bootstrap needs to be included before tests run');
2
3/**
4 * Tests Date class
5 *
6 * @group kohana
7 * @group kohana.core
8 * @group kohana.core.date
9 *
10 * @package    Kohana
11 * @category   Tests
12 * @author     Kohana Team
13 * @author     BRMatt <matthew@sigswitch.com>
14 * @copyright  (c) 2008-2012 Kohana Team
15 * @license    http://kohanaframework.org/license
16 */
17class Kohana_DateTest extends Unittest_TestCase
18{
19	protected $_original_timezone = NULL;
20	protected $default_locale;
21
22	/**
23	 * Ensures we have a consistant timezone for testing.
24	 */
25	// @codingStandardsIgnoreStart
26	public function setUp()
27	// @codingStandardsIgnoreEnd
28	{
29		parent::setUp();
30
31		$this->_original_timezone = date_default_timezone_get();
32		$this->default_locale = setlocale(LC_ALL, 0);
33
34		date_default_timezone_set('America/Chicago');
35		setlocale(LC_ALL, 'en_US.utf8');
36	}
37
38	/**
39	 * Restores original timezone after testing.
40	 */
41	// @codingStandardsIgnoreStart
42	public function tearDown()
43	// @codingStandardsIgnoreEnd
44	{
45		date_default_timezone_set($this->_original_timezone);
46		setlocale(LC_ALL, $this->default_locale);
47
48		parent::tearDown();
49	}
50
51	/**
52	 * Provides test data for test_offset()
53	 *
54	 * @return array
55	 */
56	public function provider_offset()
57	{
58		return array(
59			array(30600, 'Asia/Calcutta', 'America/Argentina/Buenos_Aires'),
60		);
61	}
62
63	/**
64	 * Tests Date::offset()
65	 *
66	 * @test
67	 * @dataProvider provider_offset
68	 * @covers Date::offset
69	 * @param integer $expected Expected offset
70	 * @param string  $remote   Remote TZ
71	 * @param string  $local    Local TZ
72	 * @param integer $now      Current timestamp
73	 */
74	public function test_offset($expected, $remote, $local, $now = NULL)
75	{
76		$this->assertSame($expected, Date::offset($remote, $local, $now));
77	}
78
79	/**
80	 * Provides test data for test_date()
81	 *
82	 * @return array
83	 */
84	public function provider_am_pm()
85	{
86		return array(
87			// All possible values
88			array(0, 'AM'),
89			array(1, 'AM'),
90			array(2, 'AM'),
91			array(3, 'AM'),
92			array(4, 'AM'),
93			array(5, 'AM'),
94			array(6, 'AM'),
95			array(7, 'AM'),
96			array(8, 'AM'),
97			array(9, 'AM'),
98			array(10, 'AM'),
99			array(11, 'AM'),
100			array(12, 'PM'),
101			array(13, 'PM'),
102			array(14, 'PM'),
103			array(15, 'PM'),
104			array(16, 'PM'),
105			array(17, 'PM'),
106			array(18, 'PM'),
107			array(19, 'PM'),
108			array(20, 'PM'),
109			array(21, 'PM'),
110			array(22, 'PM'),
111			array(23, 'PM'),
112			array(24, 'PM'),
113			// ampm doesn't validate the hour, so I don't think we should test it..
114			// test strings are converted
115			array('0', 'AM'),
116			array('12', 'PM'),
117		);
118	}
119
120	/**
121	 * Tests Date::ampm()
122	 *
123	 * @test
124	 * @covers Date::ampm
125	 * @dataProvider provider_am_pm
126	 * @param <type> $hour
127	 * @param <type> $expected
128	 */
129	public function test_am_pm($hour, $expected)
130	{
131		$this->assertSame(
132			$expected,
133			Date::ampm($hour)
134		);
135	}
136
137	/**
138	 * Provides test data for test_adjust()
139	 *
140	 * @return array
141	 */
142	public function provider_adjust()
143	{
144		return array(
145			// Might as well test all possibilities
146			array(1,  'am', '01'),
147			array(2,  'am', '02'),
148			array(3,  'am', '03'),
149			array(4,  'am', '04'),
150			array(5,  'am', '05'),
151			array(6,  'am', '06'),
152			array(7,  'am', '07'),
153			array(8,  'am', '08'),
154			array(9,  'am', '09'),
155			array(10, 'am', '10'),
156			array(11, 'am', '11'),
157			array(12, 'am', '00'),
158			array(1,  'pm', '13'),
159			array(2,  'pm', '14'),
160			array(3,  'pm', '15'),
161			array(4,  'pm', '16'),
162			array(5,  'pm', '17'),
163			array(6,  'pm', '18'),
164			array(7,  'pm', '19'),
165			array(8,  'pm', '20'),
166			array(9,  'pm', '21'),
167			array(10, 'pm', '22'),
168			array(11, 'pm', '23'),
169			array(12, 'pm', '12'),
170			// It should also work with strings instead of ints
171			array('10', 'pm', '22'),
172			array('10', 'am', '10'),
173		);
174	}
175
176	/**
177	 * Tests Date::ampm()
178	 *
179	 * @test
180	 * @dataProvider provider_adjust
181	 * @param integer $hour       Hour in 12 hour format
182	 * @param string  $ampm       Either am or pm
183	 * @param string  $expected   Expected result
184	 */
185	public function test_adjust($hour, $ampm, $expected)
186	{
187		$this->assertSame(
188			$expected,
189			Date::adjust($hour, $ampm)
190		);
191	}
192
193	/**
194	 * Provides test data for test_days()
195	 *
196	 * @return array
197	 */
198	public function provider_days()
199	{
200		return array(
201			// According to "the rhyme" these should be the same every year
202			array(9, FALSE, 30),
203			array(4, FALSE, 30),
204			array(6, FALSE, 30),
205			array(11, FALSE, 30),
206			array(1, FALSE, 31),
207			array(3, FALSE, 31),
208			array(5, FALSE, 31),
209			array(7, FALSE, 31),
210			array(8, FALSE, 31),
211			array(10, FALSE, 31),
212			// February is such a pain
213			array(2, 2001, 28),
214			array(2, 2000, 29),
215			array(2, 2012, 29),
216		);
217	}
218
219	/**
220	 * Tests Date::days()
221	 *
222	 * @test
223	 * @covers Date::days
224	 * @dataProvider provider_days
225	 * @param integer $month
226	 * @param integer $year
227	 * @param integer $expected
228	 */
229	public function test_days($month, $year, $expected)
230	{
231		$days = Date::days($month, $year);
232
233		$this->assertSame(
234			$expected,
235			count($days)
236		);
237
238		// This should be a mirrored array, days => days
239		for ($i = 1; $i <= $expected; ++$i)
240		{
241			$this->assertArrayHasKey($i, $days);
242			// Combining the type check into this saves about 400-500 assertions!
243			$this->assertSame( (string) $i, $days[$i]);
244		}
245	}
246
247	/**
248	 * Provides test data for test_formatted_time()
249	 *
250	 * @return array
251	 */
252	public function provider_formatted_time()
253	{
254		return array(
255			// Test the default format
256			array('2010-04-16 17:00:00', '5:00PM 16th April 2010'),
257			// Now we use our own format
258			// Binary date!
259			array('01/01/2010 01:00', '1AM 1st January 2010', 'd/m/Y H:i'),
260			// Timezones (see #3902)
261			array('2011-04-01 01:23:45 Antarctica/South_Pole', '2011-04-01 01:23:45', 'Y-m-d H:i:s e', 'Antarctica/South_Pole'),
262			array('2011-04-01 01:23:45 Antarctica/South_Pole', '2011-03-31 14:23:45 Europe/Paris', 'Y-m-d H:i:s e', 'Antarctica/South_Pole'),
263			array('2011-04-01 01:23:45 Antarctica/South_Pole', '@1301574225', 'Y-m-d H:i:s e', 'Antarctica/South_Pole'),
264		);
265	}
266
267	/**
268	 * Tests Date::formatted_time()
269	 *
270	 * @test
271	 * @dataProvider provider_formatted_time
272	 * @covers Date::formatted_time
273	 * @ticket 3035 3902
274	 * @param string         $expected         Expected output
275	 * @param string|integer $datetime_str     The datetime timestamp / string
276	 * @param string|null    $timestamp_format The output format
277	 * @param string|null    $timezone         The timezone identifier
278	 */
279	public function test_formatted_time($expected, $datetime_str, $timestamp_format = NULL, $timezone = NULL)
280	{
281		$timestamp = Date::formatted_time($datetime_str, $timestamp_format, $timezone);
282
283		$this->assertSame($expected, $timestamp);
284	}
285
286	/**
287	 * Provider for test_months()
288	 *
289	 * @return array Test data
290	 */
291	public function provider_months()
292	{
293		return array(
294			array(
295				array(
296					1 => "1",
297					2 => "2",
298					3 => "3",
299					4 => "4",
300					5 => "5",
301					6 => "6",
302					7 => "7",
303					8 => "8",
304					9 => "9",
305					10 => "10",
306					11 => "11",
307					12 => "12"
308				),
309				NULL
310			),
311			array(
312				array(
313					1 => "1",
314					2 => "2",
315					3 => "3",
316					4 => "4",
317					5 => "5",
318					6 => "6",
319					7 => "7",
320					8 => "8",
321					9 => "9",
322					10 => "10",
323					11 => "11",
324					12 => "12"
325				),
326				'Guinness'
327			),
328			array(
329				array(
330					1 => "January",
331					2 => "February",
332					3 => "March",
333					4 => "April",
334					5 => "May",
335					6 => "June",
336					7 => "July",
337					8 => "August",
338					9 => "September",
339					10 => "October",
340					11 => "November",
341					12 => "December"
342				),
343				Date::MONTHS_LONG
344			),
345			array(
346				array(
347					1 => "Jan",
348					2 => "Feb",
349					3 => "Mar",
350					4 => "Apr",
351					5 => "May",
352					6 => "Jun",
353					7 => "Jul",
354					8 => "Aug",
355					9 => "Sep",
356					10 => "Oct",
357					11 => "Nov",
358					12 => "Dec"
359				),
360				Date::MONTHS_SHORT
361			)
362
363		);
364	}
365
366	/**
367	 * Date::months() should allow the user to specify different format types, defaulting
368	 * to a mirrored month number => month number array if format is NULL or unrecognised
369	 *
370	 * @test
371	 * @dataProvider provider_months
372	 * @covers Date::months
373	 */
374	public function test_months($expected, $format)
375	{
376		$months = Date::months($format);
377
378		$this->assertSame($expected, $months);
379	}
380
381	/**
382	 * Provides test data for test_span()
383	 *
384	 * @return array
385	 */
386	public function provider_span()
387	{
388		$time = time();
389		return array(
390			// Test that it must specify an output format
391			array(
392				$time,
393				$time,
394				'',
395				FALSE
396			),
397			// Test that providing only one output just returns that output
398			array(
399				$time - 30,
400				$time,
401				'seconds',
402				30
403			),
404			// Random tests
405			array(
406				$time - 30,
407				$time,
408				'years,months,weeks,days,hours,minutes,seconds',
409				array('years' => 0, 'months' => 0, 'weeks' => 0, 'days' => 0, 'hours' => 0, 'minutes' => 0, 'seconds' => 30),
410			),
411			array(
412				$time - (60 * 60 * 24 * 782) + (60 * 25),
413				$time,
414				'years,months,weeks,days,hours,minutes,seconds',
415				array('years' => 2, 'months' => 1, 'weeks' => 3, 'days' => 0, 'hours' => 1, 'minutes' => 28, 'seconds' => 24),
416			),
417			// Should be able to compare with the future & that it only uses formats specified
418			array(
419				$time + (60 * 60 * 24 * 15) + (60 * 5),
420				$time,
421				'weeks,days,hours,minutes,seconds',
422				array('weeks' => 2, 'days' => 1, 'hours' => 0, 'minutes' => 5, 'seconds' => 0),
423			),
424			array(
425				// Add a bit of extra time to account for phpunit processing
426				$time + (14 * 31 * 24* 60 * 60) + (79 * 80),
427				NULL,
428				'months,years',
429				array('months' => 2, 'years' => 1),
430			),
431		);
432	}
433
434	/**
435	 * Tests Date::span()
436	 *
437	 * @test
438	 * @covers Date::span
439	 * @dataProvider provider_span
440	 * @param integer $time1     Time in the past
441	 * @param integer $time2     Time to compare against
442	 * @param string  $output    Units to output
443	 * @param array   $expected  Array of $outputs => values
444	 */
445	public function test_span($time1, $time2, $output, $expected)
446	{
447		$this->assertSame(
448			$expected,
449			Date::span($time1, $time2, $output)
450		);
451	}
452
453	/**
454	 * Provides test data to test_fuzzy_span
455	 *
456	 * This test data is provided on the assumption that it
457	 * won't take phpunit more than 30 seconds to get the
458	 * data from this provider to the test... ;)
459	 *
460	 * @return array Test Data
461	 */
462	public function provider_fuzzy_span()
463	{
464		$now = time();
465
466		return array(
467			array('moments ago', $now - 30, $now),
468			array('in moments', $now + 30, $now),
469
470			array('a few minutes ago', $now - 10*60, $now),
471			array('in a few minutes', $now + 10*60, $now),
472
473			array('less than an hour ago', $now - 45*60, $now),
474			array('in less than an hour', $now + 45*60, $now),
475
476			array('a couple of hours ago', $now - 2*60*60, $now),
477			array('in a couple of hours', $now + 2*60*60, $now),
478
479			array('less than a day ago', $now - 12*60*60, $now),
480			array('in less than a day', $now + 12*60*60, $now),
481
482			array('about a day ago', $now - 30*60*60, $now),
483			array('in about a day', $now + 30*60*60, $now),
484
485			array('a couple of days ago', $now - 3*24*60*60, $now),
486			array('in a couple of days', $now + 3*24*60*60, $now),
487
488			array('less than a week ago', $now - 5*24*60*60, $now),
489			array('in less than a week', $now + 5*24*60*60, $now),
490
491			array('about a week ago', $now - 9*24*60*60, $now),
492			array('in about a week', $now + 9*24*60*60, $now),
493
494			array('less than a month ago', $now - 20*24*60*60, $now),
495			array('in less than a month', $now + 20*24*60*60, $now),
496
497			array('about a month ago', $now - 40*24*60*60, $now),
498			array('in about a month', $now + 40*24*60*60, $now),
499
500			array('a couple of months ago', $now - 3*30*24*60*60, $now),
501			array('in a couple of months', $now + 3*30*24*60*60, $now),
502
503			array('less than a year ago', $now - 7*31*24*60*60, $now),
504			array('in less than a year', $now + 7*31*24*60*60, $now),
505
506			array('about a year ago', $now - 18*31*24*60*60, $now),
507			array('in about a year', $now + 18*31*24*60*60, $now),
508
509			array('a couple of years ago', $now - 3*12*31*24*60*60, $now),
510			array('in a couple of years', $now + 3*12*31*24*60*60, $now),
511
512			array('a few years ago', $now - 5*12*31*24*60*60, $now),
513			array('in a few years', $now + 5*12*31*24*60*60, $now),
514
515			array('about a decade ago', $now - 11*12*31*24*60*60, $now),
516			array('in about a decade', $now + 11*12*31*24*60*60, $now),
517
518			array('a couple of decades ago', $now - 20*12*31*24*60*60, $now),
519			array('in a couple of decades', $now + 20*12*31*24*60*60, $now),
520
521			array('several decades ago', $now - 50*12*31*24*60*60, $now),
522			array('in several decades', $now + 50*12*31*24*60*60, $now),
523
524			array('a long time ago', $now - pow(10,10), $now),
525			array('in a long time', $now + pow(10,10), $now),
526		);
527	}
528
529	/**
530	 * Test of Date::fuzy_span()
531	 *
532	 * @test
533	 * @dataProvider provider_fuzzy_span
534	 * @param string  $expected        Expected output
535	 * @param integer $timestamp       Timestamp to use
536	 * @param integer $local_timestamp The local timestamp to use
537	 */
538	public function test_fuzzy_span($expected, $timestamp, $local_timestamp)
539	{
540		$this->assertSame(
541			$expected,
542			Date::fuzzy_span($timestamp, $local_timestamp)
543		);
544	}
545
546	/**
547	 * Provides test data for test_years()
548	 *
549	 * @return array Test Data
550	 */
551	public function provider_years()
552	{
553		return array(
554			array(
555				array (
556					2005 => '2005',
557					2006 => '2006',
558					2007 => '2007',
559				    2008 => '2008',
560				    2009 => '2009',
561				    2010 => '2010',
562				    2011 => '2011',
563				    2012 => '2012',
564					2013 => '2013',
565					2014 => '2014',
566					2015 => '2015',
567				),
568				2005,
569				2015
570			),
571		);
572	}
573
574	/**
575	 * Tests Data::years()
576	 *
577	 * @test
578	 * @dataProvider provider_years
579	 */
580	public function test_years($expected, $start = FALSE, $end = FALSE)
581	{
582		$this->assertSame(
583			$expected,
584			Date::years($start, $end)
585		);
586	}
587
588	public function provider_hours()
589	{
590		return array(
591			array(
592				array(
593					1 => '1',
594					2 => '2',
595					3 => '3',
596					4 => '4',
597					5 => '5',
598					6 => '6',
599					7 => '7',
600					8 => '8',
601					9 => '9',
602					10 => '10',
603					11 => '11',
604					12 => '12',
605				),
606			),
607		);
608	}
609
610	/**
611	 * Test for Date::hours
612	 *
613	 * @test
614	 * @dataProvider provider_hours
615	 */
616	public function test_hours($expected, $step = 1, $long = FALSE, $start = NULL)
617	{
618		$this->assertSame(
619			$expected,
620			Date::hours($step, $long, $start)
621		);
622	}
623
624	/**
625	 * Provides test data for test_seconds
626	 *
627	 * @return array Test data
628	 */
629	public function provider_seconds()
630	{
631		return array(
632			array(
633				// Thank god for var_export()
634				array (
635					0 => '00', 1 => '01', 2 => '02', 3 => '03', 4 => '04',
636					5 => '05', 6 => '06', 7 => '07', 8 => '08', 9 => '09',
637					10 => '10', 11 => '11', 12 => '12', 13 => '13', 14 => '14',
638					15 => '15', 16 => '16', 17 => '17', 18 => '18', 19 => '19',
639					20 => '20', 21 => '21', 22 => '22', 23 => '23', 24 => '24',
640					25 => '25', 26 => '26', 27 => '27', 28 => '28', 29 => '29',
641					30 => '30', 31 => '31', 32 => '32', 33 => '33', 34 => '34',
642					35 => '35', 36 => '36', 37 => '37', 38 => '38', 39 => '39',
643					40 => '40', 41 => '41', 42 => '42', 43 => '43', 44 => '44',
644					45 => '45', 46 => '46', 47 => '47', 48 => '48', 49 => '49',
645					50 => '50', 51 => '51', 52 => '52', 53 => '53', 54 => '54',
646					55 => '55', 56 => '56', 57 => '57', 58 => '58', 59 => '59',
647				),
648				1,
649				0,
650				60
651			),
652		);
653	}
654
655	/**
656	 *
657	 * @test
658	 * @dataProvider provider_seconds
659	 * @covers Date::seconds
660	 */
661	public function test_seconds($expected, $step = 1, $start = 0, $end = 60)
662	{
663		$this->assertSame(
664			$expected,
665			Date::seconds($step, $start, $end)
666		);
667	}
668
669	/**
670	 * Provides test data for test_minutes
671	 *
672	 * @return array Test data
673	 */
674	public function provider_minutes()
675	{
676		return array(
677			array(
678				array(
679					0 => '00', 5 => '05', 10 => '10',
680					15 => '15', 20 => '20', 25 => '25',
681					30 => '30', 35 => '35', 40 => '40',
682					45 => '45', 50 => '50', 55 => '55',
683				),
684				5,
685			),
686		);
687	}
688
689	/**
690	 *
691	 * @test
692	 * @dataProvider provider_minutes
693	 */
694	public function test_minutes($expected, $step)
695	{
696		$this->assertSame(
697			$expected,
698			Date::minutes($step)
699		);
700	}
701
702	/**
703	 * This tests that the minutes helper defaults to using a $step of 5
704	 * and thus returns an array of 5 minute itervals
705	 *
706	 * @test
707	 * @covers Date::minutes
708	 */
709	public function test_minutes_defaults_to_using_step_of5()
710	{
711		$minutes = array(
712			0 => '00', 5 => '05', 10 => '10',
713			15 => '15', 20 => '20', 25 => '25',
714			30 => '30', 35 => '35', 40 => '40',
715			45 => '45', 50 => '50', 55 => '55',
716		);
717
718		$this->assertSame(
719			$minutes,
720			Date::minutes()
721		);
722	}
723
724	/**
725	 * Provids for test_unix2dos
726	 *
727	 * @return array Test Data
728	 */
729	public function provider_unix2dos()
730	{
731		return array(
732			array(
733				1024341746,
734				1281786936
735			),
736			array(
737				2162688,
738				315554400
739			)
740		);
741	}
742
743	/**
744	 * Test Date::unix2dos()
745	 *
746	 * You should always pass a timestamp as otherwise the current
747	 * date/time would be used and that's oviously variable
748	 *
749	 * Geert seems to be the only person who knows how unix2dos() works
750	 * so we just throw in some random values and see what happens
751	 *
752	 * @test
753	 * @dataProvider provider_unix2dos
754	 * @covers Date::unix2dos
755	 * @param integer $expected  Expected output
756	 * @param integer $timestamp Input timestamp
757	 */
758	public function test_unix2dos($expected, $timestamp)
759	{
760		$this->assertSame($expected, Date::unix2dos($timestamp));
761	}
762
763	/**
764	 * Provides test data for test_dos2unix
765	 *
766	 * @return array Test data
767	 */
768	public function provider_dos2unix()
769	{
770		return array(
771			array(
772				1281786936,
773				1024341746,
774			),
775			array(
776				315554400,
777				2162688,
778			),
779		);
780	}
781
782	/**
783	 * Tests Date::dos2unix
784	 *
785	 * @test
786	 * @dataProvider provider_dos2unix
787	 * @param integer $expected  Expected output
788	 * @param integer $timestamp Input timestamp
789	 */
790	public function test_dos2unix($expected, $timestamp)
791	{
792		$this->assertEquals($expected, Date::dos2unix($timestamp));
793	}
794}
795