diff --git a/core/includes/common.inc b/core/includes/common.inc index 022303e..f89d11a 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1897,21 +1897,42 @@ function format_interval($interval, $granularity = 2, $langcode = NULL) { * @param $langcode * (optional) Language code to translate to. Defaults to the language used to * display the page. + * @params 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 $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 to use for the date, Defaults to the site + * default calendar. + * - int $date_type + * The datetype to use in the formatter, defaults to + * IntlDateFormatter::FULL. + * - int $time_type + * 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 * A translated date string in the requested format. */ -function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { +function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL, $settings = array()) { + // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { $drupal_static_fast['timezones'] = &drupal_static(__FUNCTION__); } $timezones = &$drupal_static_fast['timezones']; + $timezone = !empty($timezone) ? $timezone : date_default_timezone_get(); - if (!isset($timezone)) { - $timezone = date_default_timezone_get(); - } // Store DateTimeZone objects in an array rather than repeatedly // constructing identical objects over the life of a request. if (!isset($timezones[$timezone])) { @@ -1922,45 +1943,72 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL $langcode = language(LANGUAGE_TYPE_INTERFACE)->langcode; } + if (empty($settings['calendar'])) { + $settings['calendar'] = config('system.calendar')->get('calendar'); + } + + // Adjust $settings to include all the values the Dateobject will need. + $settings += array( + 'timezone' => $timezone, + 'langcode' => $langcode, + ); + + // Format strings and settings differ for the IntlDateFormatter + // and the normal PHP format() method. + $intl_format = ''; + $intl_type = ''; + switch ($type) { case 'short': $format = variable_get('date_format_short', 'm/d/Y - H:i'); + $intl_format = ''; + $intl_type = 'SHORT'; break; case 'long': $format = variable_get('date_format_long', 'l, F j, Y - H:i'); + $intl_format = ''; + $intl_type = 'LONG'; break; case 'html_datetime': $format = variable_get('date_format_html_datetime', 'Y-m-d\TH:i:sO'); + $intl_format = "yyyy-MM-dd'Tkk:mm:ssZZ"; break; case 'html_date': $format = variable_get('date_format_html_date', 'Y-m-d'); + $intl_format = "yyyy-MM-dd"; break; case 'html_time': $format = variable_get('date_format_html_time', 'H:i:s'); + $intl_format = "H:mm:ss"; break; case 'html_yearless_date': $format = variable_get('date_format_html_yearless_date', 'm-d'); + $intl_format = "MM-d"; break; case 'html_week': $format = variable_get('date_format_html_week', 'Y-\WW'); + $intl_format = "Y-'WW"; break; case 'html_month': $format = variable_get('date_format_html_month', 'Y-m'); + $intl_format = "Y-MM"; break; case 'html_year': $format = variable_get('date_format_html_year', 'Y'); + $intl_format = "Y"; break; case 'custom': // No change to format. + $intl_format = $format; break; case 'medium': @@ -1968,20 +2016,44 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL // Retrieve the format of the custom $type passed. if ($type != 'medium') { $format = variable_get('date_format_' . $type, ''); + $intl_format = $format; } // Fall back to 'medium'. if ($format === '') { $format = variable_get('date_format_medium', 'D, m/d/Y - H:i'); + $intl_format = ''; + $intl_type = 'MEDIUM'; } break; } + // Fix settings for the IntlDateFormatter. + // We can only use these constants if the IntlDateFormatter + // exists. + if (class_exists('IntlDateFormatter')) { + switch ($intl_type) { + case 'SHORT': + $settings['date_type'] = IntlDateFormatter::SHORT; + $settings['time_type'] = IntlDateFormatter::SHORT; + break; + case 'MEDIUM': + $settings['date_type'] = IntlDateFormatter::MEDIUM; + $settings['time_type'] = IntlDateFormatter::SHORT; + break; + case 'LONG': + $settings['date_type'] = IntlDateFormatter::LONG; + $settings['time_type'] = IntlDateFormatter::SHORT; + break; + } + $format = $intl_format; + $settings['format_string_type'] = DrupalDateTime::INTL; + } + // Create a DrupalDateTime object from the timestamp and timezone. - $date_time = new DrupalDateTime($timestamp, $timezones[$timezone]); + $date = new \Drupal\Core\Datetime\DrupalDateTime($timestamp, $timezones[$timezone]); - // Call date_format(). - $settings = array('langcode' => $langcode); - return $date_time->format($format, $settings); + // Call $date->format(). + return $date->format($format, $settings); } /** diff --git a/core/includes/form.inc b/core/includes/form.inc index 49ace44..71bd93d 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -2951,12 +2951,19 @@ function theme_date($variables) { * Expands a date element into year, month, and day select elements. */ function form_process_date($element) { + $calendar = system_calendar(); + + // If empty, this will default to the current date. + // Using the full path to the class so we don't have to use + // this file unless needed. + $date = new \Drupal\Core\Datetime\DrupalDateTime($element['#value']); + // Default to current date if (empty($element['#value'])) { $element['#value'] = array( - 'day' => format_date(REQUEST_TIME, 'custom', 'j'), - 'month' => format_date(REQUEST_TIME, 'custom', 'n'), - 'year' => format_date(REQUEST_TIME, 'custom', 'Y'), + 'day' => $date->format('j'), + 'month' => $date->format('n'), + 'year' => $date->format('Y'), ); } @@ -2975,17 +2982,17 @@ function form_process_date($element) { foreach ($order as $type) { switch ($type) { case 'day': - $options = drupal_map_assoc(range(1, 31)); + $options = $calendar->days($element['#required']); $title = t('Day'); break; case 'month': - $options = drupal_map_assoc(range(1, 12), 'map_month'); + $options = $calendar->months_names_abbr($element['#required']); $title = t('Month'); break; case 'year': - $options = drupal_map_assoc(range(1900, 2050)); + $options = $calendar->years(1900, 2050, $element['#required']); $title = t('Year'); break; } @@ -3007,35 +3014,13 @@ function form_process_date($element) { * Validates the date type to prevent invalid dates (e.g., February 30, 2006). */ function date_validate($element) { - if (!checkdate($element['#value']['month'], $element['#value']['day'], $element['#value']['year'])) { + $date = new \Drupal\Core\Datetime\DrupalDateTime($element['#value']); + if ($date->hasErrors()) { form_error($element, t('The specified date is invalid.')); } } /** - * Renders a month name for display. - * - * Callback for drupal_map_assoc() within form_process_date(). - */ -function map_month($month) { - $months = &drupal_static(__FUNCTION__, array( - 1 => 'Jan', - 2 => 'Feb', - 3 => 'Mar', - 4 => 'Apr', - 5 => 'May', - 6 => 'Jun', - 7 => 'Jul', - 8 => 'Aug', - 9 => 'Sep', - 10 => 'Oct', - 11 => 'Nov', - 12 => 'Dec', - )); - return t($months[$month]); -} - -/** * Sets the value for a weight element, with zero as a default. */ function weight_value(&$form) { diff --git a/core/includes/theme.inc b/core/includes/theme.inc index cc29a98..d63215a 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1479,7 +1479,7 @@ function template_preprocess_datetime(&$variables) { // Format the 'datetime' attribute based on the timestamp. // @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime if (!isset($variables['attributes']['datetime']) && isset($variables['timestamp'])) { - $variables['attributes']['datetime'] = format_date($variables['timestamp'], 'html_datetime', '', 'UTC'); + $variables['attributes']['datetime'] = format_date($variables['timestamp'], 'html_datetime', '', 'UTC')); } // If no text was provided, try to auto-generate it. diff --git a/core/lib/Drupal/Core/Datetime/Plugin/DateCalendar.php b/core/lib/Drupal/Core/Datetime/Plugin/DateCalendar.php new file mode 100644 index 0000000..e445b41 --- /dev/null +++ b/core/lib/Drupal/Core/Datetime/Plugin/DateCalendar.php @@ -0,0 +1,55 @@ +discovery = new HookDiscovery('date_calendar_info'); + $this->factory = new DefaultFactory($this->discovery); + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::createInstance(). + * + * @param string $plugin_id + * The id of a plugin, i.e. the calendar type. + * @param array $configuration + * Not used in this plugin. + * + * @return Drupal\Core\TypedData\TypedDataInterface + */ + public function createInstance($plugin_id, array $configuration = array()) { + + // A missing or invalid calendar $plugin_id creates serious problems, + // so check first and swap in the known default calendar if the + // value won't work. + $plugins = array_keys($this->getDefinitions()); + if (!in_array($plugin_id, $plugins)) { + watchdog('datetime', t("The system was unable to use the '@calendar' calendar. Using @default calendar instead.", array('@calendar' => $plugin_id, '@default' => self::DEFAULT_CALENDAR))); + $plugin_id = self::DEFAULT_CALENDAR; + } + return $this->factory->createInstance($plugin_id, $configuration); + } + +} diff --git a/core/lib/Drupal/Core/Datetime/Plugin/DateCalendarInterface.php b/core/lib/Drupal/Core/Datetime/Plugin/DateCalendarInterface.php new file mode 100644 index 0000000..234557b --- /dev/null +++ b/core/lib/Drupal/Core/Datetime/Plugin/DateCalendarInterface.php @@ -0,0 +1,269 @@ + 'January', + 2 => 'February', + 3 => 'March', + 4 => 'April', + 5 => 'May', + 6 => 'June', + 7 => 'July', + 8 => 'August', + 9 => 'September', + 10 => 'October', + 11 => 'November', + 12 => 'December', + ); + } + + /** + * Constructs an untranslated array of abbreviated month names. + * + * @return array + * An array of month names. + */ + public static function month_names_abbr_untranslated() { + // Force the key to use the correct month value, rather than + // starting with zero. + return array( + 1 => 'Jan', + 2 => 'Feb', + 3 => 'Mar', + 4 => 'Apr', + 5 => 'May', + 6 => 'Jun', + 7 => 'Jul', + 8 => 'Aug', + 9 => 'Sep', + 10 => 'Oct', + 11 => 'Nov', + 12 => 'Dec', + ); + } + + /** + * Constructs an untranslated array of week days. + * + * @return array + * An array of week day names + */ + public static function week_days_untranslated() { + return array( + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ); + } + + /** + * Returns a translated array of month names. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of month names. + */ + public static function month_names($required = FALSE) { + // Force the key to use the correct month value, rather than + // starting with zero. + $month_names = array( + 1 => t('January', array(), array('context' => 'Long month name')), + 2 => t('February', array(), array('context' => 'Long month name')), + 3 => t('March', array(), array('context' => 'Long month name')), + 4 => t('April', array(), array('context' => 'Long month name')), + 5 => t('May', array(), array('context' => 'Long month name')), + 6 => t('June', array(), array('context' => 'Long month name')), + 7 => t('July', array(), array('context' => 'Long month name')), + 8 => t('August', array(), array('context' => 'Long month name')), + 9 => t('September', array(), array('context' => 'Long month name')), + 10 => t('October', array(), array('context' => 'Long month name')), + 11 => t('November', array(), array('context' => 'Long month name')), + 12 => t('December', array(), array('context' => 'Long month name')), + ); + $none = array('' => ''); + return !$required ? $none + $month_names : $month_names; + } + + /** + * Constructs a translated array of month name abbreviations + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of month abbreviations. + */ + public static function month_names_abbr($required = FALSE) { + // Force the key to use the correct month value, rather than + // starting with zero. + $month_names = array( + 1 => t('Jan'), + 2 => t('Feb'), + 3 => t('Mar'), + 4 => t('Apr'), + 5 => t('May'), + 6 => t('Jun'), + 7 => t('Jul'), + 8 => t('Aug'), + 9 => t('Sep'), + 10 => t('Oct'), + 11 => t('Nov'), + 12 => t('Dec'), + ); + $none = array('' => ''); + return !$required ? $none + $month_names : $month_names; + } + + /** + * Returns a translated array of week names. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day names + */ + public static function week_days($required = FALSE) { + $weekdays = array( + t('Sunday'), + t('Monday'), + t('Tuesday'), + t('Wednesday'), + t('Thursday'), + t('Friday'), + t('Saturday'), + ); + $none = array('' => ''); + return !$required ? $none + $weekdays : $weekdays; + } + + /** + * Constructs a translated array of week day abbreviations. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day abbreviations + */ + public static function week_days_abbr($required = FALSE) { + $weekdays = array( + t('Sun', array(), array('context' => 'Sunday abbreviation')), + t('Mon', array(), array('context' => 'Monday abbreviation')), + t('Tue', array(), array('context' => 'Tuesday abbreviation')), + t('Wed', array(), array('context' => 'Wednesday abbreviation')), + t('Thu', array(), array('context' => 'Thursday abbreviation')), + t('Fri', array(), array('context' => 'Friday abbreviation')), + t('Sat', array(), array('context' => 'Saturday abbreviation')), + ); + $none = array('' => ''); + return !$required ? $none + $weekdays : $weekdays; + } + + /** + * Constructs a translated array of 2-letter week day abbreviations. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day 2 letter abbreviations + */ + public static function week_days_abbr2($required = FALSE) { + $weekdays = array( + t('Su', array(), array('context' => 'Sunday 2 letter abbreviation')), + t('Mo', array(), array('context' => 'Monday 2 letter abbreviation')), + t('Tu', array(), array('context' => 'Tuesday 2 letter abbreviation')), + t('We', array(), array('context' => 'Wednesday 2 letter abbreviation')), + t('Th', array(), array('context' => 'Thursday 2 letter abbreviation')), + t('Fr', array(), array('context' => 'Friday 2 letter abbreviation')), + t('Sa', array(), array('context' => 'Saturday 2 letter abbreviation')), + ); + $none = array('' => ''); + return !$required ? $none + $weekdays : $weekdays; + } + + /** + * Constructs a translated array of 1-letter week day abbreviations. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day 1 letter abbreviations + */ + public static function week_days_abbr1($required = FALSE) { + $weekdays = array( + t('S', array(), array('context' => 'Sunday 1 letter abbreviation')), + t('M', array(), array('context' => 'Monday 1 letter abbreviation')), + t('T', array(), array('context' => 'Tuesday 1 letter abbreviation')), + t('W', array(), array('context' => 'Wednesday 1 letter abbreviation')), + t('T', array(), array('context' => 'Thursday 1 letter abbreviation')), + t('F', array(), array('context' => 'Friday 1 letter abbreviation')), + t('S', array(), array('context' => 'Saturday 1 letter abbreviation')), + ); + $none = array('' => ''); + return !$required ? $none + $weekdays : $weekdays; + } + + /** + * Reorders weekdays to match the first day of the week. + * + * @param array $weekdays + * An array of weekdays. + * + * @return array + * An array of weekdays reordered to match the first day of the week. + */ + public static function week_days_ordered($weekdays) { + $first_day = variable_get('date_first_day', 0); + if ($first_day > 0) { + for ($i = 1; $i <= $first_day; $i++) { + $last = array_shift($weekdays); + array_push($weekdays, $last); + } + } + return $weekdays; + } + + /** + * Constructs an array of years in a specified range. + * + * @param int $min + * The minimum year in the array. + * @param int $max + * The maximum year in the array. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of years in the selected range. + */ + public static function years($min = 0, $max = 0, $required = FALSE) { + // Ensure $min and $max are valid values. + if (empty($min)) { + $min = intval(date('Y', REQUEST_TIME) - 3); + } + if (empty($max)) { + $max = intval(date('Y', REQUEST_TIME) + 3); + } + $none = array(0 => ''); + $range = drupal_map_assoc(range($min, $max)); + return !$required ? $none + $range : $range; + } + + /** + * Constructs an array of days in a month. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * @param int $month + * (optional) The month in which to find the number of days. + * @param int $year + * (optional) The year in which to find the number of days. + * + * @return array + * An array of days for the selected month. + */ + public static function days($required = FALSE, $month = NULL, $year = NULL) { + // If we have a month and year, find the right last day of the month. + if (!empty($month) && !empty($year)) { + $date = new DrupalDateTime($year . '-' . $month . '-01 00:00:00', 'UTC'); + $max = $date->format('t'); + } + // If there is no month and year given, default to 31. + if (empty($max)) { + $max = 31; + } + $none = array(0 => ''); + $range = drupal_map_assoc(range(1, $max)); + return !$required ? $none + $range : $range; + } + + + /** + * Constructs an array of hours. + * + * @param string $format + * A date format string that indicates the format to use for the hours. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of hours in the selected format. + */ + public static function hours($format = 'H', $required = FALSE) { + $hours = array(); + if ($format == 'h' || $format == 'g') { + $min = 1; + $max = 12; + } + else { + $min = 0; + $max = 23; + } + for ($i = $min; $i <= $max; $i++) { + $formatted = ($format == 'H' || $format == 'h') ? DrupalDateTime::datePad($i) : $i; + $hours[$i] = $formatted; + } + $none = array('' => ''); + return !$required ? $none + $hours : $hours; + } + + /** + * Constructs an array of minutes. + * + * @param string $format + * A date format string that indicates the format to use for the minutes. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * @param int $increment + * A the integer value to increment the values. Defaults to 1. + * + * @return array + * An array of minutes in the selected format. + */ + public static function minutes($format = 'i', $required = FALSE, $increment = 1) { + $minutes = array(); + // Ensure $increment has a value so we don't loop endlessly. + if (empty($increment)) { + $increment = 1; + } + for ($i = 0; $i < 60; $i += $increment) { + $formatted = $format == 'i' ? DrupalDateTime::datePad($i) : $i; + $minutes[$i] = $formatted; + } + $none = array('' => ''); + return !$required ? $none + $minutes : $minutes; + } + + /** + * Constructs an array of seconds. + * + * @param string $format + * A date format string that indicates the format to use for the seconds. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * @param int $increment + * A the integer value to increment the values. Defaults to 1. + * + * @return array + * An array of seconds in the selected format. + */ + public static function seconds($format = 's', $required = FALSE, $increment = 1) { + $seconds = array(); + // Ensure $increment has a value so we don't loop endlessly. + if (empty($increment)) { + $increment = 1; + } + for ($i = 0; $i < 60; $i += $increment) { + $formatted = $format == 's' ? DrupalDateTime::datePad($i) : $i; + $seconds[$i] = $formatted; + } + $none = array('' => ''); + return !$required ? $none + $seconds : $seconds; + } + + /** + * Constructs an array of AM and PM options. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of AM and PM options. + */ + public static function ampm($required = FALSE) { + $none = array('' => ''); + $ampm = array( + 'am' => t('am', array(), array('context' => 'ampm')), + 'pm' => t('pm', array(), array('context' => 'ampm')), + ); + return !$required ? $none + $ampm : $ampm; + } + + /** + * Identifies the number of days in a month for a date. + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * + * @return integer + * The number of days in the month. + */ + public static function days_in_month($date = NULL) { + if (!$date instanceOf DrupalDateTime) { + $date = new DrupalDateTime($date); + } + if (!$date->hasErrors()) { + return $date->format('t'); + } + return NULL; + } + + /** + * Identifies the number of days in a year for a date. + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * + * @return integer + * The number of days in the year. + */ + public static function days_in_year($date = NULL) { + if (!$date instanceOf DrupalDateTime) { + $date = new DrupalDateTime($date); + } + if (!$date->hasErrors()) { + if ($date->format('L')) { + return 366; + } + else { + return 365; + } + } + return NULL; + } + + /** + * Returns day of week for a given date (0 = Sunday). + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * + * @return int + * The number of the day in the week. + */ + public static function day_of_week($date = NULL) { + if (!$date instanceOf DrupalDateTime) { + $date = new DrupalDateTime($date); + } + if (!$date->hasErrors()) { + return $date->format('w'); + } + return NULL; + } + + /** + * Returns translated name of the day of week for a given date. + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * @param string $abbr + * (optional) Whether to return the abbreviated name for that day. + * Defaults to TRUE. + * + * @return string + * The name of the day in the week for that date. + */ + public static function day_of_week_name($date = NULL, $abbr = TRUE) { + if (!$date instanceOf DrupalDateTime) { + $date = new DrupalDateTime($date); + } + $dow = self::day_of_week($date); + $days = $abbr ? self::week_days_abbr() : self::week_days(); + return $days[$dow]; + } + +} diff --git a/core/modules/system/config/system.calendar.yml b/core/modules/system/config/system.calendar.yml new file mode 100644 index 0000000..a711b76 --- /dev/null +++ b/core/modules/system/config/system.calendar.yml @@ -0,0 +1 @@ +calendar: gregorian diff --git a/core/modules/system/lib/Drupal/system/Tests/Datetime/DateCalendarTest.php b/core/modules/system/lib/Drupal/system/Tests/Datetime/DateCalendarTest.php new file mode 100644 index 0000000..346abf9 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Datetime/DateCalendarTest.php @@ -0,0 +1,70 @@ + 'Date Calendar', + 'description' => 'Test Date Calendar functionality.', + 'group' => 'Datetime', + ); + } + + /** + * Set up required modules. + */ + public static $modules = array('datetime_test'); + + /** + * Test setup. + */ + public function setUp() { + parent::setUp(); + } + + /** + * Test . + */ + public function testDateCalendar() { + + // Test gregorian system calendar. + $plugin = new DateCalendar(); + $calendar = $plugin->createInstance('gregorian'); + $this->assertTrue(in_array('January', $calendar->month_names()), 'The gregorian calendar can be loaded.'); + + // Test alternate 'Gamma' calendar. + $plugin = new DateCalendar(); + $options = $plugin->getDefinitions(); + $this->assertTrue(array_key_exists('gamma', $options), 'An alternate calendar can be found.'); + + $calendar = $plugin->createInstance('gamma'); + $this->assertTrue(in_array('Alpha', $calendar->month_names()), 'An alternate calendar can be loaded.'); + + // Test that a bogus calendar is switched to gregorian. + $plugin = new DateCalendar(); + $calendar = $plugin->createInstance('bogus'); + $this->assertTrue(in_array('January', $calendar->month_names()), 'A nonexisting calendar is replaced with the gregorian calendar.'); + + + } + + /** + * Tear down after tests. + */ + public function tearDown() { + parent::tearDown(); + } +} diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index c55289c..53d967c 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -8,6 +8,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Drupal\Core\Datetime\DrupalDateTime; /** * Menu callback; Provide the administration overview page. @@ -1861,6 +1862,11 @@ function system_regional_settings() { // Date settings: $zones = system_time_zones(); + // Get calendar information. + $calendar_type = config('system.calendar')->get('calendar'); + $calendar_plugin = new \Drupal\Core\Datetime\Plugin\DateCalendar(); + $calendar = $calendar_plugin->createInstance($calendar_type); + $form['locale'] = array( '#type' => 'fieldset', '#title' => t('Locale'), @@ -1875,11 +1881,23 @@ function system_regional_settings() { '#attributes' => array('class' => array('country-detect')), ); + // Find out what calendar systems are available. + foreach ($calendar_plugin->getDefinitions() as $name => $definition) { + $options[$name] = $definition['label']; + } + $form['locale']['calendar'] = array( + '#type' => 'select', + '#title' => t('Default calendar system'), + '#default_value' => $calendar_type, + '#options' => $options, + ); + + // Get a list of day names using the site calendar. $form['locale']['date_first_day'] = array( '#type' => 'select', '#title' => t('First day of week'), '#default_value' => variable_get('date_first_day', 0), - '#options' => array(0 => t('Sunday'), 1 => t('Monday'), 2 => t('Tuesday'), 3 => t('Wednesday'), 4 => t('Thursday'), 5 => t('Friday'), 6 => t('Saturday')), + '#options' => $calendar->week_days(TRUE), ); $form['timezone'] = array( @@ -1930,10 +1948,23 @@ function system_regional_settings() { '#description' => t('Only applied if users may set their own time zone.') ); + $form['#submit'][] = 'system_calendar_settings_submit'; + return system_settings_form($form); } /** + * Form submission handler for system_calendar_settings(). + * + * @ingroup forms + */ +function system_calendar_settings_submit($form, &$form_state) { + $config = config('system.calendar'); + $config->set('calendar', $form_state['values']['calendar']); + $config->save(); +} + +/** * Form builder; Configure the site date and time settings. * * @ingroup forms diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index a9a2e17..32df69f 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -147,6 +147,34 @@ function hook_cron() { } /** + * Defines available calendar system information. + * + * Drupal provides a pluggable system for defining the calendar + * system used when displaying dates and date parts. Modules that provide + * information about calendar systems other than Gregorian can + * implement this hook and identify the calendar system being added + * and the values for days, weeks, and months in that system. + * + * @return array + * An associative array where the key is the calendar name and the value is + * again an associative array. Supported keys are: + * - label: The human readable label of the calendar system. + * - class: The associated calendar class. Must implement + * \Drupal\Core\Datetime\Plugin\DateCalendarInterface. + * + * @see system_calendar() + * @see \Drupal\Date\Datetime\Plugin\Type\Gregorian + */ +function hook_date_calendar_info() { + return array( + 'gregorian' => array( + 'label' => t('Gregorian'), + 'class' => '\Drupal\Core\Datetime\Plugin\Type\Gregorian', + ), + ); +} + +/** * Defines available data types for the typed data API. * * The typed data API allows modules to support any kind of data based upon diff --git a/core/modules/system/system.module b/core/modules/system/system.module index ea3dadc..29f404b 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -7,7 +7,6 @@ use Drupal\Core\Utility\ModuleInfo; use Drupal\Core\TypedData\Primitive; - use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -1995,6 +1994,42 @@ function system_stream_wrappers() { } /** + * Implements hook_date_calendar_info(). + */ +function system_date_calendar_info() { + return array( + 'gregorian' => array( + 'label' => t('Gregorian'), + 'class' => '\Drupal\Core\Datetime\Plugin\Type\Gregorian', + ), + ); +} + +/** +* Get system calendar information and store for future use. +*/ +function system_calendar($calendar = NULL) { + // Use the advanced drupal_static() pattern, since this could + // be called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['calendars'] = &drupal_static(__FUNCTION__); + } + $calendars = &$drupal_static_fast['calendars']; + + if (!isset($calendar)) { + $calendar = config('system.calendar')->get('calendar'); + } + // Store Calendar objects in an array rather than repeatedly + // constructing identical objects over the life of a request. + if (!isset($calendars[$calendar])) { + $plugin = new \Drupal\Core\Datetime\Plugin\DateCalendar(); + $calendars[$calendar] = $plugin->createInstance($calendar); + } + return $calendars[$calendar]; +} + +/** * Implements hook_data_type_info(). */ function system_data_type_info() { @@ -3539,7 +3574,7 @@ function system_time_zones($blank = NULL) { // expression. if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) { $zones[$zone] = t('@zone: @date', array('@zone' => t(str_replace('_', ' ', $zone)), '@date' => format_date(REQUEST_TIME, 'custom', variable_get('date_format_long', 'l, F j, Y - H:i') . ' O', $zone))); - } + } } // Sort the translated time zones alphabetically. asort($zones); diff --git a/core/modules/system/tests/modules/datetime_test/datetime_test.info b/core/modules/system/tests/modules/datetime_test/datetime_test.info new file mode 100644 index 0000000..127781f --- /dev/null +++ b/core/modules/system/tests/modules/datetime_test/datetime_test.info @@ -0,0 +1,6 @@ +name = "Datetime Test" +description = "Support module for Datetime tests." +core = 8.x +package = Testing +version = VERSION +hidden = TRUE diff --git a/core/modules/system/tests/modules/datetime_test/datetime_test.module b/core/modules/system/tests/modules/datetime_test/datetime_test.module new file mode 100644 index 0000000..77f564b --- /dev/null +++ b/core/modules/system/tests/modules/datetime_test/datetime_test.module @@ -0,0 +1,19 @@ + array( + 'label' => t('Gamma'), + 'class' => '\Drupal\datetime_test\Gamma', + ), + ); +} + diff --git a/core/modules/system/tests/modules/datetime_test/lib/Drupal/datetime_test/Gamma.php b/core/modules/system/tests/modules/datetime_test/lib/Drupal/datetime_test/Gamma.php new file mode 100644 index 0000000..b817d3e --- /dev/null +++ b/core/modules/system/tests/modules/datetime_test/lib/Drupal/datetime_test/Gamma.php @@ -0,0 +1,284 @@ + 'Alpha', + 2 => 'Beta', + 3 => 'Gamma', + 4 => 'Delta', + ); + } + + /** + * Constructs a translated array of month name abbreviations + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of month abbreviations. + */ + public static function month_names_abbr($required = FALSE) {} + + /** + * Returns a translated array of week names. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day names + */ + public static function week_days($required = FALSE) {} + + /** + * Constructs a translated array of week day abbreviations. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day abbreviations + */ + public static function week_days_abbr($required = FALSE) {} + + /** + * Constructs a translated array of 2-letter week day abbreviations. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day 2 letter abbreviations + */ + public static function week_days_abbr2($required = FALSE) {} + + /** + * Constructs a translated array of 1-letter week day abbreviations. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of week day 1 letter abbreviations + */ + public static function week_days_abbr1($required = FALSE) {} + + /** + * Reorders weekdays to match the first day of the week. + * + * @param array $weekdays + * An array of weekdays. + * + * @return array + * An array of weekdays reordered to match the first day of the week. + */ + public static function week_days_ordered($weekdays) {} + + /** + * Constructs an array of years in a specified range. + * + * @param int $min + * The minimum year in the array. + * @param int $max + * The maximum year in the array. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of years in the selected range. + */ + public static function years($min = 0, $max = 0, $required = FALSE) {} + + /** + * Constructs an array of days in a month. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * @param int $month + * (optional) The month in which to find the number of days. + * @param int $year + * (optional) The year in which to find the number of days. + * + * @return array + * An array of days for the selected month. + */ + public static function days($required = FALSE, $month = NULL, $year = NULL) {} + + /** + * Constructs an array of hours. + * + * @param string $format + * A date format string that indicates the format to use for the hours. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of hours in the selected format. + */ + public static function hours($format = 'H', $required = FALSE) {} + + /** + * Constructs an array of minutes. + * + * @param string $format + * A date format string that indicates the format to use for the minutes. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * @param int $increment + * A the integer value to increment the values. Defaults to 1. + * + * @return array + * An array of minutes in the selected format. + */ + public static function minutes($format = 'i', $required = FALSE, $increment = 1) {} + + /** + * Constructs an array of seconds. + * + * @param string $format + * A date format string that indicates the format to use for the seconds. + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * @param int $increment + * A the integer value to increment the values. Defaults to 1. + * + * @return array + * An array of seconds in the selected format. + */ + public static function seconds($format = 's', $required = FALSE, $increment = 1) {} + + /** + * Constructs an array of AM and PM options. + * + * @param bool $required + * (optional) If FALSE, the returned array will include a blank value. + * Defaults to FALSE. + * + * @return array + * An array of AM and PM options. + */ + public static function ampm($required = FALSE) {} + + /** + * Identifies the number of days in a month for a date. + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * + * @return integer + * The number of days in the month. + */ + public static function days_in_month($date = NULL) {} + + /** + * Identifies the number of days in a year for a date. + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * + * @return integer + * The number of days in the year. + */ + public static function days_in_year($date = NULL) {} + + /** + * Returns day of week for a given date (0 = Sunday). + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * + * @return int + * The number of the day in the week. + */ + public static function day_of_week($date = NULL) {} + + /** + * Returns translated name of the day of week for a given date. + * + * @param mixed $date + * (optional) A date object, timestamp, or a date string. + * Defaults to current date. + * @param string $abbr + * (optional) Whether to return the abbreviated name for that day. + * Defaults to TRUE. + * + * @return string + * The name of the day in the week for that date. + */ + public static function day_of_week_name($date = NULL, $abbr = TRUE) {} + +}