format('c'); if (is_array($input)) { $input = var_export($input, TRUE); } $this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value)); } /** * Test creating dates from string and array input. * * @param mixed $input * Input argument for DateTimePlus. * @param string $timezone * Timezone argument for DateTimePlus. * @param string $expected * Expected output from DateTimePlus::format(). * * @dataProvider providerTestDateArrays */ public function testDateArrays($input, $timezone, $expected) { $date = DateTimePlus::createFromArray($input, $timezone); $value = $date->format('c'); if (is_array($input)) { $input = var_export($input, TRUE); } $this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value)); } /** * Test date diffs. * * @param mixed $input1 * A DateTimePlus object. * @param mixed $input2 * Date argument for DateTimePlus::diff method. * @param bool $absolute * Absolute flag for DateTimePlus::diff method. * @param \DateInterval $expected * The expected result of the DateTimePlus::diff operation. * * @dataProvider providerTestDateDiff */ public function testDateDiff($input1, $input2, $absolute, \DateInterval $expected) { $interval = $input1->diff($input2, $absolute); $this->assertEquals($interval, $expected); } /** * Test date diff exception caused by invalid input. * * @param mixed $input1 * A DateTimePlus object. * @param mixed $input2 * Date argument for DateTimePlus::diff method. * @param bool $absolute * Absolute flag for DateTimePlus::diff method. * * @dataProvider providerTestInvalidDateDiff */ public function testInvalidDateDiff($input1, $input2, $absolute) { $this->setExpectedException(\BadMethodCallException::class, 'Method Drupal\Component\Datetime\DateTimePlus::diff expects parameter 1 to be a \DateTime or \Drupal\Component\Datetime\DateTimePlus object'); $interval = $input1->diff($input2, $absolute); } /** * Test creating dates from invalid array input. * * @param mixed $input * Input argument for DateTimePlus. * @param string $timezone * Timezone argument for DateTimePlus. * @param string $class * The Exception subclass to expect to be thrown. * * @dataProvider providerTestInvalidDateArrays */ public function testInvalidDateArrays($input, $timezone, $class) { $this->setExpectedException($class); $this->assertInstanceOf( '\Drupal\Component\DateTimePlus', DateTimePlus::createFromArray($input, $timezone) ); } /** * Test creating dates from timestamps, and manipulating timezones. * * @param int $input * Input argument for DateTimePlus::createFromTimestamp(). * @param array $initial * An array containing: * - 'timezone_initial' - Timezone argument for DateTimePlus. * - 'format_initial' - Format argument for DateTimePlus. * - 'expected_initial_date' - Expected output from DateTimePlus::format(). * - 'expected_initial_timezone' - Expected output from * DateTimePlus::getTimeZone()::getName(). * - 'expected_initial_offset' - Expected output from DateTimePlus::getOffset(). * @param array $transform * An array containing: * - 'timezone_transform' - Argument to transform date to another timezone via * DateTimePlus::setTimezone(). * - 'format_transform' - Format argument to use when transforming date to * another timezone. * - 'expected_transform_date' - Expected output from DateTimePlus::format(), * after timezone transform. * - 'expected_transform_timezone' - Expected output from * DateTimePlus::getTimeZone()::getName(), after timezone transform. * - 'expected_transform_offset' - Expected output from * DateTimePlus::getOffset(), after timezone transform. * * @dataProvider providerTestTimestamp */ public function testTimestamp($input, array $initial, array $transform) { // Initialize a new date object. $date = DateTimePlus::createFromTimestamp($input, $initial['timezone']); $this->assertDateTimestamp($date, $input, $initial, $transform); } /** * Test creating dates from datetime strings. * * @param string $input * Input argument for DateTimePlus(). * @param array $initial * @see testTimestamp() * @param array $transform * @see testTimestamp() * * @dataProvider providerTestDateTimestamp */ public function testDateTimestamp($input, array $initial, array $transform) { // Initialize a new date object. $date = new DateTimePlus($input, $initial['timezone']); $this->assertDateTimestamp($date, $input, $initial, $transform); } /** * Assertion helper for testTimestamp and testDateTimestamp since they need * different dataProviders. * * @param DateTimePlus $date * DateTimePlus to test. * @input mixed $input * The original input passed to the test method. * @param array $initial * @see testTimestamp() * @param array $transform * @see testTimestamp() */ public function assertDateTimestamp($date, $input, $initial, $transform) { // Check format. $value = $date->format($initial['format']); $this->assertEquals($initial['expected_date'], $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $initial['timezone'], $initial['expected_date'], $value)); // Check timezone name. $value = $date->getTimeZone()->getName(); $this->assertEquals($initial['expected_timezone'], $value, sprintf("The current timezone is %s: should be %s.", $value, $initial['expected_timezone'])); // Check offset. $value = $date->getOffset(); $this->assertEquals($initial['expected_offset'], $value, sprintf("The current offset is %s: should be %s.", $value, $initial['expected_offset'])); // Transform the date to another timezone. $date->setTimezone(new \DateTimeZone($transform['timezone'])); // Check transformed format. $value = $date->format($transform['format']); $this->assertEquals($transform['expected_date'], $value, sprintf("Test \$date->setTimezone(new \\DateTimeZone(%s)): should be %s, found %s.", $transform['timezone'], $transform['expected_date'], $value)); // Check transformed timezone. $value = $date->getTimeZone()->getName(); $this->assertEquals($transform['expected_timezone'], $value, sprintf("The current timezone should be %s, found %s.", $transform['expected_timezone'], $value)); // Check transformed offset. $value = $date->getOffset(); $this->assertEquals($transform['expected_offset'], $value, sprintf("The current offset should be %s, found %s.", $transform['expected_offset'], $value)); } /** * Test creating dates from format strings. * * @param string $input * Input argument for DateTimePlus. * @param string $timezone * Timezone argument for DateTimePlus. * @param string $format_date * Format argument for DateTimePlus::format(). * @param string $expected * Expected output from DateTimePlus::format(). * * @dataProvider providerTestDateFormat */ public function testDateFormat($input, $timezone, $format, $format_date, $expected) { $date = DateTimePlus::createFromFormat($format, $input, $timezone); $value = $date->format($format_date); $this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s, %s): should be %s, found %s.", $input, $timezone, $format, $expected, $value)); } /** * Test invalid date handling. * * @param mixed $input * Input argument for DateTimePlus. * @param string $timezone * Timezone argument for DateTimePlus. * @param string $format * Format argument for DateTimePlus. * @param string $message * Message to print if no errors are thrown by the invalid dates. * @param string $class * The Exception subclass to expect to be thrown. * * @dataProvider providerTestInvalidDates */ public function testInvalidDates($input, $timezone, $format, $message, $class) { $this->setExpectedException($class); DateTimePlus::createFromFormat($format, $input, $timezone); } /** * Tests that DrupalDateTime can detect the right timezone to use. * When specified or not. * * @param mixed $input * Input argument for DateTimePlus. * @param mixed $timezone * Timezone argument for DateTimePlus. * @param string $expected_timezone * Expected timezone returned from DateTimePlus::getTimezone::getName(). * @param string $message * Message to print on test failure. * * @dataProvider providerTestDateTimezone */ public function testDateTimezone($input, $timezone, $expected_timezone, $message) { $date = new DateTimePlus($input, $timezone); $timezone = $date->getTimezone()->getName(); $this->assertEquals($timezone, $expected_timezone, $message); } /** * Test that DrupalDateTime can detect the right timezone to use when * constructed from a datetime object. */ public function testDateTimezoneWithDateTimeObject() { // Create a date object with another date object. $input = new \DateTime('now', new \DateTimeZone('Pacific/Midway')); $timezone = NULL; $expected_timezone = 'Pacific/Midway'; $message = 'DateTimePlus uses the specified timezone if provided.'; $date = DateTimePlus::createFromDateTime($input, $timezone); $timezone = $date->getTimezone()->getName(); $this->assertEquals($timezone, $expected_timezone, $message); } /** * Provides data for date tests. * * @return array * An array of arrays, each containing the input parameters for * DateTimePlusTest::testDates(). * * @see DateTimePlusTest::testDates() */ public function providerTestDates() { return [ // String input. // Create date object from datetime string. ['2009-03-07 10:30', 'America/Chicago', '2009-03-07T10:30:00-06:00'], // Same during daylight savings time. ['2009-06-07 10:30', 'America/Chicago', '2009-06-07T10:30:00-05:00'], // Create date object from date string. ['2009-03-07', 'America/Chicago', '2009-03-07T00:00:00-06:00'], // Same during daylight savings time. ['2009-06-07', 'America/Chicago', '2009-06-07T00:00:00-05:00'], // Create date object from date string. ['2009-03-07 10:30', 'Australia/Canberra', '2009-03-07T10:30:00+11:00'], // Same during daylight savings time. ['2009-06-07 10:30', 'Australia/Canberra', '2009-06-07T10:30:00+10:00'], ]; } /** * Provides data for date tests. * * @return array * An array of arrays, each containing the input parameters for * DateTimePlusTest::testDates(). * * @see DateTimePlusTest::testDates() */ public function providerTestDateArrays() { return [ // Array input. // Create date object from date array, date only. [['year' => 2010, 'month' => 2, 'day' => 28], 'America/Chicago', '2010-02-28T00:00:00-06:00'], // Create date object from date array with hour. [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'America/Chicago', '2010-02-28T10:00:00-06:00'], // Create date object from date array, date only. [['year' => 2010, 'month' => 2, 'day' => 28], 'Europe/Berlin', '2010-02-28T00:00:00+01:00'], // Create date object from date array with hour. [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'Europe/Berlin', '2010-02-28T10:00:00+01:00'], ]; } /** * Provides data for testDateFormats. * * @return array * An array of arrays, each containing: * - 'input' - Input to DateTimePlus. * - 'timezone' - Timezone for DateTimePlus. * - 'format' - Date format for DateTimePlus. * - 'format_date' - Date format for use in $date->format() method. * - 'expected' - The expected return from DateTimePlus. * * @see testDateFormats() */ public function providerTestDateFormat() { return [ // Create a year-only date. ['2009', NULL, 'Y', 'Y', '2009'], // Create a month and year-only date. ['2009-10', NULL, 'Y-m', 'Y-m', '2009-10'], // Create a time-only date. ['T10:30:00', NULL, '\TH:i:s', 'H:i:s', '10:30:00'], // Create a time-only date. ['10:30:00', NULL, 'H:i:s', 'H:i:s', '10:30:00'], ]; } /** * Provides data for testInvalidDates. * * @return array * An array of arrays, each containing: * - 'input' - Input for DateTimePlus. * - 'timezone' - Timezone for DateTimePlus. * - 'format' - Format for DateTimePlus. * - 'message' - Message to display on failure. * * @see testInvalidDates */ public function providerTestInvalidDates() { return [ // Test for invalid month names when we are using a short version // of the month. ['23 abc 2012', NULL, 'd M Y', "23 abc 2012 contains an invalid month name and did not produce errors.", \InvalidArgumentException::class], // Test for invalid hour. ['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], // Test for invalid day. ['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], // Test for invalid month. ['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], // Test for invalid year. ['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], ]; } /** * Data provider for testInvalidDateArrays. * * @return array * An array of arrays, each containing: * - 'input' - Input for DateTimePlus. * - 'timezone' - Timezone for DateTimePlus. * * @see testInvalidDateArrays */ public function providerTestInvalidDateArrays() { return [ // One year larger than the documented upper limit of checkdate(). [['year' => 32768, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class], // One year smaller than the documented lower limit of checkdate(). [['year' => 0, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class], // Test for invalid month from date array. [['year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class], // Test for invalid hour from date array. [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class], // Test for invalid minute from date array. [['year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class], // Regression test for https://www.drupal.org/node/2084455. [['hour' => 59, 'minute' => 1, 'second' => 1], 'America/Chicago', \InvalidArgumentException::class], ]; } /** * Provides data for testDateTimezone. * * @return array * An array of arrays, each containing: * - 'date' - Date string or object for DateTimePlus. * - 'timezone' - Timezone string for DateTimePlus. * - 'expected' - Expected return from DateTimePlus::getTimezone()::getName(). * - 'message' - Message to display on test failure. * * @see testDateTimezone */ public function providerTestDateTimezone() { // Use a common date for most of the tests. $date_string = '2007-01-31 21:00:00'; // Detect the system timezone. $system_timezone = date_default_timezone_get(); return [ // Create a date object with an unspecified timezone, which should // end up using the system timezone. [$date_string, NULL, $system_timezone, 'DateTimePlus uses the system timezone when there is no site timezone.'], // Create a date object with a specified timezone name. [$date_string, 'America/Yellowknife', 'America/Yellowknife', 'DateTimePlus uses the specified timezone if provided.'], // Create a date object with a timezone object. [$date_string, new \DateTimeZone('Australia/Canberra'), 'Australia/Canberra', 'DateTimePlus uses the specified timezone if provided.'], // Create a date object with another date object. [new DateTimePlus('now', 'Pacific/Midway'), NULL, 'Pacific/Midway', 'DateTimePlus uses the specified timezone if provided.'], ]; } /** * Provides data for testTimestamp. * * @return array * An array of arrays, each containing the arguments required for * self::testTimestamp(). * * @see testTimestamp() */ public function providerTestTimestamp() { return [ // Create date object from a unix timestamp and display it in // local time. [ 'input' => 0, 'initial' => [ 'timezone' => 'UTC', 'format' => 'c', 'expected_date' => '1970-01-01T00:00:00+00:00', 'expected_timezone' => 'UTC', 'expected_offset' => 0, ], 'transform' => [ 'timezone' => 'America/Los_Angeles', 'format' => 'c', 'expected_date' => '1969-12-31T16:00:00-08:00', 'expected_timezone' => 'America/Los_Angeles', 'expected_offset' => '-28800', ], ], // Create a date using the timestamp of zero, then display its // value both in UTC and the local timezone. [ 'input' => 0, 'initial' => [ 'timezone' => 'America/Los_Angeles', 'format' => 'c', 'expected_date' => '1969-12-31T16:00:00-08:00', 'expected_timezone' => 'America/Los_Angeles', 'expected_offset' => '-28800', ], 'transform' => [ 'timezone' => 'UTC', 'format' => 'c', 'expected_date' => '1970-01-01T00:00:00+00:00', 'expected_timezone' => 'UTC', 'expected_offset' => 0, ], ], ]; } /** * Provides data for testDateTimestamp. * * @return array * An array of arrays, each containing the arguments required for * self::testDateTimestamp(). * * @see testDateTimestamp() */ public function providerTestDateTimestamp() { return [ // Create date object from datetime string in UTC, and convert // it to a local date. [ 'input' => '1970-01-01 00:00:00', 'initial' => [ 'timezone' => 'UTC', 'format' => 'c', 'expected_date' => '1970-01-01T00:00:00+00:00', 'expected_timezone' => 'UTC', 'expected_offset' => 0, ], 'transform' => [ 'timezone' => 'America/Los_Angeles', 'format' => 'c', 'expected_date' => '1969-12-31T16:00:00-08:00', 'expected_timezone' => 'America/Los_Angeles', 'expected_offset' => '-28800', ], ], // Convert the local time to UTC using string input. [ 'input' => '1969-12-31 16:00:00', 'initial' => [ 'timezone' => 'America/Los_Angeles', 'format' => 'c', 'expected_date' => '1969-12-31T16:00:00-08:00', 'expected_timezone' => 'America/Los_Angeles', 'expected_offset' => '-28800', ], 'transform' => [ 'timezone' => 'UTC', 'format' => 'c', 'expected_date' => '1970-01-01T00:00:00+00:00', 'expected_timezone' => 'UTC', 'expected_offset' => 0, ], ], // Convert the local time to UTC using string input. [ 'input' => '1969-12-31 16:00:00', 'initial' => [ 'timezone' => 'Europe/Warsaw', 'format' => 'c', 'expected_date' => '1969-12-31T16:00:00+01:00', 'expected_timezone' => 'Europe/Warsaw', 'expected_offset' => '+3600', ], 'transform' => [ 'timezone' => 'UTC', 'format' => 'c', 'expected_date' => '1969-12-31T15:00:00+00:00', 'expected_timezone' => 'UTC', 'expected_offset' => 0, ], ], ]; } /** * Provides data for date tests. * * @return array * An array of arrays, each containing the input parameters for * DateTimePlusTest::testDateDiff(). * * @see DateTimePlusTest::testDateDiff() */ public function providerTestDateDiff() { $empty_interval = new \DateInterval('PT0S'); $positive_19_hours = new \DateInterval('PT19H'); $positive_18_hours = new \DateInterval('PT18H'); $positive_1_hour = new \DateInterval('PT1H'); $negative_1_hour = new \DateInterval('PT1H'); $negative_1_hour->invert = 1; return [ // There should be a 19 hour time interval between // new years in Sydney and new years in LA in year 2000. [ 'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')), 'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')), 'absolute' => FALSE, 'expected' => $positive_19_hours, ], // In 1970 Sydney did not observe daylight savings time // So there is only a 18 hour time interval. [ 'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')), 'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')), 'absolute' => FALSE, 'expected' => $positive_18_hours, ], [ 'input1' => DateTimePlus::createFromFormat('U', 3600, new \DateTimeZone('America/Los_Angeles')), 'input2' => DateTimePlus::createFromFormat('U', 0, new \DateTimeZone('UTC')), 'absolute' => FALSE, 'expected' => $negative_1_hour, ], [ 'input1' => DateTimePlus::createFromFormat('U', 3600), 'input2' => DateTimePlus::createFromFormat('U', 0), 'absolute' => FALSE, 'expected' => $negative_1_hour, ], [ 'input1' => DateTimePlus::createFromFormat('U', 3600), 'input2' => \DateTime::createFromFormat('U', 0), 'absolute' => FALSE, 'expected' => $negative_1_hour, ], [ 'input1' => DateTimePlus::createFromFormat('U', 3600), 'input2' => DateTimePlus::createFromFormat('U', 0), 'absolute' => TRUE, 'expected' => $positive_1_hour, ], [ 'input1' => DateTimePlus::createFromFormat('U', 3600), 'input2' => \DateTime::createFromFormat('U', 0), 'absolute' => TRUE, 'expected' => $positive_1_hour, ], [ 'input1' => DateTimePlus::createFromFormat('U', 0), 'input2' => DateTimePlus::createFromFormat('U', 0), 'absolute' => FALSE, 'expected' => $empty_interval, ], ]; } /** * Provides data for date tests. * * @return array * An array of arrays, each containing the input parameters for * DateTimePlusTest::testInvalidDateDiff(). * * @see DateTimePlusTest::testInvalidDateDiff() */ public function providerTestInvalidDateDiff() { return [ [ 'input1' => DateTimePlus::createFromFormat('U', 3600), 'input2' => '1970-01-01 00:00:00', 'absolute' => FALSE, ], [ 'input1' => DateTimePlus::createFromFormat('U', 3600), 'input2' => NULL, 'absolute' => FALSE, ], ]; } /** * Tests invalid values passed to constructor. * * @param string $time * A date/time string. * @param string[] $errors * An array of error messages. * * @covers ::__construct * * @dataProvider providerTestInvalidConstructor */ public function testInvalidConstructor($time, array $errors) { $date = new DateTimePlus($time); $this->assertEquals(TRUE, $date->hasErrors()); $this->assertEquals($errors, $date->getErrors()); } /** * Provider for testInvalidConstructor(). * * @return array * An array of invalid date/time strings, and corresponding error messages. */ public function providerTestInvalidConstructor() { return [ [ 'YYYY-MM-DD', [ 'The timezone could not be found in the database', 'Unexpected character', 'Double timezone specification', ], ], [ '2017-MM-DD', [ 'Unexpected character', 'The timezone could not be found in the database', ], ], [ 'YYYY-03-DD', [ 'The timezone could not be found in the database', 'Unexpected character', 'Double timezone specification', ], ], [ 'YYYY-MM-07', [ 'The timezone could not be found in the database', 'Unexpected character', 'Double timezone specification', ], ], [ '2017-13-55', [ 'Unexpected character', ], ], [ 'YYYY-MM-DD hh:mm:ss', [ 'The timezone could not be found in the database', 'Unexpected character', 'Double timezone specification', ], ], [ '2017-03-07 25:70:80', [ 'Unexpected character', 'Double time specification', ], ], [ 'lorem ipsum dolor sit amet', [ 'The timezone could not be found in the database', 'Double timezone specification', ], ], ]; } /** * Tests the $settings['validate_format'] parameter in ::createFromFormat(). */ public function testValidateFormat() { // Check that an input that does not strictly follow the input format will // produce the desired date. In this case the year string '11' doesn't // precisely match the 'Y' formater parameter, but PHP will parse it // regardless. However, when formatted with the same string, the year will // be output with four digits. With the ['validate_format' => FALSE] // $settings, this will not thrown an exception. $date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => FALSE]); $this->assertEquals('0011-03-31 17:44:00', $date->format('Y-m-d H:i:s')); // Parse the same date with ['validate_format' => TRUE] and make sure we // get the expected exception. $this->setExpectedException(\UnexpectedValueException::class); $date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => TRUE]); } }