Version 1
[yaffs-website] / web / core / tests / Drupal / Tests / Component / Datetime / DateTimePlusTest.php
1 <?php
2
3 namespace Drupal\Tests\Component\Datetime;
4
5 use Drupal\Tests\UnitTestCase;
6 use Drupal\Component\Datetime\DateTimePlus;
7
8 /**
9  * @coversDefaultClass \Drupal\Component\Datetime\DateTimePlus
10  * @group Datetime
11  */
12 class DateTimePlusTest extends UnitTestCase {
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 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     return [
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
313   /**
314    * Provides data for date tests.
315    *
316    * @return array
317    *   An array of arrays, each containing the input parameters for
318    *   DateTimePlusTest::testDates().
319    *
320    * @see DateTimePlusTest::testDates()
321    */
322   public function providerTestDateArrays() {
323     return [
324       // Array input.
325       // Create date object from date array, date only.
326       [['year' => 2010, 'month' => 2, 'day' => 28], 'America/Chicago', '2010-02-28T00:00:00-06:00'],
327       // Create date object from date array with hour.
328       [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'America/Chicago', '2010-02-28T10:00:00-06:00'],
329       // Create date object from date array, date only.
330       [['year' => 2010, 'month' => 2, 'day' => 28], 'Europe/Berlin', '2010-02-28T00:00:00+01:00'],
331       // Create date object from date array with hour.
332       [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'Europe/Berlin', '2010-02-28T10:00:00+01:00'],
333     ];
334   }
335
336   /**
337    * Provides data for testDateFormats.
338    *
339    * @return array
340    *   An array of arrays, each containing:
341    *   - 'input' - Input to DateTimePlus.
342    *   - 'timezone' - Timezone for DateTimePlus.
343    *   - 'format' - Date format for DateTimePlus.
344    *   - 'format_date' - Date format for use in $date->format() method.
345    *   - 'expected' - The expected return from DateTimePlus.
346    *
347    * @see testDateFormats()
348    */
349   public function providerTestDateFormat() {
350     return [
351       // Create a year-only date.
352       ['2009', NULL, 'Y', 'Y', '2009'],
353       // Create a month and year-only date.
354       ['2009-10', NULL, 'Y-m', 'Y-m', '2009-10'],
355       // Create a time-only date.
356       ['T10:30:00', NULL, '\TH:i:s', 'H:i:s', '10:30:00'],
357       // Create a time-only date.
358       ['10:30:00', NULL, 'H:i:s', 'H:i:s', '10:30:00'],
359     ];
360   }
361
362   /**
363    * Provides data for testInvalidDates.
364    *
365    * @return array
366    *   An array of arrays, each containing:
367    *   - 'input' - Input for DateTimePlus.
368    *   - 'timezone' - Timezone for DateTimePlus.
369    *   - 'format' - Format for DateTimePlus.
370    *   - 'message' - Message to display on failure.
371    *
372    * @see testInvalidDates
373    */
374   public function providerTestInvalidDates() {
375     return [
376       // Test for invalid month names when we are using a short version
377       // of the month.
378       ['23 abc 2012', NULL, 'd M Y', "23 abc 2012 contains an invalid month name and did not produce errors.", \InvalidArgumentException::class],
379       // Test for invalid hour.
380       ['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],
381       // Test for invalid day.
382       ['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],
383       // Test for invalid month.
384       ['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],
385       // Test for invalid year.
386       ['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],
387
388     ];
389   }
390
391   /**
392    * Data provider for testInvalidDateArrays.
393    *
394    * @return array
395    *   An array of arrays, each containing:
396    *   - 'input' - Input for DateTimePlus.
397    *   - 'timezone' - Timezone for DateTimePlus.
398    *
399    * @see testInvalidDateArrays
400    */
401   public function providerTestInvalidDateArrays() {
402     return [
403       // One year larger than the documented upper limit of checkdate().
404       [['year' => 32768, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
405       // One year smaller than the documented lower limit of checkdate().
406       [['year' => 0, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
407       // Test for invalid month from date array.
408       [['year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
409       // Test for invalid hour from date array.
410       [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
411       // Test for invalid minute from date array.
412       [['year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
413       // Regression test for https://www.drupal.org/node/2084455.
414       [['hour' => 59, 'minute' => 1, 'second' => 1], 'America/Chicago', \InvalidArgumentException::class],
415     ];
416   }
417
418   /**
419    * Provides data for testDateTimezone.
420    *
421    * @return array
422    *   An array of arrays, each containing:
423    *   - 'date' - Date string or object for DateTimePlus.
424    *   - 'timezone' - Timezone string for DateTimePlus.
425    *   - 'expected' - Expected return from DateTimePlus::getTimezone()::getName().
426    *   - 'message' - Message to display on test failure.
427    *
428    * @see testDateTimezone
429    */
430   public function providerTestDateTimezone() {
431     // Use a common date for most of the tests.
432     $date_string = '2007-01-31 21:00:00';
433
434     // Detect the system timezone.
435     $system_timezone = date_default_timezone_get();
436
437     return [
438       // Create a date object with an unspecified timezone, which should
439       // end up using the system timezone.
440       [$date_string, NULL, $system_timezone, 'DateTimePlus uses the system timezone when there is no site timezone.'],
441       // Create a date object with a specified timezone name.
442       [$date_string, 'America/Yellowknife', 'America/Yellowknife', 'DateTimePlus uses the specified timezone if provided.'],
443       // Create a date object with a timezone object.
444       [$date_string, new \DateTimeZone('Australia/Canberra'), 'Australia/Canberra', 'DateTimePlus uses the specified timezone if provided.'],
445       // Create a date object with another date object.
446       [new DateTimePlus('now', 'Pacific/Midway'), NULL, 'Pacific/Midway', 'DateTimePlus uses the specified timezone if provided.'],
447     ];
448   }
449
450   /**
451    * Provides data for testTimestamp.
452    *
453    * @return array
454    *   An array of arrays, each containing the arguments required for
455    *   self::testTimestamp().
456    *
457    * @see testTimestamp()
458    */
459   public function providerTestTimestamp() {
460     return [
461       // Create date object from a unix timestamp and display it in
462       // local time.
463       [
464         'input' => 0,
465         'initial' => [
466           'timezone' => 'UTC',
467           'format' => 'c',
468           'expected_date' => '1970-01-01T00:00:00+00:00',
469           'expected_timezone' => 'UTC',
470           'expected_offset' => 0,
471         ],
472         'transform' => [
473           'timezone' => 'America/Los_Angeles',
474           'format' => 'c',
475           'expected_date' => '1969-12-31T16:00:00-08:00',
476           'expected_timezone' => 'America/Los_Angeles',
477           'expected_offset' => '-28800',
478         ],
479       ],
480       // Create a date using the timestamp of zero, then display its
481       // value both in UTC and the local timezone.
482       [
483         'input' => 0,
484         'initial' => [
485           'timezone' => 'America/Los_Angeles',
486           'format' => 'c',
487           'expected_date' => '1969-12-31T16:00:00-08:00',
488           'expected_timezone' => 'America/Los_Angeles',
489           'expected_offset' => '-28800',
490         ],
491         'transform' => [
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       ],
499     ];
500   }
501
502   /**
503    * Provides data for testDateTimestamp.
504    *
505    * @return array
506    *   An array of arrays, each containing the arguments required for
507    *   self::testDateTimestamp().
508    *
509    * @see testDateTimestamp()
510    */
511   public function providerTestDateTimestamp() {
512     return [
513       // Create date object from datetime string in UTC, and convert
514       // it to a local date.
515       [
516         'input' => '1970-01-01 00:00:00',
517         'initial' => [
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         'transform' => [
525           'timezone' => 'America/Los_Angeles',
526           'format' => 'c',
527           'expected_date' => '1969-12-31T16:00:00-08:00',
528           'expected_timezone' => 'America/Los_Angeles',
529           'expected_offset' => '-28800',
530         ],
531       ],
532       // Convert the local time to UTC using string input.
533       [
534         'input' => '1969-12-31 16:00:00',
535         'initial' => [
536           'timezone' => 'America/Los_Angeles',
537           'format' => 'c',
538           'expected_date' => '1969-12-31T16:00:00-08:00',
539           'expected_timezone' => 'America/Los_Angeles',
540           'expected_offset' => '-28800',
541         ],
542         'transform' => [
543           'timezone' => 'UTC',
544           'format' => 'c',
545           'expected_date' => '1970-01-01T00:00:00+00:00',
546           'expected_timezone' => 'UTC',
547           'expected_offset' => 0,
548         ],
549       ],
550       // Convert the local time to UTC using string input.
551       [
552         'input' => '1969-12-31 16:00:00',
553         'initial' => [
554           'timezone' => 'Europe/Warsaw',
555           'format' => 'c',
556           'expected_date' => '1969-12-31T16:00:00+01:00',
557           'expected_timezone' => 'Europe/Warsaw',
558           'expected_offset' => '+3600',
559         ],
560         'transform' => [
561           'timezone' => 'UTC',
562           'format' => 'c',
563           'expected_date' => '1969-12-31T15:00:00+00:00',
564           'expected_timezone' => 'UTC',
565           'expected_offset' => 0,
566         ],
567       ],
568     ];
569   }
570
571   /**
572    * Provides data for date tests.
573    *
574    * @return array
575    *   An array of arrays, each containing the input parameters for
576    *   DateTimePlusTest::testDateDiff().
577    *
578    * @see DateTimePlusTest::testDateDiff()
579    */
580   public function providerTestDateDiff() {
581
582     $empty_interval = new \DateInterval('PT0S');
583
584     $positive_19_hours = new \DateInterval('PT19H');
585
586     $positive_18_hours = new \DateInterval('PT18H');
587
588     $positive_1_hour = new \DateInterval('PT1H');
589
590     $negative_1_hour = new \DateInterval('PT1H');
591     $negative_1_hour->invert = 1;
592
593     return [
594       // There should be a 19 hour time interval between
595       // new years in Sydney and new years in LA in year 2000.
596       [
597         'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
598         'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
599         'absolute' => FALSE,
600         'expected' => $positive_19_hours,
601       ],
602       // In 1970 Sydney did not observe daylight savings time
603       // So there is only a 18 hour time interval.
604       [
605         'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
606         'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
607         'absolute' => FALSE,
608         'expected' => $positive_18_hours,
609       ],
610       [
611         'input1' => DateTimePlus::createFromFormat('U', 3600, new \DateTimeZone('America/Los_Angeles')),
612         'input2' => DateTimePlus::createFromFormat('U', 0, new \DateTimeZone('UTC')),
613         'absolute' => FALSE,
614         'expected' => $negative_1_hour,
615       ],
616       [
617         'input1' => DateTimePlus::createFromFormat('U', 3600),
618         'input2' => DateTimePlus::createFromFormat('U', 0),
619         'absolute' => FALSE,
620         'expected' => $negative_1_hour,
621       ],
622       [
623         'input1' => DateTimePlus::createFromFormat('U', 3600),
624         'input2' => \DateTime::createFromFormat('U', 0),
625         'absolute' => FALSE,
626         'expected' => $negative_1_hour,
627       ],
628       [
629         'input1' => DateTimePlus::createFromFormat('U', 3600),
630         'input2' => DateTimePlus::createFromFormat('U', 0),
631         'absolute' => TRUE,
632         'expected' => $positive_1_hour,
633       ],
634       [
635         'input1' => DateTimePlus::createFromFormat('U', 3600),
636         'input2' => \DateTime::createFromFormat('U', 0),
637         'absolute' => TRUE,
638         'expected' => $positive_1_hour,
639       ],
640       [
641         'input1' => DateTimePlus::createFromFormat('U', 0),
642         'input2' => DateTimePlus::createFromFormat('U', 0),
643         'absolute' => FALSE,
644         'expected' => $empty_interval,
645       ],
646     ];
647   }
648
649   /**
650    * Provides data for date tests.
651    *
652    * @return array
653    *   An array of arrays, each containing the input parameters for
654    *   DateTimePlusTest::testInvalidDateDiff().
655    *
656    * @see DateTimePlusTest::testInvalidDateDiff()
657    */
658   public function providerTestInvalidDateDiff() {
659     return [
660       [
661         'input1' => DateTimePlus::createFromFormat('U', 3600),
662         'input2' => '1970-01-01 00:00:00',
663         'absolute' => FALSE,
664       ],
665       [
666         'input1' => DateTimePlus::createFromFormat('U', 3600),
667         'input2' => NULL,
668         'absolute' => FALSE,
669       ],
670     ];
671   }
672
673   /**
674    * Tests invalid values passed to constructor.
675    *
676    * @param string $time
677    *   A date/time string.
678    * @param string[] $errors
679    *   An array of error messages.
680    *
681    * @covers ::__construct
682    *
683    * @dataProvider providerTestInvalidConstructor
684    */
685   public function testInvalidConstructor($time, array $errors) {
686     $date = new DateTimePlus($time);
687
688     $this->assertEquals(TRUE, $date->hasErrors());
689     $this->assertEquals($errors, $date->getErrors());
690   }
691
692   /**
693    * Provider for testInvalidConstructor().
694    *
695    * @return array
696    *   An array of invalid date/time strings, and corresponding error messages.
697    */
698   public function providerTestInvalidConstructor() {
699     return [
700       [
701         'YYYY-MM-DD',
702         [
703           'The timezone could not be found in the database',
704           'Unexpected character',
705           'Double timezone specification',
706         ],
707       ],
708       [
709         '2017-MM-DD',
710         [
711           'Unexpected character',
712           'The timezone could not be found in the database',
713         ],
714       ],
715       [
716         'YYYY-03-DD',
717         [
718           'The timezone could not be found in the database',
719           'Unexpected character',
720           'Double timezone specification',
721         ],
722       ],
723       [
724         'YYYY-MM-07',
725         [
726           'The timezone could not be found in the database',
727           'Unexpected character',
728           'Double timezone specification',
729         ],
730       ],
731       [
732         '2017-13-55',
733         [
734           'Unexpected character',
735         ],
736       ],
737       [
738         'YYYY-MM-DD hh:mm:ss',
739         [
740           'The timezone could not be found in the database',
741           'Unexpected character',
742           'Double timezone specification',
743         ],
744       ],
745       [
746         '2017-03-07 25:70:80',
747         [
748           'Unexpected character',
749           'Double time specification',
750         ],
751       ],
752       [
753         'lorem ipsum dolor sit amet',
754         [
755           'The timezone could not be found in the database',
756           'Double timezone specification',
757         ],
758       ],
759     ];
760   }
761
762   /**
763    * Tests the $settings['validate_format'] parameter in ::createFromFormat().
764    */
765   public function testValidateFormat() {
766     // Check that an input that does not strictly follow the input format will
767     // produce the desired date. In this case the year string '11' doesn't
768     // precisely match the 'Y' formater parameter, but PHP will parse it
769     // regardless. However, when formatted with the same string, the year will
770     // be output with four digits. With the ['validate_format' => FALSE]
771     // $settings, this will not thrown an exception.
772     $date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => FALSE]);
773     $this->assertEquals('0011-03-31 17:44:00', $date->format('Y-m-d H:i:s'));
774
775     // Parse the same date with ['validate_format' => TRUE] and make sure we
776     // get the expected exception.
777     $this->setExpectedException(\UnexpectedValueException::class);
778     $date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => TRUE]);
779   }
780
781 }