diff --git a/core/includes/common.inc b/core/includes/common.inc
index 4ae4397..501190e 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -386,42 +386,6 @@ function date_iso8601($date) {
 }
 
 /**
- * Translates a formatted date string.
- *
- * Callback for preg_replace_callback() within format_date().
- */
-function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
-  // We cache translations to avoid redundant and rather costly calls to t().
-  static $cache, $langcode;
-
-  if (!isset($matches)) {
-    $langcode = $new_langcode;
-    return;
-  }
-
-  $code = $matches[1];
-  $string = $matches[2];
-
-  if (!isset($cache[$langcode][$code][$string])) {
-    $options = array(
-      'langcode' => $langcode,
-    );
-
-    if ($code == 'F') {
-      $options['context'] = 'Long month name';
-    }
-
-    if ($code == '') {
-      $cache[$langcode][$code][$string] = $string;
-    }
-    else {
-      $cache[$langcode][$code][$string] = t($string, array(), $options);
-    }
-  }
-  return $cache[$langcode][$code][$string];
-}
-
-/**
  * @} End of "defgroup format".
  */
 
diff --git a/core/lib/Drupal/Core/Datetime/DateFormatter.php b/core/lib/Drupal/Core/Datetime/DateFormatter.php
index 0b4f817..82e9969 100644
--- a/core/lib/Drupal/Core/Datetime/DateFormatter.php
+++ b/core/lib/Drupal/Core/Datetime/DateFormatter.php
@@ -193,6 +193,33 @@ public function formatInterval($interval, $granularity = 2, $langcode = NULL) {
   }
 
   /**
+   * Provides values for all date formatting characters for a given timestamp.
+   *
+   * @param string|null $langcode
+   *   (optional) Language code of the date format, if different from the site
+   *   default language.
+   * @param int|null $timestamp
+   *   (optional) The Unix timestamp to format, defaults to current time.
+   * @param string|null $timezone
+   *   (optional) The timezone to use, if different from the site's default
+   *   timezone.
+   *
+   * @return array
+   *   An array of formatted date values, indexed by the date format character.
+   *
+   * @see date()
+   */
+  public function getSampleDateFormats($langcode = NULL, $timestamp = NULL, $timezone = NULL) {
+    $timestamp = $timestamp ?: time();
+    // All date format characters for the PHP date() function.
+    $date_chars = str_split('dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU');
+    $date_elements = array_combine($date_chars, $date_chars);
+    return array_map(function($character) use ($timestamp, $timezone, $langcode) {
+      return $this->format($timestamp, 'custom', $character, $timezone, $langcode);
+    }, $date_elements);
+  }
+
+  /**
    * Loads the given format pattern for the given langcode.
    *
    * @param string $format
@@ -200,8 +227,9 @@ public function formatInterval($interval, $granularity = 2, $langcode = NULL) {
    * @param string $langcode
    *   The langcode of the language to use.
    *
-   * @return string
-   *   The pattern for the date format in the given language.
+   * @return string|null
+   *   The pattern for the date format in the given language for non-custom
+   *   formats, NULL otherwise.
    */
   protected function dateFormat($format, $langcode) {
     if (!isset($this->dateFormats[$format][$langcode])) {
diff --git a/core/lib/Drupal/Core/Datetime/DrupalDateTime.php b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
index 74bd68b..977108b 100644
--- a/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
+++ b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
@@ -7,6 +7,7 @@
 namespace Drupal\Core\Datetime;
 
 use Drupal\Component\Datetime\DateTimePlus;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
  * Extends DateTimePlus().
@@ -23,6 +24,14 @@
  */
 class DrupalDateTime extends DateTimePlus {
 
+  use StringTranslationTrait;
+
+  /**
+   * Format string translation cache.
+   *
+   */
+  protected $formatTranslationCache;
+
   /**
    * Constructs a date object.
    *
@@ -84,7 +93,8 @@ protected function prepareTimezone($timezone) {
    *   The formatted value of the date.
    */
   public function format($format, $settings = array()) {
-    $settings['langcode'] = !empty($settings['langcode']) ? $settings['langcode'] : $this->langcode;
+    $langcode = !empty($settings['langcode']) ? $settings['langcode'] : $this->langcode;
+    $value = '';
     // Format the date and catch errors.
     try {
       // Encode markers that should be translated. 'A' becomes
@@ -98,15 +108,33 @@ public function format($format, $settings = array()) {
       // Call date_format().
       $format = parent::format($format);
 
-      // Pass the langcode to _format_date_callback().
-      _format_date_callback(NULL, $settings['langcode']);
+      // Translates a formatted date string.
+      $translation_callback = function($matches) use ($langcode) {
+        $code = $matches[1];
+        $string = $matches[2];
+        if (!isset($this->formatTranslationCache[$langcode][$code][$string])) {
+          $options = array('langcode' => $langcode);
+          if ($code == 'F') {
+            $options['context'] = 'Long month name';
+          }
+
+          if ($code == '') {
+            $this->formatTranslationCache[$langcode][$code][$string] = $string;
+          }
+          else {
+            $this->formatTranslationCache[$langcode][$code][$string] = $this->t($string, array(), $options);
+          }
+        }
+        return $this->formatTranslationCache[$langcode][$code][$string];
+      };
 
       // Translate the marked sequences.
-      $value = preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', '_format_date_callback', $format);
+      $value = preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', $translation_callback, $format);
     }
     catch (\Exception $e) {
       $this->errors[] = $e->getMessage();
     }
     return $value;
   }
+
 }
diff --git a/core/modules/config_translation/src/FormElement/DateFormat.php b/core/modules/config_translation/src/FormElement/DateFormat.php
index 729afee..db291f7 100644
--- a/core/modules/config_translation/src/FormElement/DateFormat.php
+++ b/core/modules/config_translation/src/FormElement/DateFormat.php
@@ -7,10 +7,6 @@
 
 namespace Drupal\config_translation\FormElement;
 
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\ReplaceCommand;
-use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 
 /**
@@ -22,51 +18,23 @@ class DateFormat extends FormElementBase {
    * {@inheritdoc}
    */
   public function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
+    /** @var \Drupal\Core\Datetime\DateFormatter $date_formatter */
+    $date_formatter = \Drupal::service('date.formatter');
     $description = $this->t('A user-defined date format. See the <a href="@url">PHP manual</a> for available options.', array('@url' => 'http://php.net/manual/function.date.php'));
-    $format = $this->t('Displayed as %date_format', array('%date_format' => \Drupal::service('date.formatter')->format(REQUEST_TIME, 'custom', $translation_config)));
+    $format = $this->t('Displayed as %date_format', array('%date_format' => $date_formatter->format(REQUEST_TIME, 'custom', $translation_config)));
 
-    return array(
+    return [
       '#type' => 'textfield',
       '#description' => $description,
-      '#field_suffix' => ' <div class="edit-date-format-suffix"><small id="edit-date-format-suffix">' . $format . '</small></div>',
-      '#ajax' => array(
-        'callback' => 'Drupal\config_translation\FormElement\DateFormat::ajaxSample',
-        'event' => 'keyup',
-        'progress' => array('type' => 'throbber', 'message' => NULL),
-      ),
-    ) + parent::getTranslationElement($translation_language, $source_config, $translation_config);
-  }
-
-  /**
-   * Ajax callback to render a sample of the input date format.
-   *
-   * @param array $form
-   *   Form API array structure.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   Form state information.
-   *
-   * @return AjaxResponse
-   *   Ajax response with the rendered sample date using the given format. If
-   *   the given format cannot be identified or was empty, the response will
-   *   be empty as well.
-   */
-  public static function ajaxSample(array $form, FormStateInterface $form_state) {
-    $response = new AjaxResponse();
-
-    $format_value = NestedArray::getValue($form_state->getValues(), $form_state->getTriggeringElement()['#array_parents']);
-    if (!empty($format_value)) {
-      // Format the date with a custom date format with the given pattern.
-      // The object is not instantiated in an Ajax context, so $this->t()
-      // cannot be used here.
-      $format = t('Displayed as %date_format', array('%date_format' => \Drupal::service('date.formatter')->format(REQUEST_TIME, 'custom', $format_value)));
-
-      // Return a command instead of a string, since the Ajax framework
-      // automatically prepends an additional empty DIV element for a string,
-      // which breaks the layout.
-      $response->addCommand(new ReplaceCommand('#edit-date-format-suffix', '<small id="edit-date-format-suffix">' . $format . '</small>'));
-    }
-
-    return $response;
+      '#field_suffix' => ' <small data-drupal-date-formatter="preview">' . $format . '</small>',
+      '#attributes' => [
+        'data-drupal-date-formatter' => 'source',
+      ],
+      '#attached' => [
+        'drupalSettings' => ['dateFormats' => $date_formatter->getSampleDateFormats($translation_language->getId())],
+        'library' => ['system/drupal.system.date'],
+      ],
+    ] + parent::getTranslationElement($translation_language, $source_config, $translation_config);
   }
 
 }
diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
index 807307c..00b36c5 100644
--- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
@@ -446,6 +446,9 @@ public function testDateFormatTranslation() {
       $this->drupalGet($translation_page_url);
       $this->assertText($label);
 
+      // Make sure that the date library is added.
+      $this->assertRaw('core/modules/system/js/system.date.js');
+
       // Update translatable fields.
       $edit = array(
         'translation[config_names][core.date_format.' . $id . '][label]' => $id . ' - FR',
diff --git a/core/modules/system/js/system.date.js b/core/modules/system/js/system.date.js
new file mode 100644
index 0000000..fcab3ef
--- /dev/null
+++ b/core/modules/system/js/system.date.js
@@ -0,0 +1,46 @@
+(function ($, Drupal, drupalSettings) {
+
+  "use strict";
+
+  var dateFormats = drupalSettings.dateFormats;
+
+  /**
+   * Display the preview for date format entered.
+   */
+  Drupal.behaviors.dateFormat = {
+    attach: function (context) {
+      var $context = $(context);
+      var $source = $context.find('[data-drupal-date-formatter="source"]').once('dateFormat');
+      var $target = $context.find('[data-drupal-date-formatter="preview"]').once('dateFormat');
+      var $preview = $target.find('em');
+
+      // All elements have to exist.
+      if (!$source.length || !$target.length) {
+        return;
+      }
+
+      /**
+       * Event handler that replaces date characters with value.
+       *
+       * @param {object} e
+       */
+      function dateFormatHandler(e) {
+        var baseValue = $(e.target).val() || '';
+        var dateString = baseValue.replace(/\\?(.?)/gi, function (key, value) {
+          return dateFormats[key] ? dateFormats[key] : value;
+        });
+
+        $preview.html(dateString);
+        $target.toggleClass('js-hide', !dateString.length);
+      }
+
+      /**
+       * On given event triggers the date character replacement.
+       */
+      $source.on('keyup.dateFormat change.dateFormat input.dateFormat', dateFormatHandler)
+        // Initialize preview.
+        .trigger('keyup');
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/system/system.js b/core/modules/system/js/system.js
similarity index 100%
rename from core/modules/system/system.js
rename to core/modules/system/js/system.js
diff --git a/core/modules/system/system.modules.js b/core/modules/system/js/system.modules.js
similarity index 100%
rename from core/modules/system/system.modules.js
rename to core/modules/system/js/system.modules.js
diff --git a/core/modules/system/src/Form/DateFormatEditForm.php b/core/modules/system/src/Form/DateFormatEditForm.php
index f8e780f..511ad1a 100644
--- a/core/modules/system/src/Form/DateFormatEditForm.php
+++ b/core/modules/system/src/Form/DateFormatEditForm.php
@@ -21,7 +21,7 @@ public function form(array $form, FormStateInterface $form_state) {
     $form = parent::form($form, $form_state);
 
     $now = t('Displayed as %date', array('%date' => $this->dateFormatter->format(REQUEST_TIME, $this->entity->id())));
-    $form['date_format_pattern']['#field_suffix'] = ' <small id="edit-date-format-suffix">' . $now . '</small>';
+    $form['date_format_pattern']['#field_suffix'] = ' <small data-drupal-date-formatter="preview">' . $now . '</small>';
     $form['date_format_pattern']['#default_value'] = $this->entity->getPattern();
 
     return $form;
diff --git a/core/modules/system/src/Form/DateFormatFormBase.php b/core/modules/system/src/Form/DateFormatFormBase.php
index c843ba2..aeaa12a 100644
--- a/core/modules/system/src/Form/DateFormatFormBase.php
+++ b/core/modules/system/src/Form/DateFormatFormBase.php
@@ -7,8 +7,6 @@
 
 namespace Drupal\system\Form;
 
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
 use Drupal\Core\Datetime\DateFormatter;
 use Drupal\Core\Form\FormStateInterface;
@@ -80,30 +78,6 @@ public function exists($entity_id, array $element) {
   }
 
   /**
-   * Returns the date for a given format string.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @return \Drupal\Core\Ajax\AjaxResponse
-   *   An AJAX Response to update the date-time value of the date format.
-   */
-  public static function dateTimeLookup(array $form, FormStateInterface $form_state) {
-    $format = '';
-    if (!$form_state->isValueEmpty('date_format_pattern')) {
-      $format = t('Displayed as %date_format', array('%date_format' => \Drupal::service('date.formatter')->format(REQUEST_TIME, 'custom', $form_state->getValue('date_format_pattern'))));
-    }
-    // Return a command instead of a string, since the Ajax framework
-    // automatically prepends an additional empty DIV element for a string, which
-    // breaks the layout.
-    $response = new AjaxResponse();
-    $response->addCommand(new ReplaceCommand('#edit-date-format-suffix', '<small id="edit-date-format-suffix">' . $format . '</small>'));
-    return $response;
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function form(array $form, FormStateInterface $form_state) {
@@ -126,20 +100,16 @@ public function form(array $form, FormStateInterface $form_state) {
         'error' => $this->t('The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".'),
       ),
     );
-
     $form['date_format_pattern'] = array(
       '#type' => 'textfield',
       '#title' => t('Format string'),
       '#maxlength' => 100,
       '#description' => $this->t('A user-defined date format. See the <a href="@url">PHP manual</a> for available options.', array('@url' => 'http://php.net/manual/function.date.php')),
-      '#default_value' => '',
-      '#field_suffix' => ' <small id="edit-date-format-suffix"></small>',
-      '#ajax' => array(
-        'callback' => '::dateTimeLookup',
-        'event' => 'keyup',
-        'progress' => array('type' => 'throbber', 'message' => NULL),
-      ),
       '#required' => TRUE,
+      '#attributes' => [
+        'data-drupal-date-formatter' => 'source',
+      ],
+      '#field_suffix' => ' <small class="js-hide" data-drupal-date-formatter="preview">' . $this->t('Displayed as %date_format', ['%date_format' => '']) . '</small>',
     );
 
     $form['langcode'] = array(
@@ -148,7 +118,8 @@ public function form(array $form, FormStateInterface $form_state) {
       '#languages' => LanguageInterface::STATE_ALL,
       '#default_value' => $this->entity->language()->getId(),
     );
-
+    $form['#attached']['drupalSettings']['dateFormats'] = $this->dateFormatter->getSampleDateFormats();
+    $form['#attached']['library'][] = 'system/drupal.system.date';
     return parent::form($form, $form_state);
   }
 
diff --git a/core/modules/system/system.libraries.yml b/core/modules/system/system.libraries.yml
index 34c90dd..b440e15 100644
--- a/core/modules/system/system.libraries.yml
+++ b/core/modules/system/system.libraries.yml
@@ -29,7 +29,7 @@ maintenance:
 drupal.system:
   version: VERSION
   js:
-    system.js: {}
+    js/system.js: {}
   dependencies:
     - core/jquery
     - core/drupal
@@ -39,7 +39,7 @@ drupal.system:
 drupal.system.modules:
   version: VERSION
   js:
-    system.modules.js: {}
+    js/system.modules.js: {}
   dependencies:
     - core/jquery
     - core/drupal
@@ -50,3 +50,14 @@ diff:
   css:
     component:
       css/system.diff.css: {}
+
+drupal.system.date:
+  version: VERSION
+  js:
+    js/system.date.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupalSettings
+    - core/jquery.once
+    - core/drupal.form
diff --git a/core/tests/Drupal/Tests/Core/Datetime/DateTest.php b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
index da9ebaa..1cbed5e 100644
--- a/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
+++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\Core\Datetime;
 
 use Drupal\Core\Datetime\DateFormatter;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -45,10 +46,22 @@ class DateTest extends UnitTestCase {
   protected $dateFormatter;
 
   protected function setUp() {
+    parent::setUp();
+
+    $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
+
     $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $this->entityManager->expects($this->once())->method('getStorage')->with('date_format')->willReturn($entity_storage);
+
     $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
     $this->stringTranslation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface');
 
+    $config_factory = $this->getConfigFactoryStub(['system.date' => ['country' => ['default' => 'GB']]]);
+    $container = new ContainerBuilder();
+    $container->set('config.factory', $config_factory);
+    $container->set('string_translation', $this->getStringTranslationStub());
+    \Drupal::setContainer($container);
+
     $this->dateFormatter = new DateFormatter($this->entityManager, $this->languageManager, $this->stringTranslation, $this->getConfigFactoryStub());
   }
 
@@ -57,7 +70,7 @@ protected function setUp() {
    *
    * @dataProvider providerTestFormatInterval
    *
-   * @see \Drupal\Core\Datetime\DateFormatter::formatInterval()
+   * @covers \Drupal\Core\Datetime\DateFormatter::formatInterval
    */
   public function testFormatInterval($interval, $granularity, $expected, $langcode = NULL) {
     // Mocks a simple formatPlural implementation.
@@ -128,4 +141,23 @@ public function testFormatIntervalZeroSecond() {
     $this->assertEquals('0 sec', $result);
   }
 
+  /**
+   * Tests the getSampleDateFormats method.
+   *
+   * @covers \Drupal\Core\Datetime\DateFormatter::getSampleDateFormats
+   */
+  public function testGetSampleDateFormats() {
+    $timestamp = strtotime('2015-03-22 14:23:00');
+    $expected = $this->dateFormatter->getSampleDateFormats('en', $timestamp, 'Europe/London');
+
+    // Removed characters related to timezone 'e' and 'T', as test does not have
+    // timezone set.
+    $date_characters = 'dDjlNSwzWFmMntLoYyaABgGhHisuIOPZcrU';
+    $date_chars = str_split($date_characters);
+
+    foreach ($date_chars as $val) {
+      $this->assertEquals($expected[$val], date($val, $timestamp));
+    }
+  }
+
 }
