diff --git a/core/includes/common.inc b/core/includes/common.inc
index 022303e..cc8e22f 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1889,78 +1889,121 @@ function format_interval($interval, $granularity = 2, $langcode = NULL) {
  * @param $format
  *   (optional) If $type is 'custom', a PHP date format string suitable for
  *   input to date(). Use a backslash to escape ordinary text, so it does not
- *   get interpreted as date format characters.
- * @param $timezone
- *   (optional) Time zone identifier, as described at
- *   http://php.net/manual/timezones.php Defaults to the time zone used to
- *   display the page.
- * @param $langcode
- *   (optional) Language code to translate to. Defaults to the language used to
- *   display the page.
+ *   get interpreted as date format characters. To use the alternate
+ *   formatting of the IntlDateFormatter, provide a format string
+ *   in the right format for that formatter, and a settings array
+ *   with the rest of the values required by that class.
+ * @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 $timezone
+ *     A timezone name. Defaults to the default timezone.
+ *   - string $langcode
+ *     The Drupal langcode. If NULL, Defaults to the language used
+ *     to display the page.
+ *   - 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
+ * @return string
  *   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 = '', $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($settings['timezone']) ? $settings['timezone'] : date_default_timezone_get();
+  $settings['timezone'] = $timezone;
 
-  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])) {
     $timezones[$timezone] = timezone_open($timezone);
   }
 
