diff --git a/core/includes/common.inc b/core/includes/common.inc index 47e9f95..c0be10a 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -5,6 +5,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Database\Database; use Drupal\Core\Template\Attribute; @@ -1997,26 +1998,12 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL break; } - // Create a DateTime object from the timestamp. - $date_time = date_create('@' . $timestamp); - // Set the time zone for the DateTime object. - date_timezone_set($date_time, $timezones[$timezone]); - - // Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'. - // xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the - // input string. - // Paired backslashes are isolated to prevent errors in read-ahead evaluation. - // The read-ahead expression ensures that A matches, but not \A. - $format = preg_replace(array('/\\\\\\\\/', '/(? $langcode); + return $date_time->format($format, $settings); } /** @@ -5004,7 +4991,8 @@ function drupal_page_set_cache($body) { $cache->data['headers'][$header_names[$name_lower]] = $value; if ($name_lower == 'expires') { // Use the actual timestamp from an Expires header if available. - $cache->expire = strtotime($value); + $date = new DrupalDateTime($value); + $cache->expire = $date->getTimestamp(); } } diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php new file mode 100644 index 0000000..907420b --- /dev/null +++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php @@ -0,0 +1,733 @@ + 2014, 'month => 4). + * Defaults to 'now'. + * @param mixed $timezone + * PHP DateTimeZone object, string or NULL allowed. + * Defaults to NULL. + * @param string $format + * PHP date() type format for parsing the input. This is recommended + * for specialized input with a known format. If provided the + * date will be created using the createFromFormat() method. + * Defaults to NULL. + * @see http://us3.php.net/manual/en/datetime.createfromformat.php + * @param array $settings + * - boolean $validate_format + * The format used in createFromFormat() allows slightly different + * values than format(). If we use an input format that works in + * both functions we can add a validation step to confirm that the + * date created from a format string exactly matches the input. + * We need to know if this can be relied on to do that validation. + * Defaults to TRUE. + * - string $langcode + * The two letter language code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $country + * The two letter country code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $calendar + * A calendar name to use for the date, Defaults to + * DateTimePlus::CALENDAR. + * - boolean $debug + * Leave evidence of the input values in the resulting object + * for debugging purposes. Defaults to FALSE. + */ + public function __construct($time = 'now', $timezone = NULL, $format = NULL, $settings = array()) { + + // Unpack settings. + $this->validateFormat = !empty($settings['validate_format']) ? $settings['validate_format'] : TRUE; + $this->langcode = !empty($settings['langcode']) ? $settings['langcode'] : NULL; + $this->country = !empty($settings['country']) ? $settings['country'] : NULL; + $this->calendar = !empty($settings['calendar']) ? $settings['calendar'] : static::CALENDAR; + + // Store the original input so it is available for validation. + $this->inputTimeRaw = $time; + $this->inputTimeZoneRaw = $timezone; + $this->inputFormatRaw = $format; + + // Massage the input values as necessary. + $this->prepareTime($time); + $this->prepareTimezone($timezone); + $this->prepareFormat($format); + + // Create a date as a clone of an input DateTime object. + if ($this->inputIsObject()) { + $this->constructFromObject(); + } + + // Create date from array of date parts. + elseif ($this->inputIsArray()) { + $this->constructFromArray(); + } + + // Create a date from a Unix timestamp. + elseif ($this->inputIsTimestamp()) { + $this->constructFromTimestamp(); + } + + // Create a date from a time string and an expected format. + elseif ($this->inputIsFormat()) { + $this->constructFromFormat(); + } + + // Create a date from any other input. + else { + $this->constructFallback(); + } + + // Clean up the error messages. + $this->checkErrors(); + $this->errors = array_unique($this->errors); + + // Now that we've validated the input, clean up the extra values. + if (empty($settings['debug'])) { + unset( + $this->inputTimeRaw, + $this->inputTimeAdjusted, + $this->inputTimeZoneRaw, + $this->inputTimeZoneAdjusted, + $this->inputFormatRaw, + $this->inputFormatAdjusted, + $this->validateFormat + ); + } + + } + + /** + * Implementation of __toString() for dates. The base DateTime + * class does not implement this. + * + * @see https://bugs.php.net/bug.php?id=62911 and + * http://www.serverphorums.com/read.php?7,555645 + */ + public function __toString() { + $format = static::FORMAT; + return $this->format($format) . ' ' . $this->getTimeZone()->getName(); + } + + /** + * Prepare the input value before trying to use it. + * Can be overridden to handle special cases. + * + * @param mixed $time + * An input value, which could be a timestamp, a string, + * a date object, or an array of date parts. + */ + protected function prepareTime($time) { + $this->inputTimeAdjusted = $time; + } + + /** + * Prepare the timezone before trying to use it. + * Most imporantly, make sure we have a valid timezone + * object before moving further. + * + * @param mixed $timezone + * Either a timezone name or a timezone object or NULL. + */ + protected function prepareTimezone($timezone) { + // If the input timezone is a valid timezone object, use it. + if ($timezone instanceOf \DateTimezone) { + $timezone_adjusted = $timezone; + } + + // When the passed-in time is a DateTime object with its own + // timezone, try to use the date's timezone. + elseif (empty($timezone) && $this->inputTimeAdjusted instanceOf \DateTime) { + $timezone_adjusted = $this->inputTimeAdjusted->getTimezone(); + } + + // Allow string timezone input, and create a timezone from it. + elseif (!empty($timezone) && is_string($timezone)) { + $timezone_adjusted = new \DateTimeZone($timezone); + } + + // Default to the system timezone when not explicitly provided. + // If the system timezone is missing, use 'UTC'. + if (empty($timezone_adjusted) || !$timezone_adjusted instanceOf \DateTimezone) { + $system_timezone = date_default_timezone_get(); + $timezone_name = !empty($system_timezone) ? $system_timezone : 'UTC'; + $timezone_adjusted = new \DateTimeZone($timezone_name); + } + + // We are finally certain that we have a usable timezone. + $this->inputTimeZoneAdjusted = $timezone_adjusted; + } + + /** + * Prepare the input format before trying to use it. + * Can be overridden to handle special cases. + * + * @param string $format + * A PHP format string. + */ + protected function prepareFormat($format) { + $this->inputFormatAdjusted = $format; + } + + /** + * Check if input is a DateTime object. + * + * @return boolean + * TRUE if the input time is a DateTime object. + */ + public function inputIsObject() { + return $this->inputTimeAdjusted instanceOf \DateTime; + } + + /** + * Create a date object from an input date object. + */ + protected function constructFromObject() { + try { + $this->inputTimeAdjusted = $this->inputTimeAdjusted->format(static::FORMAT); + parent::__construct($this->inputTimeAdjusted, $this->inputTimeZoneAdjusted); + } + catch (\Exception $e) { + $this->errors[] = $e->getMessage(); + } + } + + /** + * Check if input time seems to be a timestamp. + * + * Providing an input format will prevent ISO values without separators + * from being mis-interpreted as timestamps. Providing a format can also + * avoid interpreting a value like '2010' with a format of 'Y' as a + * timestamp. The 'U' format indicates this is a timestamp. + * + * @return boolean + * TRUE if the input time is a timestamp. + */ + public function inputIsTimestamp() { + return is_numeric($this->inputTimeAdjusted) && (empty($this->inputFormatAdjusted) || $this->inputFormatAdjusted == 'U'); + } + + /** + * Create a date object from timestamp input. + * + * The timezone for timestamps is always UTC. In this case the + * timezone we set controls the timezone used when displaying + * the value using format(). + */ + protected function constructFromTimestamp() { + try { + parent::__construct('', $this->inputTimeZoneAdjusted); + $this->setTimestamp($this->inputTimeAdjusted); + } + catch (\Exception $e) { + $this->errors[] = $e->getMessage(); + } + } + + /** + * Check if input is an array of date parts. + * + * @return boolean + * TRUE if the input time is a DateTime object. + */ + public function inputIsArray() { + return is_array($this->inputTimeAdjusted); + } + + /** + * Create a date object from an array of date parts. + * + * Convert the input value into an ISO date, forcing a full ISO + * date even if some values are missing. + */ + protected function constructFromArray() { + try { + parent::__construct('', $this->inputTimeZoneAdjusted); + $this->inputTimeAdjusted = static::prepareArray($this->inputTimeAdjusted, TRUE); + if (static::checkArray($this->inputTimeAdjusted)) { + // Even with validation, we can end up with a value that the + // parent class won't handle, like a year outside the range + // of -9999 to 9999, which will pass checkdate() but + // fail to construct a date object. + $this->inputTimeAdjusted = static::arrayToISO($this->inputTimeAdjusted); + parent::__construct($this->inputTimeAdjusted, $this->inputTimeZoneAdjusted); + } + else { + throw new \Exception('The array contains invalid values.'); + } + } + catch (\Exception $e) { + $this->errors[] = $e->getMessage(); + } + } + + /** + * Check if input is a string with an expected format. + * + * @return boolean + * TRUE if the input time is a string with an expected format. + */ + public function inputIsFormat() { + return is_string($this->inputTimeAdjusted) && !empty($this->inputFormatAdjusted); + } + + /** + * Create a date object from an input format. + * + */ + protected function constructFromFormat() { + // Try to create a date from the format and use it if possible. + // A regular try/catch won't work right here, if the value is + // invalid it doesn't return an exception. + try { + parent::__construct('', $this->inputTimeZoneAdjusted); + $date = parent::createFromFormat($this->inputFormatAdjusted, $this->inputTimeAdjusted, $this->inputTimeZoneAdjusted); + if (!$date instanceOf \DateTime) { + throw new \Exception('The date cannot be created from a format.'); + } + else { + $this->setTimestamp($date->getTimestamp()); + $this->setTimezone($date->getTimezone()); + + try { + // The createFromFormat function is forgiving, it might + // create a date that is not exactly a match for the provided + // value, so test for that. For instance, an input value of + // '11' using a format of Y (4 digits) gets created as + // '0011' instead of '2011'. + // Use the parent::format() because we do not want to use + // the IntlDateFormatter here. + if ($this->validateFormat && parent::format($this->inputFormatAdjusted) != $this->inputTimeRaw) { + throw new \Exception('The created date does not match the input value.'); + } + } + catch (\Exception $e) { + $this->errors[] = $e->getMessage(); + } + } + } + catch (\Exception $e) { + $this->errors[] = $e->getMessage(); + } + } + + /** + * Fallback construction for values that don't match any of the + * other patterns. + * + * Let the parent dateTime attempt to turn this string into a + * valid date. + */ + protected function constructFallback() { + + try { + // One last test for invalid input before we try to construct + // a date. If the input contains totally bogus information + // it will blow up badly if we pass it to the constructor. + // The date_parse() function will tell us if the input + // makes sense. + if (!empty($this->inputTimeAdjusted)) { + $test = date_parse($this->inputTimeAdjusted); + if (!empty($test['errors'])) { + $this->errors[] = $test['errors']; + } + } + + if (empty($this->errors)) { + @parent::__construct($this->inputTimeAdjusted, $this->inputTimeZoneAdjusted); + } + } + catch (\Exception $e) { + $this->errors[] = $e->getMessage(); + } + } + + /** + * Examine getLastErrors() and see what errors to report. + * + * We're interested in two kinds of errors: anything that DateTime + * considers an error, and also a warning that the date was invalid. + * PHP creates a valid date from invalid data with only a warning, + * 2011-02-30 becomes 2011-03-03, for instance, but we don't want that. + * + * @see http://us3.php.net/manual/en/time.getlasterrors.php + */ + public function checkErrors() { + $errors = $this->getLastErrors(); + if (!empty($errors['errors'])) { + $this->errors += $errors['errors']; + } + // Most warnings are messages that the date could not be parsed + // which causes it to be altered. We're going to consider a warning + // as bad as an error. + if (!empty($errors['warnings'])) { + $this->errors[] = 'The date is invalid.'; + } + } + + /** + * Detect if there were errors in the processing of this date. + */ + public function hasErrors() { + return (boolean) count($this->errors); + } + + /** + * Public function to retrieve the error messages. + */ + public function getErrors() { + return $this->errors; + } + + /** + * Creates an ISO date from an array of values. + * + * @param array $array + * An array of date values keyed by date part. + * @param bool $force_valid_date + * (optional) Whether to force a full date by filling in missing + * values. Defaults to FALSE. + * + * @return string + * The date as an ISO string. + */ + public static function arrayToISO($array, $force_valid_date = FALSE) { + $array = static::prepareArray($array, $force_valid_date); + $input_time = ''; + if ($array['year'] !== '') { + $input_time = static::datePad(intval($array['year']), 4); + if ($force_valid_date || $array['month'] !== '') { + $input_time .= '-' . static::datePad(intval($array['month'])); + if ($force_valid_date || $array['day'] !== '') { + $input_time .= '-' . static::datePad(intval($array['day'])); + } + } + } + if ($array['hour'] !== '') { + $input_time .= $input_time ? 'T' : ''; + $input_time .= static::datePad(intval($array['hour'])); + if ($force_valid_date || $array['minute'] !== '') { + $input_time .= ':' . static::datePad(intval($array['minute'])); + if ($force_valid_date || $array['second'] !== '') { + $input_time .= ':' . static::datePad(intval($array['second'])); + } + } + } + return $input_time; + } + + /** + * Creates a complete array from a possibly incomplete array of date parts. + * + * @param array $array + * An array of date values keyed by date part. + * @param bool $force_valid_date + * (optional) Whether to force a valid date by filling in missing + * values with valid values or just to use empty values instead. + * Defaults to FALSE. + * + * @return array + * A complete array of date parts. + */ + public static function prepareArray($array, $force_valid_date = FALSE) { + if ($force_valid_date) { + $now = new \DateTime(); + $array += array( + 'year' => $now->format('Y'), + 'month' => 1, + 'day' => 1, + 'hour' => 0, + 'minute' => 0, + 'second' => 0, + ); + } + else { + $array += array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ); + } + return $array; + } + + /** + * Check that an array of date parts has a year, month, and day, + * and that those values create a valid date. If time is provided, + * verify that the time values are valid. Sort of an + * equivalent to checkdate(). + * + * @param array $array + * An array of datetime values keyed by date part. + * + * @return boolean + * TRUE if the datetime parts contain valid values, otherwise FALSE. + */ + public static function checkArray($array) { + $valid_date = FALSE; + $valid_time = TRUE; + // Check for a valid date using checkdate(). Only values that + // meet that test are valid. + if (array_key_exists('year', $array) && array_key_exists('month', $array) && array_key_exists('day', $array)) { + if (@checkdate($array['month'], $array['day'], $array['year'])) { + $valid_date = TRUE; + } + } + // Testing for valid time is reversed. Missing time is OK, + // but incorrect values are not. + foreach (array('hour', 'minute', 'second') as $key) { + if (array_key_exists($key, $array)) { + $value = $array[$key]; + switch ($value) { + case 'hour': + if (!preg_match('/^([1-2][0-3]|[01]?[0-9])$/', $value)) { + $valid_time = FALSE; + } + break; + case 'minute': + case 'second': + default: + if (!preg_match('/^([0-5][0-9]|[0-9])$/', $value)) { + $valid_time = FALSE; + } + break; + } + } + } + return $valid_date && $valid_time; + } + + /** + * Helper function to left pad date parts with zeros. + * + * Provided because this is needed so often with dates. + * + * @param int $value + * The value to pad. + * @param int $size + * (optional) Size expected, usually 2 or 4. Defaults to 2. + * + * @return string + * The padded value. + */ + public static function datePad($value, $size = 2) { + return sprintf("%0" . $size . "d", $value); + } + + + /** + * Test if the IntlDateFormatter is available and we have the + * right information to be able to use it. + */ + public function canUseIntl() { + return class_exists('IntlDateFormatter') && !empty($this->calendar) && !empty($this->langcode) && !empty($this->country); + } + + /** + * Format the date for display. + * + * Use the IntlDateFormatter to display the format, if possible. + * Because the IntlDateFormatter is not always available, we + * add an optional array of settings that provides the information + * the IntlDateFormatter will need. + * + * @param string $format + * A format string using either PHP's date() or the + * IntlDateFormatter() format. + * @param array $settings + * - string $format_string_type + * Which pattern is used by the format string. When using the + * Intl formatter, the format string must use the Intl pattern, + * which is different from the pattern used by the DateTime + * format function. Defaults to DateTimePlus::PHP. + * - string $timezone + * A timezone name. Defaults to the timezone of the date object. + * - string $langcode + * The two letter language code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $country + * The two letter country code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $calendar + * A calendar name to use for the date, Defaults to + * DateTimePlus::CALENDAR. + * - int $date_type + * The date type to use in the formatter, defaults to + * IntlDateFormatter::FULL. + * - int $time_type + * The date type to use in the formatter, defaults to + * IntlDateFormatter::FULL. + * - boolean $lenient + * Whether or not to use lenient processing in the intl + * formatter. Defaults to FALSE; + * + * @return string + * The formatted value of the date. + */ + public function format($format, $settings = array()) { + + // If there were construction errors, we can't format the date. + if ($this->hasErrors()) { + return; + } + + $format_string_type = isset($settings['format_string_type']) ? $settings['format_string_type'] : static::PHP; + $langcode = !empty($settings['langcode']) ? $settings['langcode'] : $this->langcode; + $country = !empty($settings['country']) ? $settings['country'] : $this->country; + $calendar = !empty($settings['calendar']) ? $settings['calendar'] : $this->calendar; + $time_zone = !empty($settings['timezone']) ? $settings['timezone'] : $this->getTimezone()->getName(); + $lenient = !empty($settings['lenient']) ? $settings['lenient'] : FALSE; + + // Format the date and catch errors. + try { + + // If we have what we need to use the IntlDateFormatter, do so. + if ($this->canUseIntl() && $format_string_type == static::INTL) { + + // Construct the $locale variable needed by the IntlDateFormatter. + $locale = $langcode . '_' . $country; + + // If we have information about a calendar, add it. + if (!empty($calendar) && $calendar != static::CALENDAR) { + $locale .= '@calendar=' . $calendar; + } + + // If we're working with a non-gregorian calendar, indicate that. + $calendar_type = \IntlDateFormatter::GREGORIAN; + if ($calendar != SELF::CALENDAR) { + $calendar_type = \IntlDateFormatter::TRADITIONAL; + } + + $date_type = !empty($settings['date_type']) ? $settings['date_type'] : \IntlDateFormatter::FULL; + $time_type = !empty($settings['time_type']) ? $settings['time_type'] : \IntlDateFormatter::FULL; + $formatter = new \IntlDateFormatter($locale, $date_type, $time_type, $time_zone, $calendar_type); + $formatter->setLenient($lenient); + $value = $formatter->format($format); + } + + // Otherwise, use the parent method. + else { + $value = parent::format($format); + } + } + catch (\Exception $e) { + $this->errors[] = $e->getMessage(); + } + return $value; + } +} diff --git a/core/lib/Drupal/Core/Datetime/DrupalDateTime.php b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php new file mode 100644 index 0000000..6d8b128 --- /dev/null +++ b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php @@ -0,0 +1,169 @@ + 2014, 'month => 4). + * Defaults to 'now'. + * @param mixed $timezone + * PHP DateTimeZone object, string or NULL allowed. + * Defaults to NULL. + * @param string $format + * PHP date() type format for parsing the input. This is recommended + * to use things like negative years, which php's parser fails on, or + * any other specialized input with a known format. If provided the + * date will be created using the createFromFormat() method. + * Defaults to NULL. + * @see http://us3.php.net/manual/en/datetime.createfromformat.php + * @param array $settings + * - boolean $validate_format + * The format used in createFromFormat() allows slightly different + * values than format(). If we use an input format that works in + * both functions we can add a validation step to confirm that the + * date created from a format string exactly matches the input. + * We need to know if this can be relied on to do that validation. + * Defaults to TRUE. + * - string $langcode + * The two letter language code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $country + * The two letter country code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $calendar + * A calendar name to use for the date, Defaults to + * DateTimePlus::CALENDAR. + * - boolean $debug + * Leave evidence of the input values in the resulting object + * for debugging purposes. Defaults to FALSE. + */ + public function __construct($time = 'now', $timezone = NULL, $format = NULL, $settings = array()) { + + // We can set the langcode and country using Drupal values. + $settings['langcode'] = !empty($settings['langcode']) ? $settings['langcode'] : language(LANGUAGE_TYPE_INTERFACE)->langcode; + $settings['country'] = !empty($settings['country']) ? $settings['country'] : variable_get('site_default_country'); + + // Instantiate the parent class. + parent::__construct($time, $timezone, $format, $settings); + + } + + /** + * Override basic component timezone handling to use Drupal's + * knowledge of the preferred user timezone. + */ + protected function prepareTimezone($timezone) { + $user_timezone = drupal_get_user_timezone(); + if (empty($timezone) && !empty($user_timezone)) { + $timezone = $user_timezone; + } + parent::prepareTimezone($timezone); + } + + /** + * Format the date for display. + * + * Use the IntlDateFormatter to display the format, if available. + * Because the IntlDateFormatter is not always available, we + * need to know whether the $format string uses the standard + * format strings used by the date() function or the alternative + * format provided by the IntlDateFormatter. + * + * @param string $format + * A format string using either date() or IntlDateFormatter() + * format. + * @param array $settings + * - string $format_string_type + * Which pattern is used by the format string. When using the + * Intl formatter, the format string must use the Intl pattern, + * which is different from the pattern used by the DateTime + * format function. Defaults to DateTimePlus::PHP. + * - string $timezone + * A timezone name. Defaults to the timezone of the date object. + * - string $langcode + * The two letter language code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $country + * The two letter country code to construct the locale string by the + * intlDateFormatter class. Used to control the result of the + * format() method if that class is available. Defaults to NULL. + * - string $calendar + * A calendar name to use for the date, Defaults to + * DateTimePlus::CALENDAR. + * - int $datetype + * The datetype to use in the formatter, defaults to + * IntlDateFormatter::FULL. + * - int $timetype + * The datetype to use in the formatter, defaults to + * IntlDateFormatter::FULL. + * - boolean $lenient + * Whether or not to use lenient processing in the intl + * formatter. Defaults to FALSE; + * + * @return string + * The formatted value of the date. + */ + public function format($format, $settings = array()) { + + $format_string_type = isset($settings['format_string_type']) ? $settings['format_string_type'] : static::PHP; + + $settings['langcode'] = !empty($settings['langcode']) ? $settings['langcode'] : $this->langcode; + $settings['country'] = !empty($settings['country']) ? $settings['country'] : $this->country; + + // Format the date and catch errors. + try { + + // If we have what we need to use the IntlDateFormatter, do so. + if ($this->canUseIntl() && $format_string_type == parent::INTL) { + $value = parent::format($format, $settings); + } + + // Otherwise, use the default Drupal method. + else { + + // Encode markers that should be translated. 'A' becomes + // '\xEF\AA\xFF'. xEF and xFF are invalid UTF-8 sequences, + // and we assume they are not in the input string. + // Paired backslashes are isolated to prevent errors in + // read-ahead evaluation. The read-ahead expression ensures that + // A matches, but not \A. + $format = preg_replace(array('/\\\\\\\\/', '/(?errors[] = $e->getMessage(); + } + return $value; + } +} diff --git a/core/lib/Drupal/Core/TypedData/Type/Date.php b/core/lib/Drupal/Core/TypedData/Type/Date.php index 07767e9..3c0f3a9 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Date.php +++ b/core/lib/Drupal/Core/TypedData/Type/Date.php @@ -7,16 +7,17 @@ namespace Drupal\Core\TypedData\Type; +use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\TypedData\TypedDataInterface; -use DateTime; use InvalidArgumentException; /** * The date data type. * - * The plain value of a date is an instance of the DateTime class. For setting - * the value an instance of the DateTime class, any string supported by - * DateTime::__construct(), or a timestamp as integer may be passed. + * The plain value of a date is an instance of the DrupalDateTime class. For setting + * the value any value supported by the __construct() of the DrupalDateTime + * class will work, including a DateTime object, a timestamp, a string + * date, or an array of date parts. */ class Date extends TypedData implements TypedDataInterface { @@ -31,18 +32,17 @@ class Date extends TypedData implements TypedDataInterface { * Implements TypedDataInterface::setValue(). */ public function setValue($value) { - if ($value instanceof DateTime || !isset($value)) { + + // Don't try to create a date from an empty value. + // It would default to the current time. + if (!isset($value)) { $this->value = $value; } - // Treat integer values as timestamps, even if supplied as PHP string. - elseif ((string) (int) $value === (string) $value) { - $this->value = new DateTime('@' . $value); - } - elseif (is_string($value)) { - $this->value = new DateTime($value); - } else { - throw new InvalidArgumentException("Invalid date format given."); + $this->value = $value instanceOf DrupalDateTime ? $value : new DrupalDateTime($value); + if ($this->value->hasErrors()) { + throw new InvalidArgumentException("Invalid date format given."); + } } } @@ -50,7 +50,7 @@ public function setValue($value) { * Implements TypedDataInterface::getString(). */ public function getString() { - return (string) $this->getValue()->format(DateTime::ISO8601); + return (string) $this->getValue(); } /** diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index cead0cd..3b9fa9b 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -7,6 +7,7 @@ namespace Drupal\comment; +use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityFormController; @@ -236,7 +237,8 @@ public function validate(array $form, array &$form_state) { $account = user_load_by_name($form_state['values']['name']); $form_state['values']['uid'] = $account ? $account->uid : 0; - if ($form_state['values']['date'] && strtotime($form_state['values']['date']) === FALSE) { + $date = new DrupalDateTime($form_state['values']['date']); + if ($date->hasErrors()) { form_set_error('date', t('You have to specify a valid date.')); } if ($form_state['values']['name'] && !$form_state['values']['is_anonymous'] && !$account) { @@ -271,7 +273,8 @@ public function submit(array $form, array &$form_state) { if (empty($comment->date)) { $comment->date = 'now'; } - $comment->created = strtotime($comment->date); + $date = new DrupalDateTime($comment->date); + $comment->created = $date->getTimestamp(); $comment->changed = REQUEST_TIME; // If the comment was posted by a registered user, assign the author's ID. diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index 5add3df..ff7c775 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -7,6 +7,7 @@ namespace Drupal\node; +use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityFormController; @@ -288,7 +289,8 @@ public function validate(array $form, array &$form_state) { } // Validate the "authored on" field. - if (!empty($node->date) && strtotime($node->date) === FALSE) { + $date = new DrupalDateTime($node->date); + if ($date->hasErrors()) { form_set_error('date', t('You have to specify a valid date.')); } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index a997a04..e7670d6 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -14,6 +14,7 @@ use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Database\Query\SelectExtender; use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Template\Attribute; use Drupal\node\Plugin\Core\Entity\Node; use Drupal\file\Plugin\Core\Entity\File; @@ -1025,7 +1026,8 @@ function node_submit(Node $node) { $node->revision_uid = $user->uid; } - $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME; + $node_created = new DrupalDateTime(!empty($node->date) ? $node->date : REQUEST_TIME); + $node->created = $node_created->getTimestamp(); $node->validated = TRUE; return $node; diff --git a/core/modules/system/lib/Drupal/system/Tests/Datetime/DateTimePlusTest.php b/core/modules/system/lib/Drupal/system/Tests/Datetime/DateTimePlusTest.php new file mode 100644 index 0000000..9eb0162 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Datetime/DateTimePlusTest.php @@ -0,0 +1,418 @@ + 'DateTimePlus', + 'description' => 'Test DateTimePlus functionality.', + 'group' => 'Datetime', + ); + } + + /** + * Set up required modules. + */ + public static $modules = array(); + + /** + * Test creating dates from string input. + */ + public function testDateStrings() { + + // Create date object from datetime string. + $input = '2009-03-07 10:30'; + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2009-03-07T10:30:00-06:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + // Same during daylight savings time. + $input = '2009-06-07 10:30'; + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2009-06-07T10:30:00-05:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + // Create date object from date string. + $input = '2009-03-07'; + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2009-03-07T00:00:00-06:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + // Same during daylight savings time. + $input = '2009-06-07'; + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2009-06-07T00:00:00-05:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + // Create date object from date string. + $input = '2009-03-07 10:30'; + $timezone = 'Australia/Canberra'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2009-03-07T10:30:00+11:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + // Same during daylight savings time. + $input = '2009-06-07 10:30'; + $timezone = 'Australia/Canberra'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2009-06-07T10:30:00+10:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + } + + /** + * Test creating dates from arrays of date parts. + */ + function testDateArrays() { + + // Create date object from date array, date only. + $input = array('year' => 2010, 'month' => 2, 'day' => 28); + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2010-02-28T00:00:00-06:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus(array('year' => 2010, 'month' => 2, 'day' => 28), $timezone): should be $expected, found $value."); + + // Create date object from date array with hour. + $input = array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10); + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2010-02-28T10:00:00-06:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus(array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10), $timezone): should be $expected, found $value."); + + // Create date object from date array, date only. + $input = array('year' => 2010, 'month' => 2, 'day' => 28); + $timezone = 'Europe/Berlin'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2010-02-28T00:00:00+01:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus(array('year' => 2010, 'month' => 2, 'day' => 28), $timezone): should be $expected, found $value."); + + // Create date object from date array with hour. + $input = array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10); + $timezone = 'Europe/Berlin'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '2010-02-28T10:00:00+01:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus(array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10), $timezone): should be $expected, found $value."); + + } + + /** + * Test creating dates from timestamps. + */ + function testDateTimestamp() { + + // Create date object from a unix timestamp and display it in + // local time. + $input = 0; + $timezone = 'UTC'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '1970-01-01T00:00:00+00:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + $expected = 'UTC'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone is $value: should be $expected."); + $expected = 0; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset is $value: should be $expected."); + + $timezone = 'America/Los_Angeles'; + $date->setTimezone(new DateTimeZone($timezone)); + $value = $date->format('c'); + $expected = '1969-12-31T16:00:00-08:00'; + $this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value."); + + $expected = 'America/Los_Angeles'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone should be $expected, found $value."); + $expected = '-28800'; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset should be $expected, found $value."); + + // Create a date using the timestamp of zero, then display its + // value both in UTC and the local timezone. + $input = 0; + $timezone = 'America/Los_Angeles'; + $date = new DateTimePlus($input, $timezone); + $offset = $date->getOffset(); + $value = $date->format('c'); + $expected = '1969-12-31T16:00:00-08:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone): should be $expected, found $value."); + + $expected = 'America/Los_Angeles'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone should be $expected, found $value."); + $expected = '-28800'; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset should be $expected, found $value."); + + $timezone = 'UTC'; + $date->setTimezone(new DateTimeZone($timezone)); + $value = $date->format('c'); + $expected = '1970-01-01T00:00:00+00:00'; + $this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value."); + + $expected = 'UTC'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone should be $expected, found $value."); + $expected = '0'; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset should be $expected, found $value."); + } + + /** + * Test timezone manipulation. + */ + function testTimezoneConversion() { + + // Create date object from datetime string in UTC, and convert + // it to a local date. + $input = '1970-01-01 00:00:00'; + $timezone = 'UTC'; + $date = new DateTimePlus($input, $timezone); + $value = $date->format('c'); + $expected = '1970-01-01T00:00:00+00:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus('$input', '$timezone'): should be $expected, found $value."); + + $expected = 'UTC'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone is $value: should be $expected."); + $expected = 0; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset is $value: should be $expected."); + + $timezone = 'America/Los_Angeles'; + $date->setTimezone(new DateTimeZone($timezone)); + $value = $date->format('c'); + $expected = '1969-12-31T16:00:00-08:00'; + $this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value."); + + $expected = 'America/Los_Angeles'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone should be $expected, found $value."); + $expected = '-28800'; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset should be $expected, found $value."); + + // Convert the local time to UTC using string input. + $input = '1969-12-31 16:00:00'; + $timezone = 'America/Los_Angeles'; + $date = new DateTimePlus($input, $timezone); + $offset = $date->getOffset(); + $value = $date->format('c'); + $expected = '1969-12-31T16:00:00-08:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus('$input', '$timezone'): should be $expected, found $value."); + + $expected = 'America/Los_Angeles'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone should be $expected, found $value."); + $expected = '-28800'; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset should be $expected, found $value."); + + $timezone = 'UTC'; + $date->setTimezone(new DateTimeZone($timezone)); + $value = $date->format('c'); + $expected = '1970-01-01T00:00:00+00:00'; + $this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value."); + + $expected = 'UTC'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone should be $expected, found $value."); + $expected = '0'; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset should be $expected, found $value."); + + // Convert the local time to UTC using string input. + $input = '1969-12-31 16:00:00'; + $timezone = 'Europe/Warsaw'; + $date = new DateTimePlus($input, $timezone); + $offset = $date->getOffset(); + $value = $date->format('c'); + $expected = '1969-12-31T16:00:00+01:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus('$input', '$timezone'): should be $expected, found $value."); + + $expected = 'Europe/Warsaw'; + $value = $date->getTimeZone()->getName(); + $this->assertEqual($expected, $value, "The current timezone should be $expected, found $value."); + $expected = '+3600'; + $value = $date->getOffset(); + $this->assertEqual($expected, $value, "The current offset should be $expected, found $value."); + + } + + /** + * Test creating dates from format strings. + */ + function testDateFormat() { + + // Create a year-only date. + $input = '2009'; + $timezone = NULL; + $format = 'Y'; + $date = new DateTimePlus($input, $timezone, $format); + $value = $date->format('Y'); + $expected = '2009'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone, $format): should be $expected, found $value."); + + // Create a month and year-only date. + $input = '2009-10'; + $timezone = NULL; + $format = 'Y-m'; + $date = new DateTimePlus($input, $timezone, $format); + $value = $date->format('Y-m'); + $expected = '2009-10'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone, $format): should be $expected, found $value."); + + // Create a time-only date. + $input = 'T10:30:00'; + $timezone = NULL; + $format = '\TH:i:s'; + $date = new DateTimePlus($input, $timezone, $format); + $value = $date->format('H:i:s'); + $expected = '10:30:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone, $format): should be $expected, found $value."); + + // Create a time-only date. + $input = '10:30:00'; + $timezone = NULL; + $format = 'H:i:s'; + $date = new DateTimePlus($input, $timezone, $format); + $value = $date->format('H:i:s'); + $expected = '10:30:00'; + $this->assertEqual($expected, $value, "Test new DateTimePlus($input, $timezone, $format): should be $expected, found $value."); + + } + + /** + * Test invalid date handling. + */ + function testInvalidDates() { + + // Test for invalid month names when we are using a short version + // of the month. + $input = '23 abc 2012'; + $timezone = NULL; + $format = 'd M Y'; + $date = new DateTimePlus($input, $timezone, $format); + $this->assertNotEqual(count($date->getErrors()), 0, "$input contains an invalid month name and produces errors."); + + // Test for invalid hour. + $input = '0000-00-00T45:30:00'; + $timezone = NULL; + $format = 'Y-m-d\TH:i:s'; + $date = new DateTimePlus($input, $timezone, $format); + $this->assertNotEqual(count($date->getErrors()), 0, "$input contains an invalid hour and produces errors."); + + // Test for invalid day. + $input = '0000-00-99T05:30:00'; + $timezone = NULL; + $format = 'Y-m-d\TH:i:s'; + $date = new DateTimePlus($input, $timezone, $format); + $this->assertNotEqual(count($date->getErrors()), 0, "$input contains an invalid day and produces errors."); + + // Test for invalid month. + $input = '0000-75-00T15:30:00'; + $timezone = NULL; + $format = 'Y-m-d\TH:i:s'; + $date = new DateTimePlus($input, $timezone, $format); + $this->assertNotEqual(count($date->getErrors()), 0, "$input contains an invalid month and produces errors."); + + // Test for invalid year. + $input = '11-08-01T15:30:00'; + $timezone = NULL; + $format = 'Y-m-d\TH:i:s'; + $date = new DateTimePlus($input, $timezone, $format); + $this->assertNotEqual(count($date->getErrors()), 0, "$input contains an invalid year and produces errors."); + + // Test for invalid year from date array. 10000 as a year will + // create an exception error in the PHP DateTime object. + $input = array('year' => 10000, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0); + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $this->assertNotEqual(count($date->getErrors()), 0, "array('year' => 10000, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0) contains an invalid year and produces errors."); + + // Test for invalid month from date array. + $input = array('year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0); + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $this->assertNotEqual(count($date->getErrors()), 0, "array('year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0) contains an invalid month and produces errors."); + + // Test for invalid hour from date array. + $input = array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0); + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $this->assertNotEqual(count($date->getErrors()), 0, "array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0) contains an invalid hour and produces errors."); + + // Test for invalid minute from date array. + $input = array('year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0); + $timezone = 'America/Chicago'; + $date = new DateTimePlus($input, $timezone); + $this->assertNotEqual(count($date->getErrors()), 0, "array('year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0) contains an invalid minute and produces errors."); + + } + + /** + * Test that DrupalDateTime can detect the right timezone to use. + * When specified or not. + */ + public function testDateTimezone() { + global $user; + + $date_string = '2007-01-31 21:00:00'; + + // Detect the system timezone. + $system_timezone = date_default_timezone_get(); + + // Create a date object with an unspecified timezone, which should + // end up using the system timezone. + $date = new DateTimePlus($date_string); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == $system_timezone, 'DateTimePlus uses the system timezone when there is no site timezone.'); + + // Create a date object with a specified timezone name. + $date = new DateTimePlus($date_string, 'America/Yellowknife'); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == 'America/Yellowknife', 'DateTimePlus uses the specified timezone if provided.'); + + // Create a date object with a timezone object. + $date = new DateTimePlus($date_string, new \DateTimeZone('Australia/Canberra')); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == 'Australia/Canberra', 'DateTimePlus uses the specified timezone if provided.'); + + // Create a date object with another date object. + $new_date = new DateTimePlus('now', 'Pacific/Midway'); + $date = new DateTimePlus($new_date); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == 'Pacific/Midway', 'DateTimePlus uses the specified timezone if provided.'); + + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Datetime/DrupalDateTimeTest.php b/core/modules/system/lib/Drupal/system/Tests/Datetime/DrupalDateTimeTest.php new file mode 100644 index 0000000..ea10896 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Datetime/DrupalDateTimeTest.php @@ -0,0 +1,110 @@ + 'DrupalDateTime', + 'description' => 'Test DrupalDateTime functionality.', + 'group' => 'Datetime', + ); + } + + /** + * Set up required modules. + */ + public static $modules = array(); + + /** + * Test setup. + */ + public function setUp() { + parent::setUp(); + + } + + /** + * Test that DrupalDateTime can detect the right timezone to use. + * Test with a variety of less commonly used timezone names to + * help ensure that the system timezone will be different than the + * stated timezones. + */ + public function testDateTimezone() { + global $user; + + $date_string = '2007-01-31 21:00:00'; + + // Make sure no site timezone has been set. + variable_set('date_default_timezone', NULL); + variable_set('configurable_timezones', 0); + + // Detect the system timezone. + $system_timezone = date_default_timezone_get(); + + // Create a date object with an unspecified timezone, which should + // end up using the system timezone. + $date = new DrupalDateTime($date_string); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == $system_timezone, 'DrupalDateTime uses the system timezone when there is no site timezone.'); + + // Create a date object with a specified timezone. + $date = new DrupalDateTime($date_string, 'America/Yellowknife'); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == 'America/Yellowknife', 'DrupalDateTime uses the specified timezone if provided.'); + + // Set a site timezone. + variable_set('date_default_timezone', 'Europe/Warsaw'); + + // Create a date object with an unspecified timezone, which should + // end up using the site timezone. + $date = new DrupalDateTime($date_string); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == 'Europe/Warsaw', 'DrupalDateTime uses the site timezone if provided.'); + + // Create user. + variable_set('configurable_timezones', 1); + $test_user = $this->drupalCreateUser(array()); + $this->drupalLogin($test_user); + + // Set up the user with a different timezone than the site. + $edit = array('mail' => $test_user->mail, 'timezone' => 'Asia/Manila'); + $this->drupalPost('user/' . $test_user->uid . '/edit', $edit, t('Save')); + + // Disable session saving as we are about to modify the global $user. + drupal_save_session(FALSE); + // Save the original user and then replace it with the test user. + $real_user = $user; + $user = user_load($test_user->uid, TRUE); + + // Simulate a Drupal bootstrap with the logged-in user. + date_default_timezone_set(drupal_get_user_timezone()); + + // Create a date object with an unspecified timezone, which should + // end up using the user timezone. + + $date = new DrupalDateTime($date_string); + $timezone = $date->getTimezone()->getName(); + $this->assertTrue($timezone == 'Asia/Manila', 'DrupalDateTime uses the user timezone, if configurable timezones are used and it is set.'); + + // Restore the original user, and enable session saving. + $user = $real_user; + // Restore default time zone. + date_default_timezone_set(drupal_get_user_timezone()); + drupal_save_session(TRUE); + + + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php index 62bd35e..00e4c95 100644 --- a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php @@ -8,7 +8,7 @@ namespace Drupal\system\Tests\TypedData; use Drupal\simpletest\WebTestBase; -use DateTime; +use Drupal\Core\Datetime\DrupalDateTime; use DateInterval; /** @@ -72,7 +72,7 @@ public function testGetAndSet() { $this->assertNull($wrapper->getValue(), 'Float wrapper is null-able.'); // Date type. - $value = new DateTime('@' . REQUEST_TIME); + $value = new DrupalDateTime(REQUEST_TIME); $wrapper = $this->createTypedData(array('type' => 'date'), $value); $this->assertTrue($wrapper->getValue() === $value, 'Date value was fetched.'); $new_value = REQUEST_TIME + 1;