diff --git a/core/includes/form.inc b/core/includes/form.inc
index 226d4a6..4f64d1a 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -7,6 +7,7 @@
 
 use Drupal\Core\Utility\Color;
 use Drupal\Core\Template\Attribute;
+use Drupal\Core\Datetime\DrupalDateTime;
 
 /**
  * @defgroup forms Form builder functions
@@ -2951,12 +2952,20 @@ 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 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 +2984,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->monthNamesAbbr($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 +3016,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 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/lib/Drupal/Core/Datetime/Plugin/DateCalendar.php b/core/lib/Drupal/Core/Datetime/Plugin/DateCalendar.php
new file mode 100644
index 0000000..7885849
--- /dev/null
+++ b/core/lib/Drupal/Core/Datetime/Plugin/DateCalendar.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Datetime\Plugin\DateCalendar.
+ *
+ * @TODO
+ *   Whenever http://drupal.org/node/1722882 is sorted out and committed,
+ *   see how that impacts the translation of calendar values and add
+ *   tests for translation.
+ */
+
+namespace Drupal\Core\Datetime\Plugin;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\Plugin\Discovery\CacheDecorator;
+use Drupal\Core\Plugin\Discovery\HookDiscovery;
+
+/**
+ * Manages Date Calendar plugins.
+ *
+ * Calendar plugins are discovered by searching for implementations
+ * of hook_datetime_calendar_info().
+ */
+class DateCalendar extends PluginManagerBase {
+
+  const DEFAULT_CALENDAR = 'gregorian';
+
+  /**
+   * Constructor.
+   */
+  public function __construct() {
+
+    // Not sure if this is the right way to find the current user's
+    // language code.
+    $cache_id = 'datetime-calendar:' . language_default()->langcode;
+    $this->discovery = new CacheDecorator(new HookDiscovery('datetime_calendar_info'), $cache_id);
+    $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..3e08c53
--- /dev/null
+++ b/core/lib/Drupal/Core/Datetime/Plugin/DateCalendarInterface.php
@@ -0,0 +1,271 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Datetime\Plugin\DateCalendarInterface.
+ *
+ * Lots of helpful functions for use in massaging dates,
+ * specific to the the calendar system in use. The values include
+ * both translated and untranslated values.
+ *
+ * Untranslated values are useful as array keys and as css
+ * identifiers, and should be listed in English.
+ *
+ * Translated values are useful for display to the user. All
+ * values that need translation should be hard-coded and wrapped in
+ * t() so the translation system will be able to process them.
+ *
+ * @see Drupal\Core\Datetime\Plugin\Type\Gregorian
+ */
+namespace Drupal\Core\Datetime\Plugin;
+
+interface DateCalendarInterface  {
+
+  /**
+   * Constructs an untranslated array of month names.
+   *
+   * @return array
+   *   An array of month names.
+   */
+  public static function monthNamesUntranslated();
+
+  /**
+   * Constructs an untranslated array of abbreviated month names.
+   *
+   * @return array
+   *   An array of month name abbreviations.
+   */
+  public static function monthNamesAbbrUntranslated();
+
+  /**
+   * 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 monthNames($required = FALSE);
+
+  /**
+   * 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 monthNamesAbbr($required = FALSE);
+
+  /**
+   * Constructs an untranslated array of week days.
+   *
+   * @return array
+   *   An array of week day names
+   */
+  public static function weekDaysUntranslated();
+
+  /**
+   * 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 weekDays($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 weekDaysAbbr($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 weekDaysAbbr2($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 weekDaysAbbr1($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 weekDaysOrdered($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 daysInMonth($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 daysInYear($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 dayOfWeek($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 dayOfWeekName($date = NULL, $abbr = TRUE);
+
+}
diff --git a/core/lib/Drupal/Core/Datetime/Plugin/Type/Gregorian.php b/core/lib/Drupal/Core/Datetime/Plugin/Type/Gregorian.php
new file mode 100644
index 0000000..bec3e87
--- /dev/null
+++ b/core/lib/Drupal/Core/Datetime/Plugin/Type/Gregorian.php
@@ -0,0 +1,522 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Datetime\Plugin\Type\Gregorian.
+ *
+ * Lots of helpful functions for use in massaging dates,
+ * specific to the the calendar system in use. The values include
+ * both translated and untranslated values.
+ *
+ * Untranslated values are useful as array keys and as css
+ * identifiers, and should be listed in English.
+ *
+ * Translated values are useful for display to the user. All
+ * values that need translation should be hard-coded and wrapped in
+ * t() so the translation system will be able to process them.
+ */
+namespace Drupal\Core\Datetime\Plugin\Type;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Datetime\Plugin\DateCalendarInterface;
+
+/**
+ * Defines a Gregorian Calendar implementation.
+ */
+class Gregorian implements DateCalendarInterface {
+
+  /**
+   * Constructs an untranslated array of month names.
+   *
+   * @return array
+   *   An array of month names.
+   */
+  public static function monthNamesUntranslated() {
+    // Force the key to use the correct month value, rather than
+    // starting with zero.
+    return array(
+            1  => '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 monthNamesAbbrUntranslated() {
+    // 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',
+           );
+  }
+
+  /**
+   * 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 monthNames($required = FALSE) {
+    // Force the key to use the correct month value, rather than
+    // starting with zero.
+    $monthnames = 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 + $monthnames : $monthnames;
+  }
+
+  /**
+   * 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 monthNamesAbbr($required = FALSE) {
+    // Force the key to use the correct month value, rather than
+    // starting with zero.
+    $monthnames = 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 + $monthnames : $monthnames;
+  }
+
+  /**
+   * Constructs an untranslated array of week days.
+   *
+   * @return array
+   *   An array of week day names
+   */
+  public static function weekDaysUntranslated() {
+    return array(
+            'Sunday',
+            'Monday',
+            'Tuesday',
+            'Wednesday',
+            'Thursday',
+            'Friday',
+            'Saturday',
+           );
+  }
+
+  /**
+   * 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 weekDays($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 weekDaysAbbr($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 weekDaysAbbr2($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 weekDaysAbbr1($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 weekDaysOrdered($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 daysInMonth($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 daysInYear($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 dayOfWeek($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 dayOfWeekName($date = NULL, $abbr = TRUE) {
+    if (!$date instanceOf DrupalDateTime) {
+      $date = new DrupalDateTime($date);
+    }
+    $dow = self::dayOfWeek($date);
+    $days = $abbr ? self::weekDaysAbbr() : self::weekDays();
+    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..1470eac
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Datetime/DateCalendarTest.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Datetime\DateCalendarTest.
+ */
+
+namespace Drupal\system\Tests\Datetime;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\Core\Datetime\Plugin\DateCalendar;
+
+class DateCalendarTest extends WebTestBase {
+
+  /**
+   * Test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => '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->monthNames()), '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->monthNames()), '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->monthNames()), '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 6ba04f4..d04bd8f 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -8,6 +8,8 @@
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Datetime\Plugin\DateCalendar;
 
 /**
  * Menu callback; Provide the administration overview page.
@@ -1864,6 +1866,11 @@ function system_regional_settings() {
   // Date settings:
   $zones = system_time_zones();
 
+  // Get calendar information.
+  $calendar_type = config('system.calendar')->get('calendar');
+  $calendar_plugin = new DateCalendar();
+  $calendar = $calendar_plugin->createInstance($calendar_type);
+
   $form['locale'] = array(
     '#type' => 'fieldset',
     '#title' => t('Locale'),
@@ -1878,11 +1885,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->weekDays(TRUE),
   );
 
   $form['timezone'] = array(
@@ -1933,10 +1952,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 d10d3e0..ce860b3 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_datetime_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 843ed78..5ec0e9f 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -7,9 +7,9 @@
 
 use Drupal\Core\Utility\ModuleInfo;
 use Drupal\Core\TypedData\Primitive;
-
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
+use Drupal\Core\Datetime\Plugin\DateCalendar;
 
 /**
  * Maximum age of temporary files in seconds.
@@ -2018,6 +2018,29 @@ function system_stream_wrappers() {
 }
 
 /**
+ * Implements hook_datetime_calendar_info().
+ */
+function system_datetime_calendar_info() {
+  return array(
+    'gregorian' => array(
+      'label' => t('Gregorian'),
+      'class' => '\Drupal\Core\Datetime\Plugin\Type\Gregorian',
+    ),
+  );
+}
+
+/**
+* Get system calendar information.
+*/
+function system_calendar($calendar = NULL) {
+  if (!isset($calendar)) {
+    $calendar = config('system.calendar')->get('calendar');
+  }
+  $plugin = new DateCalendar();
+  return $plugin->createInstance($calendar);
+}
+
+/**
  * Implements hook_data_type_info().
  */
 function system_data_type_info() {
@@ -3579,7 +3602,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..569b6f3
--- /dev/null
+++ b/core/modules/system/tests/modules/datetime_test/datetime_test.module
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Test module for Datetime functionality.
+ */
+
+/**
+ * Implements hook_datetime_calendar_info().
+ */
+function datetime_test_datetime_calendar_info() {
+  return array(
+    'gamma' => 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..7eb3a76
--- /dev/null
+++ b/core/modules/system/tests/modules/datetime_test/lib/Drupal/datetime_test/Gamma.php
@@ -0,0 +1,273 @@
+<?php
+/**
+ * @file
+ * A test calendar plugin.
+ */
+namespace Drupal\datetime_test;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Datetime\Plugin\DateCalendarInterface;
+
+/**
+ * Defines a Gamma Calendar implementation.
+ */
+class Gamma implements DateCalendarInterface {
+
+  /**
+   * Constructs an untranslated array of month names.
+   *
+   * @return array
+   *   An array of month names.
+   */
+  public static function monthNamesUntranslated() {}
+
+  /**
+   * Constructs an untranslated array of abbreviated month names.
+   *
+   * @return array
+   *   An array of month names.
+   */
+  public static function monthNamesAbbrUntranslated() {}
+
+  /**
+   * 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 monthNames($required = FALSE) {
+    // Force the key to use the correct month value, rather than
+    // starting with zero.
+    return array(
+            1 => '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 monthNamesAbbr($required = FALSE) {}
+
+  /**
+   * Constructs an untranslated array of week days.
+   *
+   * @return array
+   *   An array of week day names
+   */
+  public static function weekDaysUntranslated() {}
+
+  /**
+   * 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 weekDays($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 weekDaysAbbr($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 weekDaysAbbr2($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 weekDaysAbbr1($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 weekDaysOrdered($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 daysInMonth($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 daysInYear($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 dayOfWeek($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 dayOfWeekName($date = NULL, $abbr = TRUE) {}
+
+}