-  if (empty($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_INTERFACE)->langcode;
+  if (empty($settings['langcode'])) {
+    $settings['langcode'] = language(LANGUAGE_TYPE_INTERFACE)->langcode;
   }
 
+  if (empty($settings['calendar'])) {
+    $settings['calendar'] = config('system.calendar')->get('calendar');
+  }
+
+  // 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 +2011,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 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 3c4576a..49e5820 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', '', array('timezone' => '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 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Datetime\Plugin\DateCalendar.
+ */
+
+namespace Drupal\Core\Datetime\Plugin;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\Plugin\Discovery\HookDiscovery;
+
+/**
+ * Manages Date Calendar plugins.
+ *
+ * Calendar plugins are discovered by searching for implementations
+ * of hook_date_calendar_info().
+ */
+class DateCalendar extends PluginManagerBase {
+
+  const DEFAULT_CALENDAR = 'gregorian';
+
+  /**
+   * Constructor.
+   */
+  public function __construct() {
+    $this->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 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Datetime\Plugin\DateCalendarInterface.
+ *
+ * Lots of helpful functions for use in massaging dates,
+ * specific 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 so the translation
+ * system will be able to process them.
+ */
+namespace Drupal\Core\Datetime\Plugin;
+
+interface DateCalendarInterface  {
+
+  /**
+   * Constructs an untranslated array of month names.
+   *
+   * @return array
+   *   An array of month names.
+   */
+  public static function month_names_untranslated();
+
+  /**
+   * Constructs an untranslated array of abbreviated month names.
+   *
+   * @return array
+   *   An array of month name abbreviations.
+   */
+  public static function month_names_abbr_untranslated();
+
+  /**
+   * Constructs an untranslated array of week days.
+   *
+   * @return array
+   *   An array of week day names
+   */
+  public static function week_days_untranslated();
+
+  /**
+   * 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);
+
+  /**
+   * 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);
+
+}
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..06001c5
--- /dev/null
+++ b/core/lib/Drupal/Core/Datetime/Plugin/Type/Gregorian.php
@@ -0,0 +1,518 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Datetime\Plugin\Type\Gregorian.
+ *
+ * Lots of helpful functions for use in massaging dates,
+ * specific 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 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 month_names_untranslated() {
+    // 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 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++) {
+      $hours[$i] = $i < 10 && ($format == 'H' || $format == 'h') ? "0$i" : $i;
+    }
+    $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) {
+      $minutes[$i] = $i < 10 && $format == 'i' ? "0$i" : $i;
+    }
+    $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) {
+      $seconds[$i] = $i < 10 && $format == 's' ? "0$i" : $i;
+    }
+    $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/aggregator/aggregator.pages.inc b/core/modules/aggregator/aggregator.pages.inc
index 78ca5bc..9f7e328 100644
--- a/core/modules/aggregator/aggregator.pages.inc
+++ b/core/modules/aggregator/aggregator.pages.inc
@@ -546,7 +546,7 @@ function template_preprocess_aggregator_summary_item(&$variables) {
   ));
   $variables['item_age'] = theme('datetime', array(
     'attributes' => array(
-      'datetime' => format_date($item->timestamp, 'html_datetime', '', 'UTC'),
+      'datetime' => format_date($item->timestamp, 'html_datetime', '', array('timezone' => 'UTC')),
       'class' => array('feed-item-age',),
     ),
     'text' => t('%age old', array('%age' => format_interval(REQUEST_TIME - $item->timestamp))),
diff --git a/core/modules/comment/comment.tokens.inc b/core/modules/comment/comment.tokens.inc
index c77cb67..26fbfae 100644
--- a/core/modules/comment/comment.tokens.inc
+++ b/core/modules/comment/comment.tokens.inc
@@ -186,11 +186,11 @@ function comment_tokens($type, $tokens, array $data = array(), array $options =
           break;
 
         case 'created':
-          $replacements[$original] = format_date($comment->created, 'medium', '', NULL, $langcode);
+          $replacements[$original] = format_date($comment->created, 'medium', '', array('langcode' => $langcode));
           break;
 
         case 'changed':
-          $replacements[$original] = format_date($comment->changed, 'medium', '', NULL, $langcode);
+          $replacements[$original] = format_date($comment->changed, 'medium', '', array('langcode' => $langcode));
           break;
 
         case 'node':
diff --git a/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php b/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php
index 4d56b7b..65f5b2d 100644
--- a/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php
@@ -56,8 +56,8 @@ function testFileTokenReplacement() {
     $tests['[file:mime]'] = check_plain($file->filemime);
     $tests['[file:size]'] = format_size($file->filesize);
     $tests['[file:url]'] = check_plain(file_create_url($file->uri));
-    $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language_interface->langcode);
-    $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language_interface->langcode);
+    $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', array('langcode' => $language_interface->langcode));
+    $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', array('langcode' => $language_interface->langcode));
     $tests['[file:owner]'] = check_plain(user_format_name($this->admin_user));
     $tests['[file:owner:uid]'] = $file->uid;
 
diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc
index 0bd1000..b07b426 100644
--- a/core/modules/node/node.tokens.inc
+++ b/core/modules/node/node.tokens.inc
@@ -163,11 +163,11 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr
           break;
 
         case 'created':
-          $replacements[$original] = format_date($node->created, 'medium', '', NULL, $langcode);
+          $replacements[$original] = format_date($node->created, 'medium', '', array('langcode' => $langcode));
           break;
 
         case 'changed':
-          $replacements[$original] = format_date($node->changed, 'medium', '', NULL, $langcode);
+          $replacements[$original] = format_date($node->changed, 'medium', '', array('langcode' => $langcode));
           break;
       }
     }
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/Common/FormatDateTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/FormatDateTest.php
index 87d97b6..2397281 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/FormatDateTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/FormatDateTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Common;
 
 use Drupal\simpletest\WebTestBase;
+use Drupal\Core\Datetime\DrupalDateTime;
 
 /**
  * Tests the format_date() function.
@@ -69,7 +70,7 @@ function testAdminDefinedFormatDate() {
     $this->drupalPost('admin/config/regional/date-time/types/add', $edit, 'Add date type');
 
     $timestamp = strtotime('2007-03-10T00:00:00+00:00');
-    $this->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07', 'Test format_date() using an admin-defined date type.');
+    $this->assertIdentical(format_date($timestamp, 'example_style', '', array('timezone' => 'America/Los_Angeles')), '9 Mar 07', 'Test format_date() using an admin-defined date type.');
     $this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'medium'), 'Test format_date() defaulting to medium when $type not found.');
   }
 
@@ -82,12 +83,12 @@ function testFormatDate() {
     $language_interface = language(LANGUAGE_TYPE_INTERFACE);
 
     $timestamp = strtotime('2007-03-26T00:00:00+00:00');
-    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.');
-    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.');
-    $this->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.');
-    $this->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.');
-    $this->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.');
-    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
+    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', array('timezone' => 'America/Los_Angeles', 'langcode' => 'en')), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.');
+    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', array('timezone' => 'America/Los_Angeles', 'langcode' => self::LANGCODE)), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.');
+    $this->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', array('timezone' => 'America/Los_Angeles', 'langcode' => self::LANGCODE)), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.');
+    $this->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', array('timezone' => 'America/Los_Angeles', 'langcode' => self::LANGCODE)), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.');
+    $this->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', array('timezone' => 'America/Los_Angeles', 'langcode' => self::LANGCODE)), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.');
+    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', array('timezone' => 'Europe/London', 'langcode' => 'en')), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
 
     // Create an admin user and add Spanish language.
     $admin_user = $this->drupalCreateUser(array('administer languages'));
@@ -120,8 +121,8 @@ function testFormatDate() {
     // Simulate a Drupal bootstrap with the logged-in user.
     date_default_timezone_set(drupal_get_user_timezone());
 
-    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.');
-    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
+    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', array('timezone' => 'America/Los_Angeles', 'langcode' => 'en')), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.');
+    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', array('timezone' => 'Europe/London')), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
     $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test custom date format.');
     $this->assertIdentical(format_date($timestamp, 'long'), 'domingo, 25. marzo 2007 - 17:00', 'Test long date format.');
     $this->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', 'Test medium date format.');
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 @@
+<?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->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/lib/Drupal/system/Tests/System/TokenReplaceTest.php b/core/modules/system/lib/Drupal/system/Tests/System/TokenReplaceTest.php
index 79bea6b..9e1cfd8 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/TokenReplaceTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/TokenReplaceTest.php
@@ -44,7 +44,7 @@ function testTokenReplacement() {
     $target .= check_plain($account->name);
     $target .= format_interval(REQUEST_TIME - $node->created, 2, $language_interface->langcode);
     $target .= check_plain($user->name);
-    $target .= format_date(REQUEST_TIME, 'short', '', NULL, $language_interface->langcode);
+    $target .= format_date(REQUEST_TIME, 'short', '', array('langcode' => $language_interface->langcode));
 
     // Test that the clear parameter cleans out non-existent tokens.
     $result = token_replace($source, array('node' => $node), array('langcode' => $language_interface->langcode, 'clear' => TRUE));
@@ -154,10 +154,10 @@ function testSystemDateTokenReplacement() {
 
     // Generate and test tokens.
     $tests = array();
-    $tests['[date:short]'] = format_date($date, 'short', '', NULL, $language_interface->langcode);
-    $tests['[date:medium]'] = format_date($date, 'medium', '', NULL, $language_interface->langcode);
-    $tests['[date:long]'] = format_date($date, 'long', '', NULL, $language_interface->langcode);
-    $tests['[date:custom:m/j/Y]'] = format_date($date, 'custom', 'm/j/Y', NULL, $language_interface->langcode);
+    $tests['[date:short]'] = format_date($date, 'short', '', array('langcode' => $language_interface->langcode));
+    $tests['[date:medium]'] = format_date($date, 'medium', '', array('langcode' => $language_interface->langcode));
+    $tests['[date:long]'] = format_date($date, 'long', '', array('langcode' => $language_interface->langcode));
+    $tests['[date:custom:m/j/Y]'] = format_date($date, 'custom', 'm/j/Y', array('langcode' => $language_interface->langcode));
     $tests['[date:since]'] = format_interval((REQUEST_TIME - $date), 2, $language_interface->langcode);
     $tests['[date:raw]'] = filter_xss($date);
 
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 0f6d668..02d2bb9 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
@@ -3604,7 +3632,7 @@ function hook_tokens($type, $tokens, array $data = array(), array $options = arr
           break;
 
         case 'created':
-          $replacements[$original] = format_date($node->created, 'medium', '', NULL, $langcode);
+          $replacements[$original] = format_date($node->created, 'medium', '', array('langcode' => $langcode));
           break;
       }
     }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index ea3dadc..fe59490 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() {
@@ -3538,7 +3573,7 @@ function system_time_zones($blank = NULL) {
     // reasons and should not be used, the list is filtered by a regular
     // 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)));
+      $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', array('timezone' => $zone))));
     }
   }
   // Sort the translated time zones alphabetically.
diff --git a/core/modules/system/system.tokens.inc b/core/modules/system/system.tokens.inc
index a5e7ad2..1a7389b 100644
--- a/core/modules/system/system.tokens.inc
+++ b/core/modules/system/system.tokens.inc
@@ -184,15 +184,15 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'short':
-          $replacements[$original] = format_date($date, 'short', '', NULL, $langcode);
+          $replacements[$original] = format_date($date, 'short', '', array('langcode' => $langcode));
           break;
 
         case 'medium':
-          $replacements[$original] = format_date($date, 'medium', '', NULL, $langcode);
+          $replacements[$original] = format_date($date, 'medium', '', array('langcode' => $langcode));
           break;
 
         case 'long':
-          $replacements[$original] = format_date($date, 'long', '', NULL, $langcode);
+          $replacements[$original] = format_date($date, 'long', '', array('langcode' => $langcode));
           break;
 
         case 'since':
@@ -207,7 +207,7 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
 
     if ($created_tokens = token_find_with_prefix($tokens, 'custom')) {
       foreach ($created_tokens as $name => $original) {
-        $replacements[$original] = format_date($date, 'custom', $name, NULL, $langcode);
+        $replacements[$original] = format_date($date, 'custom', $name, array('langcode' => $langcode));
       }
     }
   }
@@ -245,7 +245,7 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
 
         // These tokens are default variations on the chained tokens handled below.
         case 'timestamp':
-          $replacements[$original] = format_date($file->timestamp, 'medium', '', NULL, $langcode);
+          $replacements[$original] = format_date($file->timestamp, 'medium', '', array('langcode' => $langcode));
           break;
 
         case 'owner':
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 @@
+<?php
+
+/**
+ * @file
+ * Test module for Datetime functionality.
+ */
+
+/**
+ * Implements hook_date_calendar_info().
+ */
+function datetime_test_date_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..b817d3e
--- /dev/null
+++ b/core/modules/system/tests/modules/datetime_test/lib/Drupal/datetime_test/Gamma.php
@@ -0,0 +1,284 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\datetime_test\Gamma.
+ *
+ * Lots of helpful functions for use in massaging dates,
+ * specific 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 so the translation
+ * system will be able to process them.
+ */
+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 month_names_untranslated() {}
+
+  /**
+   * Constructs an untranslated array of abbreviated month names.
+   *
+   * @return array
+   *   An array of month names.
+   */
+  public static function month_names_abbr_untranslated() {}
+
+  /**
+   * Constructs an untranslated array of week days.
+   *
+   * @return array
+   *   An array of week day names
+   */
+  public static function week_days_untranslated() {}
+
+  /**
+   * 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.
+    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 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) {}
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php b/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php
index 3daa476..e860101 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php
@@ -65,10 +65,10 @@ function testUserTokenReplacement() {
     $tests['[user:mail]'] = check_plain($account->mail);
     $tests['[user:url]'] = url("user/$account->uid", $url_options);
     $tests['[user:edit-url]'] = url("user/$account->uid/edit", $url_options);
-    $tests['[user:last-login]'] = format_date($account->login, 'medium', '', NULL, $language_interface->langcode);
-    $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', NULL, $language_interface->langcode);
-    $tests['[user:created]'] = format_date($account->created, 'medium', '', NULL, $language_interface->langcode);
-    $tests['[user:created:short]'] = format_date($account->created, 'short', '', NULL, $language_interface->langcode);
+    $tests['[user:last-login]'] = format_date($account->login, 'medium', '', array('langcode' => $language_interface->langcode));
+    $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', array('langcode' => $language_interface->langcode));
+    $tests['[user:created]'] = format_date($account->created, 'medium', '', array('langcode' => $language_interface->langcode));
+    $tests['[user:created:short]'] = format_date($account->created, 'short', '', array('langcode' => $language_interface->langcode));
     $tests['[current-user:name]'] = check_plain(user_format_name($global_account));
 
     // Test to make sure that we generated something for each token.
diff --git a/core/modules/user/user.tokens.inc b/core/modules/user/user.tokens.inc
index bc37434..2ce84aa 100644
--- a/core/modules/user/user.tokens.inc
+++ b/core/modules/user/user.tokens.inc
@@ -103,12 +103,12 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
 
         // These tokens are default variations on the chained tokens handled below.
         case 'last-login':
-          $replacements[$original] = !empty($account->login) ? format_date($account->login, 'medium', '', NULL, $langcode) : t('never');
+          $replacements[$original] = !empty($account->login) ? format_date($account->login, 'medium', '', array('langcode' => $langcode)) : t('never');
           break;
 
         case 'created':
           // In the case of user_presave the created date may not yet be set.
-          $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $langcode) : t('not yet created');
+          $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', array('langcode' => $langcode)) : t('not yet created');
           break;
       }
     }
