b755be8325ae7c77fb5457c9fc4318339dfa69c3
[yaffs-website] / Drupal / Tests / Component / Datetime / DateTimePlusTest.php
1 <?php
2
3 namespace Drupal\Tests\Component\Datetime;
4
5 use Drupal\Component\Datetime\DateTimePlus;
6 use PHPUnit\Framework\TestCase;
7
8 /**
9  * @coversDefaultClass \Drupal\Component\Datetime\DateTimePlus
10  * @group Datetime
11  */
12 class DateTimePlusTest extends TestCase {
13
14   /**
15    * Test creating dates from string and array input.
16    *
17    * @param mixed $input
18    *   Input argument for DateTimePlus.
19    * @param string $timezone
20    *   Timezone argument for DateTimePlus.
21    * @param string $expected
22    *   Expected output from DateTimePlus::format().
23    *
24    * @dataProvider providerTestDates
25    */
26   public function testDates($input, $timezone, $expected) {
27     $date = new DateTimePlus($input, $timezone);
28     $value = $date->format('c');
29
30     if (is_array($input)) {
31       $input = var_export($input, TRUE);
32     }
33     $this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value));
34   }
35
36   /**
37    * Test creating dates from string and array input.
38    *
39    * @param mixed $input
40    *   Input argument for DateTimePlus.
41    * @param string $timezone
42    *   Timezone argument for DateTimePlus.
43    * @param string $expected
44    *   Expected output from DateTimePlus::format().
45    *
46    * @dataProvider providerTestDateArrays
47    */
48   public function testDateArrays($input, $timezone, $expected) {
49     $date = DateTimePlus::createFromArray($input, $timezone);
50     $value = $date->format('c');
51
52     if (is_array($input)) {
53       $input = var_export($input, TRUE);
54     }
55     $this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value));
56   }
57
58   /**
59    * Test date diffs.
60    *
61    * @param mixed $input1
62    *   A DateTimePlus object.
63    * @param mixed $input2
64    *   Date argument for DateTimePlus::diff method.
65    * @param bool $absolute
66    *   Absolute flag for DateTimePlus::diff method.
67    * @param \DateInterval $expected
68    *   The expected result of the DateTimePlus::diff operation.
69    *
70    * @dataProvider providerTestDateDiff
71    */
72   public function testDateDiff($input1, $input2, $absolute, \DateInterval $expected) {
73     $interval = $input1->diff($input2, $absolute);
74     $this->assertEquals($interval, $expected);
75   }
76
77   /**
78    * Test date diff exception caused by invalid input.
79    *
80    * @param mixed $input1
81    *   A DateTimePlus object.
82    * @param mixed $input2
83    *   Date argument for DateTimePlus::diff method.
84    * @param bool $absolute
85    *   Absolute flag for DateTimePlus::diff method.
86    *
87    * @dataProvider providerTestInvalidDateDiff
88    */
89   public function testInvalidDateDiff($input1, $input2, $absolute) {
90     $this->setExpectedException(\BadMethodCallException::class, 'Method Drupal\Component\Datetime\DateTimePlus::diff expects parameter 1 to be a \DateTime or \Drupal\Component\Datetime\DateTimePlus object');
91     $interval = $input1->diff($input2, $absolute);
92   }
93
94   /**
95    * Test creating dates from invalid array input.
96    *
97    * @param mixed $input
98    *   Input argument for DateTimePlus.
99    * @param string $timezone
100    *   Timezone argument for DateTimePlus.
101    * @param string $class
102    *   The Exception subclass to expect to be thrown.
103    *
104    * @dataProvider providerTestInvalidDateArrays
105    */
106   public function testInvalidDateArrays($input, $timezone, $class) {
107     $this->setExpectedException($class);
108     $this->assertInstanceOf(
109       '\Drupal\Component\DateTimePlus',
110       DateTimePlus::createFromArray($input, $timezone)
111     );
112   }
113
114   /**
115    * Test creating dates from timestamps, and manipulating timezones.
116    *
117    * @param int $input
118    *   Input argument for DateTimePlus::createFromTimestamp().
119    * @param array $initial
120    *   An array containing:
121    *   - 'timezone_initial' - Timezone argument for DateTimePlus.
122    *   - 'format_initial' - Format argument for DateTimePlus.
123    *   - 'expected_initial_date' - Expected output from DateTimePlus::format().
124    *   - 'expected_initial_timezone' - Expected output from
125    *      DateTimePlus::getTimeZone()::getName().
126    *   - 'expected_initial_offset' - Expected output from DateTimePlus::getOffset().
127    * @param array $transform
128    *   An array containing:
129    *   - 'timezone_transform' - Argument to transform date to another timezone via
130    *     DateTimePlus::setTimezone().
131    *   - 'format_transform' - Format argument to use when transforming date to
132    *     another timezone.
133    *   - 'expected_transform_date' - Expected output from DateTimePlus::format(),
134    *     after timezone transform.
135    *   - 'expected_transform_timezone' - Expected output from
136    *     DateTimePlus::getTimeZone()::getName(), after timezone transform.
137    *   - 'expected_transform_offset' - Expected output from
138    *      DateTimePlus::getOffset(), after timezone transform.
139    *
140    * @dataProvider providerTestTimestamp
141    */
142   public function testTimestamp($input, array $initial, array $transform) {
143     // Initialize a new date object.
144     $date = DateTimePlus::createFromTimestamp($input, $initial['timezone']);
145     $this->assertDateTimestamp($date, $input, $initial, $transform);
146   }
147
148   /**
149    * Test creating dates from datetime strings.
150    *
151    * @param string $input
152    *   Input argument for DateTimePlus().
153    * @param array $initial
154    *   @see testTimestamp()
155    * @param array $transform
156    *   @see testTimestamp()
157    *
158    * @dataProvider providerTestDateTimestamp
159    */
160   public function testDateTimestamp($input, array $initial, array $transform) {
161     // Initialize a new date object.
162     $date = new DateTimePlus($input, $initial['timezone']);
163     $this->assertDateTimestamp($date, $input, $initial, $transform);
164   }
165
166   /**
167    * Assertion helper for testTimestamp and testDateTimestamp since they need
168    * different dataProviders.
169    *
170    * @param \Drupal\Component\Datetime\DateTimePlus $date
171    *   DateTimePlus to test.
172    * @input mixed $input
173    *   The original input passed to the test method.
174    * @param array $initial
175    *   @see testTimestamp()
176    * @param array $transform
177    *   @see testTimestamp()
178    */
179   public function assertDateTimestamp($date, $input, $initial, $transform) {
180     // Check format.
181     $value = $date->format($initial['format']);
182     $this->assertEquals($initial['expected_date'], $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $initial['timezone'], $initial['expected_date'], $value));
183
184     // Check timezone name.
185     $value = $date->getTimeZone()->getName();
186     $this->assertEquals($initial['expected_timezone'], $value, sprintf("The current timezone is %s: should be %s.", $value, $initial['expected_timezone']));
187
188     // Check offset.
189     $value = $date->getOffset();
190     $this->assertEquals($initial['expected_offset'], $value, sprintf("The current offset is %s: should be %s.", $value, $initial['expected_offset']));
191
192     // Transform the date to another timezone.
193     $date->setTimezone(new \DateTimeZone($transform['timezone']));
194
195     // Check transformed format.
196     $value = $date->format($transform['format']);
197     $this->assertEquals($transform['expected_date'], $value, sprintf("Test \$date->setTimezone(new \\DateTimeZone(%s)): should be %s, found %s.", $transform['timezone'], $transform['expected_date'], $value));
198
199     // Check transformed timezone.
200     $value = $date->getTimeZone()->getName();
201     $this->assertEquals($transform['expected_timezone'], $value, sprintf("The current timezone should be %s, found %s.", $transform['expected_timezone'], $value));
202
203     // Check transformed offset.
204     $value = $date->getOffset();
205     $this->assertEquals($transform['expected_offset'], $value, sprintf("The current offset should be %s, found %s.", $transform['expected_offset'], $value));
206   }
207
208   /**
209    * Test creating dates from format strings.
210    *
211    * @param string $input
212    *   Input argument for DateTimePlus.
213    * @param string $timezone
214    *   Timezone argument for DateTimePlus.
215    * @param string $format_date
216    *   Format argument for DateTimePlus::format().
217    * @param string $expected
218    *   Expected output from DateTimePlus::format().
219    *
220    * @dataProvider providerTestDateFormat
221    */
222   public function testDateFormat($input, $timezone, $format, $format_date, $expected) {
223     $date = DateTimePlus::createFromFormat($format, $input, $timezone);
224     $value = $date->format($format_date);
225     $this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s, %s): should be %s, found %s.", $input, $timezone, $format, $expected, $value));
226   }
227
228   /**
229    * Test invalid date handling.
230    *
231    * @param mixed $input
232    *   Input argument for DateTimePlus.
233    * @param string $timezone
234    *   Timezone argument for DateTimePlus.
235    * @param string $format
236    *   Format argument for DateTimePlus.
237    * @param string $message
238    *   Message to print if no errors are thrown by the invalid dates.
239    * @param string $class
240    *   The Exception subclass to expect to be thrown.
241    *
242    * @dataProvider providerTestInvalidDates
243    */
244   public function testInvalidDates($input, $timezone, $format, $message, $class) {
245     $this->setExpectedException($class);
246     DateTimePlus::createFromFormat($format, $input, $timezone);
247   }
248
249   /**
250    * Tests that DrupalDateTime can detect the right timezone to use.
251    * When specified or not.
252    *
253    * @param mixed $input
254    *   Input argument for DateTimePlus.
255    * @param mixed $timezone
256    *   Timezone argument for DateTimePlus.
257    * @param string $expected_timezone
258    *   Expected timezone returned from DateTimePlus::getTimezone::getName().
259    * @param string $message
260    *   Message to print on test failure.
261    *
262    * @dataProvider providerTestDateTimezone
263    */
264   public function testDateTimezone($input, $timezone, $expected_timezone, $message) {
265     $date = new DateTimePlus($input, $timezone);
266     $timezone = $date->getTimezone()->getName();
267     $this->assertEquals($timezone, $expected_timezone, $message);
268   }
269
270   /**
271    * Test that DrupalDateTime can detect the right timezone to use when
272    * constructed from a datetime object.
273    */
274   public function testDateTimezoneWithDateTimeObject() {
275     // Create a date object with another date object.
276     $input = new \DateTime('now', new \DateTimeZone('Pacific/Midway'));
277     $timezone = NULL;
278     $expected_timezone = 'Pacific/Midway';
279     $message = 'DateTimePlus uses the specified timezone if provided.';
280
281     $date = DateTimePlus::createFromDateTime($input, $timezone);
282     $timezone = $date->getTimezone()->getName();
283     $this->assertEquals($timezone, $expected_timezone, $message);
284   }
285
286   /**
287    * Provides data for date tests.
288    *
289    * @return array
290    *   An array of arrays, each containing the input parameters for
291    *   DateTimePlusTest::testDates().
292    *
293    * @see DateTimePlusTest::testDates()
294    */
295   public function providerTestDates() {
296     $dates = [
297       // String input.
298       // Create date object from datetime string.
299       ['2009-03-07 10:30', 'America/Chicago', '2009-03-07T10:30:00-06:00'],
300       // Same during daylight savings time.
301       ['2009-06-07 10:30', 'America/Chicago', '2009-06-07T10:30:00-05:00'],
302       // Create date object from date string.
303       ['2009-03-07', 'America/Chicago', '2009-03-07T00:00:00-06:00'],
304       // Same during daylight savings time.
305       ['2009-06-07', 'America/Chicago', '2009-06-07T00:00:00-05:00'],
306       // Create date object from date string.
307       ['2009-03-07 10:30', 'Australia/Canberra', '2009-03-07T10:30:00+11:00'],
308       // Same during daylight savings time.
309       ['2009-06-07 10:30', 'Australia/Canberra', '2009-06-07T10:30:00+10:00'],
310     ];
311
312     // On 32-bit systems, timestamps are limited to 1901-2038.
313     if (PHP_INT_SIZE > 4) {
314       // Create a date object in the distant past.
315       // @see https://www.drupal.org/node/2795489#comment-12127088
316       if (version_compare(PHP_VERSION, '5.6.15', '>=')) {
317         $dates[] = ['1809-02-12 10:30', 'America/Chicago', '1809-02-12T10:30:00-06:00'];
318       }
319       // Create a date object in the far future.
320       $dates[] = ['2345-01-02 02:04', 'UTC', '2345-01-02T02:04:00+00:00'];
321     }
322
323     return $dates;
324   }
325
326   /**
327    * Provides data for date tests.
328    *
329    * @return array
330    *   An array of arrays, each containing the input parameters for
331    *   DateTimePlusTest::testDates().
332    *
333    * @see DateTimePlusTest::testDates()
334    */
335   public function providerTestDateArrays() {
336     $dates = [
337       // Array input.
338       // Create date object from date array, date only.
339       [['year' => 2010, 'month' => 2, 'day' => 28], 'America/Chicago', '2010-02-28T00:00:00-06:00'],
340       // Create date object from date array with hour.
341       [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'America/Chicago', '2010-02-28T10:00:00-06:00'],
342       // Create date object from date array, date only.
343       [['year' => 2010, 'month' => 2, 'day' => 28], 'Europe/Berlin', '2010-02-28T00:00:00+01:00'],
344       // Create date object from date array with hour.
345       [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'Europe/Berlin', '2010-02-28T10:00:00+01:00'],
346     ];
347
348     // On 32-bit systems, timestamps are limited to 1901-2038.
349     if (PHP_INT_SIZE > 4) {
350       // Create a date object in the distant past.
351       // @see https://www.drupal.org/node/2795489#comment-12127088
352       if (version_compare(PHP_VERSION, '5.6.15', '>=')) {
353         $dates[] = [['year' => 1809, 'month' => 2, 'day' => 12], 'America/Chicago', '1809-02-12T00:00:00-06:00'];
354       }
355       // Create a date object in the far future.
356       $dates[] = [['year' => 2345, 'month' => 1, 'day' => 2], 'UTC', '2345-01-02T00:00:00+00:00'];
357     }
358
359     return $dates;
360   }
361
362   /**
363    * Provides data for testDateFormats.
364    *
365    * @return array
366    *   An array of arrays, each containing:
367    *   - 'input' - Input to DateTimePlus.
368    *   - 'timezone' - Timezone for DateTimePlus.
369    *   - 'format' - Date format for DateTimePlus.
370    *   - 'format_date' - Date format for use in $date->format() method.
371    *   - 'expected' - The expected return from DateTimePlus.
372    *
373    * @see testDateFormats()
374    */
375   public function providerTestDateFormat() {
376     return [
377       // Create a year-only date.
378       ['2009', NULL, 'Y', 'Y', '2009'],
379       // Create a month and year-only date.
380       ['2009-10', NULL, 'Y-m', 'Y-m', '2009-10'],
381       // Create a time-only date.
382       ['T10:30:00', NULL, '\TH:i:s', 'H:i:s', '10:30:00'],
383       // Create a time-only date.
384       ['10:30:00', NULL, 'H:i:s', 'H:i:s', '10:30:00'],
385     ];
386   }
387
388   /**
389    * Provides data for testInvalidDates.
390    *
391    * @return array
392    *   An array of arrays, each containing:
393    *   - 'input' - Input for DateTimePlus.
394    *   - 'timezone' - Timezone for DateTimePlus.
395    *   - 'format' - Format for DateTimePlus.
396    *   - 'message' - Message to display on failure.
397    *
398    * @see testInvalidDates
399    */
400   public function providerTestInvalidDates() {
401     return [
402       // Test for invalid month names when we are using a short version
403       // of the month.
404       ['23 abc 2012', NULL, 'd M Y', "23 abc 2012 contains an invalid month name and did not produce errors.", \InvalidArgumentException::class],
405       // Test for invalid hour.
406       ['0000-00-00T45:30:00', NULL, 'Y-m-d\TH:i:s', "0000-00-00T45:30:00 contains an invalid hour and did not produce errors.", \UnexpectedValueException::class],
407       // Test for invalid day.
408       ['0000-00-99T05:30:00', NULL, 'Y-m-d\TH:i:s', "0000-00-99T05:30:00 contains an invalid day and did not produce errors.", \UnexpectedValueException::class],
409       // Test for invalid month.
410       ['0000-75-00T15:30:00', NULL, 'Y-m-d\TH:i:s', "0000-75-00T15:30:00 contains an invalid month and did not produce errors.", \UnexpectedValueException::class],
411       // Test for invalid year.
412       ['11-08-01T15:30:00', NULL, 'Y-m-d\TH:i:s', "11-08-01T15:30:00 contains an invalid year and did not produce errors.", \UnexpectedValueException::class],
413
414     ];
415   }
416
417   /**
418    * Data provider for testInvalidDateArrays.
419    *
420    * @return array
421    *   An array of arrays, each containing:
422    *   - 'input' - Input for DateTimePlus.
423    *   - 'timezone' - Timezone for DateTimePlus.
424    *
425    * @see testInvalidDateArrays
426    */
427   public function providerTestInvalidDateArrays() {
428     return [
429       // One year larger than the documented upper limit of checkdate().
430       [['year' => 32768, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
431       // One year smaller than the documented lower limit of checkdate().
432       [['year' => 0, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
433       // Test for invalid month from date array.
434       [['year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
435       // Test for invalid hour from date array.
436       [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
437       // Test for invalid minute from date array.
438       [['year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
439       // Regression test for https://www.drupal.org/node/2084455.
440       [['hour' => 59, 'minute' => 1, 'second' => 1], 'America/Chicago', \InvalidArgumentException::class],
441     ];
442   }
443
444   /**
445    * Provides data for testDateTimezone.
446    *
447    * @return array
448    *   An array of arrays, each containing:
449    *   - 'date' - Date string or object for DateTimePlus.
450    *   - 'timezone' - Timezone string for DateTimePlus.
451    *   - 'expected' - Expected return from DateTimePlus::getTimezone()::getName().
452    *   - 'message' - Message to display on test failure.
453    *
454    * @see testDateTimezone
455    */
456   public function providerTestDateTimezone() {
457     // Use a common date for most of the tests.
458     $date_string = '2007-01-31 21:00:00';
459
460     // Detect the system timezone.
461     $system_timezone = date_default_timezone_get();
462
463     return [
464       // Create a date object with an unspecified timezone, which should
465       // end up using the system timezone.
466       [$date_string, NULL, $system_timezone, 'DateTimePlus uses the system timezone when there is no site timezone.'],
467       // Create a date object with a specified timezone name.
468       [$date_string, 'America/Yellowknife', 'America/Yellowknife', 'DateTimePlus uses the specified timezone if provided.'],
469       // Create a date object with a timezone object.
470       [$date_string, new \DateTimeZone('Australia/Canberra'), 'Australia/Canberra', 'DateTimePlus uses the specified timezone if provided.'],
471       // Create a date object with another date object.
472       [new DateTimePlus('now', 'Pacific/Midway'), NULL, 'Pacific/Midway', 'DateTimePlus uses the specified timezone if provided.'],
473     ];
474   }
475
476   /**
477    * Provides data for testTimestamp.
478    *
479    * @return array
480    *   An array of arrays, each containing the arguments required for
481    *   self::testTimestamp().
482    *
483    * @see testTimestamp()
484    */
485   public function providerTestTimestamp() {
486     return [
487       // Create date object from a unix timestamp and display it in
488       // local time.
489       [
490         'input' => 0,
491         'initial' => [
492           'timezone' => 'UTC',
493           'format' => 'c',
494           'expected_date' => '1970-01-01T00:00:00+00:00',
495           'expected_timezone' => 'UTC',
496           'expected_offset' => 0,
497         ],
498         'transform' => [
499           'timezone' => 'America/Los_Angeles',
500           'format' => 'c',
501           'expected_date' => '1969-12-31T16:00:00-08:00',
502           'expected_timezone' => 'America/Los_Angeles',
503           'expected_offset' => '-28800',
504         ],
505       ],
506       // Create a date using the timestamp of zero, then display its
507       // value both in UTC and the local timezone.
508       [
509         'input' => 0,
510         'initial' => [
511           'timezone' => 'America/Los_Angeles',
512           'format' => 'c',
513           'expected_date' => '1969-12-31T16:00:00-08:00',
514           'expected_timezone' => 'America/Los_Angeles',
515           'expected_offset' => '-28800',
516         ],
517         'transform' => [
518           'timezone' => 'UTC',
519           'format' => 'c',
520           'expected_date' => '1970-01-01T00:00:00+00:00',
521           'expected_timezone' => 'UTC',
522           'expected_offset' => 0,
523         ],
524       ],
525     ];
526   }
527
528   /**
529    * Provides data for testDateTimestamp.
530    *
531    * @return array
532    *   An array of arrays, each containing the arguments required for
533    *   self::testDateTimestamp().
534    *
535    * @see testDateTimestamp()
536    */
537   public function providerTestDateTimestamp() {
538     return [
539       // Create date object from datetime string in UTC, and convert
540       // it to a local date.
541       [
542         'input' => '1970-01-01 00:00:00',
543         'initial' => [
544           'timezone' => 'UTC',
545           'format' => 'c',
546           'expected_date' => '1970-01-01T00:00:00+00:00',
547           'expected_timezone' => 'UTC',
548           'expected_offset' => 0,
549         ],
550         'transform' => [
551           'timezone' => 'America/Los_Angeles',
552           'format' => 'c',
553           'expected_date' => '1969-12-31T16:00:00-08:00',
554           'expected_timezone' => 'America/Los_Angeles',
555           'expected_offset' => '-28800',
556         ],
557       ],
558       // Convert the local time to UTC using string input.
559       [
560         'input' => '1969-12-31 16:00:00',
561         'initial' => [
562           'timezone' => 'America/Los_Angeles',
563           'format' => 'c',
564           'expected_date' => '1969-12-31T16:00:00-08:00',
565           'expected_timezone' => 'America/Los_Angeles',
566           'expected_offset' => '-28800',
567         ],
568         'transform' => [
569           'timezone' => 'UTC',
570           'format' => 'c',
571           'expected_date' => '1970-01-01T00:00:00+00:00',
572           'expected_timezone' => 'UTC',
573           'expected_offset' => 0,
574         ],
575       ],
576       // Convert the local time to UTC using string input.
577       [
578         'input' => '1969-12-31 16:00:00',
579         'initial' => [
580           'timezone' => 'Europe/Warsaw',
581           'format' => 'c',
582           'expected_date' => '1969-12-31T16:00:00+01:00',
583           'expected_timezone' => 'Europe/Warsaw',
584           'expected_offset' => '+3600',
585         ],
586         'transform' => [
587           'timezone' => 'UTC',
588           'format' => 'c',
589           'expected_date' => '1969-12-31T15:00:00+00:00',
590           'expected_timezone' => 'UTC',
591           'expected_offset' => 0,
592         ],
593       ],
594     ];
595   }
596
597   /**
598    * Provides data for date tests.
599    *
600    * @return array
601    *   An array of arrays, each containing the input parameters for
602    *   DateTimePlusTest::testDateDiff().
603    *
604    * @see DateTimePlusTest::testDateDiff()
605    */
606   public function providerTestDateDiff() {
607
608     $empty_interval = new \DateInterval('PT0S');
609
610     $positive_19_hours = new \DateInterval('PT19H');
611
612     $positive_18_hours = new \DateInterval('PT18H');
613
614     $positive_1_hour = new \DateInterval('PT1H');
615
616     $negative_1_hour = new \DateInterval('PT1H');
617     $negative_1_hour->invert = 1;
618
619     return [
620       // There should be a 19 hour time interval between
621       // new years in Sydney and new years in LA in year 2000.
622       [
623         'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
624         'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
625         'absolute' => FALSE,
626         'expected' => $positive_19_hours,
627       ],
628       // In 1970 Sydney did not observe daylight savings time
629       // So there is only a 18 hour time interval.
630       [
631         'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
632         'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
633         'absolute' => FALSE,
634         'expected' => $positive_18_hours,
635       ],
636       [
637         'input1' => DateTimePlus::createFromFormat('U', 3600, new \DateTimeZone('America/Los_Angeles')),
638         'input2' => DateTimePlus::createFromFormat('U', 0, new \DateTimeZone('UTC')),
639         'absolute' => FALSE,
640         'expected' => $negative_1_hour,
641       ],
642       [
643         'input1' => DateTimePlus::createFromFormat('U', 3600),
644         'input2' => DateTimePlus::createFromFormat('U', 0),
645         'absolute' => FALSE,
646         'expected' => $negative_1_hour,
647       ],
648       [
649         'input1' => DateTimePlus::createFromFormat('U', 3600),
650         'input2' => \DateTime::createFromFormat('U', 0),
651         'absolute' => FALSE,
652         'expected' => $negative_1_hour,
653       ],
654       [
655         'input1' => DateTimePlus::createFromFormat('U', 3600),
656         'input2' => DateTimePlus::createFromFormat('U', 0),
657         'absolute' => TRUE,
658         'expected' => $positive_1_hour,
659       ],
660       [
661         'input1' => DateTimePlus::createFromFormat('U', 3600),
662         'input2' => \DateTime::createFromFormat('U', 0),
663         'absolute' => TRUE,
664         'expected' => $positive_1_hour,
665       ],
666       [
667         'input1' => DateTimePlus::createFromFormat('U', 0),
668         'input2' => DateTimePlus::createFromFormat('U', 0),
669         'absolute' => FALSE,
670         'expected' => $empty_interval,
671       ],
672     ];
673   }
674
675   /**
676    * Provides data for date tests.
677    *
678    * @return array
679    *   An array of arrays, each containing the input parameters for
680    *   DateTimePlusTest::testInvalidDateDiff().
681    *
682    * @see DateTimePlusTest::testInvalidDateDiff()
683    */
684   public function providerTestInvalidDateDiff() {
685     return [
686       [
687         'input1' => DateTimePlus::createFromFormat('U', 3600),
688         'input2' => '1970-01-01 00:00:00',
689         'absolute' => FALSE,
690       ],
691       [
692         'input1' => DateTimePlus::createFromFormat('U', 3600),
693         'input2' => NULL,
694         'absolute' => FALSE,
695       ],
696     ];
697   }
698
699   /**
700    * Tests invalid values passed to constructor.
701    *
702    * @param string $time
703    *   A date/time string.
704    * @param string[] $errors
705    *   An array of error messages.
706    *
707    * @covers ::__construct
708    *
709    * @dataProvider providerTestInvalidConstructor
710    */
711   public function testInvalidConstructor($time, array $errors) {
712     $date = new DateTimePlus($time);
713
714     $this->assertEquals(TRUE, $date->hasErrors());
715     $this->assertEquals($errors, $date->getErrors());
716   }
717
718   /**
719    * Provider for testInvalidConstructor().
720    *
721    * @return array
722    *   An array of invalid date/time strings, and corresponding error messages.
723    */
724   public function providerTestInvalidConstructor() {
725     return [
726       [
727         'YYYY-MM-DD',
728         [
729           'The timezone could not be found in the database',
730           'Unexpected character',
731           'Double timezone specification',
732         ],
733       ],
734       [
735         '2017-MM-DD',
736         [
737           'Unexpected character',
738           'The timezone could not be found in the database',
739         ],
740       ],
741       [
742         'YYYY-03-DD',
743         [
744           'The timezone could not be found in the database',
745           'Unexpected character',
746           'Double timezone specification',
747         ],
748       ],
749       [
750         'YYYY-MM-07',
751         [
752           'The timezone could not be found in the database',
753           'Unexpected character',
754           'Double timezone specification',
755         ],
756       ],
757       [
758         '2017-13-55',
759         [
760           'Unexpected character',
761         ],
762       ],
763       [
764         'YYYY-MM-DD hh:mm:ss',
765         [
766           'The timezone could not be found in the database',
767           'Unexpected character',
768           'Double timezone specification',
769         ],
770       ],
771       [
772         '2017-03-07 25:70:80',
773         [
774           'Unexpected character',
775           'Double time specification',
776         ],
777       ],
778       [
779         'lorem ipsum dolor sit amet',
780         [
781           'The timezone could not be found in the database',
782           'Double timezone specification',
783         ],
784       ],
785     ];
786   }
787
788   /**
789    * Tests the $settings['validate_format'] parameter in ::createFromFormat().
790    */
791   public function testValidateFormat() {
792     // Check that an input that does not strictly follow the input format will
793     // produce the desired date. In this case the year string '11' doesn't
794     // precisely match the 'Y' formater parameter, but PHP will parse it
795     // regardless. However, when formatted with the same string, the year will
796     // be output with four digits. With the ['validate_format' => FALSE]
797     // $settings, this will not thrown an exception.
798     $date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => FALSE]);
799     $this->assertEquals('0011-03-31 17:44:00', $date->format('Y-m-d H:i:s'));
800
801     // Parse the same date with ['validate_format' => TRUE] and make sure we
802     // get the expected exception.
803     $this->setExpectedException(\UnexpectedValueException::class);
804     $date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => TRUE]);
805   }
806
807   /**
808    * Tests that object methods are chainable.
809    *
810    * @covers ::__call
811    */
812   public function testChainable() {
813     $date = new DateTimePlus('now', 'Australia/Sydney');
814
815     $date->setTimestamp(12345678);
816     $rendered = $date->render();
817     $this->assertEquals('1970-05-24 07:21:18 Australia/Sydney', $rendered);
818
819     $date->setTimestamp(23456789);
820     $rendered = $date->setTimezone(new \DateTimeZone('America/New_York'))->render();
821     $this->assertEquals('1970-09-29 07:46:29 America/New_York', $rendered);
822
823     $date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-05-24 07:21:18', new \DateTimeZone('Australia/Sydney'))
824       ->setTimezone(new \DateTimeZone('America/New_York'));
825     $rendered = $date->render();
826     $this->assertInstanceOf(DateTimePlus::class, $date);
827     $this->assertEquals(12345678, $date->getTimestamp());
828     $this->assertEquals('1970-05-23 17:21:18 America/New_York', $rendered);
829   }
830
831   /**
832    * Tests that non-chainable methods work.
833    *
834    * @covers ::__call
835    */
836   public function testChainableNonChainable() {
837     $datetime1 = new DateTimePlus('2009-10-11 12:00:00');
838     $datetime2 = new DateTimePlus('2009-10-13 12:00:00');
839     $interval = $datetime1->diff($datetime2);
840     $this->assertInstanceOf(\DateInterval::class, $interval);
841     $this->assertEquals('+2 days', $interval->format('%R%a days'));
842   }
843
844   /**
845    * Tests that chained calls to non-existent functions throw an exception.
846    *
847    * @covers ::__call
848    */
849   public function testChainableNonCallable() {
850     $this->setExpectedException(\BadMethodCallException::class, 'Call to undefined method Drupal\Component\Datetime\DateTimePlus::nonexistent()');
851     $date = new DateTimePlus('now', 'Australia/Sydney');
852     $date->setTimezone(new \DateTimeZone('America/New_York'))->nonexistent();
853   }
854
855 }