diff --git a/core/includes/common.inc b/core/includes/common.inc
index b3dc9cb..81aeded 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -5,6 +5,7 @@
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Template\Attribute;
 
@@ -1975,26 +1976,12 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL
       break;
   }
 
-  // Create a DateTime object from the timestamp.
-  $date_time = date_create('@' . $timestamp);
-  // Set the time zone for the DateTime object.
-  date_timezone_set($date_time, $timezones[$timezone]);
-
-  // Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'.
-  // xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the
-  // input string.
-  // Paired backslashes are isolated to prevent errors in read-ahead evaluation.
-  // The read-ahead expression ensures that A matches, but not \A.
-  $format = preg_replace(array('/\\\\\\\\/', '/(?<!\\\\)([AaeDlMTF])/'), array("\xEF\\\\\\\\\xFF", "\xEF\\\\\$1\$1\xFF"), $format);
+  // Create a DrupalDateTime object from the timestamp and timezone.
+  $date_time = new DrupalDateTime($timestamp, $timezones[$timezone]);
 
   // Call date_format().
-  $format = date_format($date_time, $format);
-
-  // Pass the langcode to _format_date_callback().
-  _format_date_callback(NULL, $langcode);
-
-  // Translate the marked sequences.
-  return preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', '_format_date_callback', $format);
+  $settings = array('langcode' => $langcode);
+  return $date_time->format($format, $settings);
 }
 
 /**
@@ -4974,7 +4961,8 @@ function drupal_page_set_cache($body) {
       $cache->data['headers'][$header_names[$name_lower]] = $value;
       if ($name_lower == 'expires') {
         // Use the actual timestamp from an Expires header if available.
-        $cache->expire = strtotime($value);
+        $date = new DrupalDateTime($value);
+        $cache->expire = $date->getTimestamp();
       }
     }
 
diff --git a/core/lib/Drupal/Core/TypedData/Type/Date.php b/core/lib/Drupal/Core/TypedData/Type/Date.php
index 07767e9..ae54856 100644
--- a/core/lib/Drupal/Core/TypedData/Type/Date.php
+++ b/core/lib/Drupal/Core/TypedData/Type/Date.php
@@ -7,16 +7,17 @@
 
 namespace Drupal\Core\TypedData\Type;
 
+use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\TypedData\TypedDataInterface;
-use DateTime;
 use InvalidArgumentException;
 
 /**
  * The date data type.
  *
- * The plain value of a date is an instance of the DateTime class. For setting
- * the value an instance of the DateTime class, any string supported by
- * DateTime::__construct(), or a timestamp as integer may be passed.
+ * The plain value of a date is an instance of the DrupalDateTime class. For setting
+ * the value any value supported by the __construct() of the DrupalDateTime
+ * class will work, including a DateTime object, a timestamp, a string
+ * date, or an array of date parts.
  */
 class Date extends TypedData implements TypedDataInterface {
 
@@ -31,18 +32,17 @@ class Date extends TypedData implements TypedDataInterface {
    * Implements TypedDataInterface::setValue().
    */
   public function setValue($value) {
-    if ($value instanceof DateTime || !isset($value)) {
+
+    // Don't try to create a date from an empty value.
+    // It would default to the current time.
+    if (!isset($value)) {
       $this->value = $value;
     }
-    // Treat integer values as timestamps, even if supplied as PHP string.
-    elseif ((string) (int) $value === (string) $value) {
-      $this->value = new DateTime('@' . $value);
-    }
-    elseif (is_string($value)) {
-      $this->value = new DateTime($value);
-    }
     else {
-      throw new InvalidArgumentException("Invalid date format given.");
+      $this->value = $value instanceOf DrupalDateTime ? $value : new DrupalDateTime($value);
+      if ($this->value->hasErrors()) {
+        throw new InvalidArgumentException("Invalid date format given.");
+      }
     }
   }
 
@@ -50,7 +50,7 @@ public function setValue($value) {
    * Implements TypedDataInterface::getString().
    */
   public function getString() {
-    return (string) $this->getValue()->format(DateTime::ISO8601);
+    return (string) $this->getValue()->__toString();
   }
 
   /**
diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
index cead0cd..3b9fa9b 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\comment;
 
+use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityFormController;
 
@@ -236,7 +237,8 @@ public function validate(array $form, array &$form_state) {
       $account = user_load_by_name($form_state['values']['name']);
       $form_state['values']['uid'] = $account ? $account->uid : 0;
 
-      if ($form_state['values']['date'] && strtotime($form_state['values']['date']) === FALSE) {
+      $date = new DrupalDateTime($form_state['values']['date']);
+      if ($date->hasErrors()) {
         form_set_error('date', t('You have to specify a valid date.'));
       }
       if ($form_state['values']['name'] && !$form_state['values']['is_anonymous'] && !$account) {
@@ -271,7 +273,8 @@ public function submit(array $form, array &$form_state) {
     if (empty($comment->date)) {
       $comment->date = 'now';
     }
-    $comment->created = strtotime($comment->date);
+    $date = new DrupalDateTime($comment->date);
+    $comment->created = $date->getTimestamp();
     $comment->changed = REQUEST_TIME;
 
     // If the comment was posted by a registered user, assign the author's ID.
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index b9bf7eb..d9545ff 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\node;
 
+use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityFormController;
 
@@ -287,7 +288,8 @@ public function validate(array $form, array &$form_state) {
     }
 
     // Validate the "authored on" field.
-    if (!empty($node->date) && strtotime($node->date) === FALSE) {
+    $date = new DrupalDateTime($node->date);
+    if ($date->hasErrors()) {
       form_set_error('date', t('You have to specify a valid date.'));
     }
 
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 2dd1b57..59d9019 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -14,6 +14,7 @@
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Database\Query\SelectExtender;
 use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Template\Attribute;
 use Drupal\node\Node;
 use Drupal\file\File;
@@ -1107,7 +1108,8 @@ function node_submit($node) {
     $node->revision_uid = $user->uid;
   }
 
-  $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME;
+  $node_created = new DrupalDateTime(!empty($node->date) ? $node->date : REQUEST_TIME);
+  $node->created = $node_created->getTimestamp();
   $node->validated = TRUE;
 
   return $node;
diff --git a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php
index 62bd35e..00e4c95 100644
--- a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php
@@ -8,7 +8,7 @@
 namespace Drupal\system\Tests\TypedData;
 
 use Drupal\simpletest\WebTestBase;
-use DateTime;
+use Drupal\Core\Datetime\DrupalDateTime;
 use DateInterval;
 
 /**
@@ -72,7 +72,7 @@ public function testGetAndSet() {
     $this->assertNull($wrapper->getValue(), 'Float wrapper is null-able.');
 
     // Date type.
-    $value = new DateTime('@' . REQUEST_TIME);
+    $value = new DrupalDateTime(REQUEST_TIME);
     $wrapper = $this->createTypedData(array('type' => 'date'), $value);
     $this->assertTrue($wrapper->getValue() === $value, 'Date value was fetched.');
     $new_value = REQUEST_TIME + 1;
