diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Timestamp.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Timestamp.php
index 8c72c85..83b4c23 100644
--- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Timestamp.php
+++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Timestamp.php
@@ -26,8 +26,8 @@ class Timestamp extends IntegerData implements DateTimeInterface {
    * {@inheritdoc}
    */
   public function getDateTime() {
-    if ($this->value) {
-      return DrupalDateTime::createFromTimestamp($this->value);
+    if (isset($this->value)) {
+      return DrupalDateTime::createFromTimestamp($this->value, 'UTC');
     }
   }
 
diff --git a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDateRangeTest.php b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDateRangeTest.php
new file mode 100644
index 0000000..85cb43d
--- /dev/null
+++ b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDateRangeTest.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\Tests\datetime\Functional\EntityResource\EntityTest;
+
+use Drupal\Core\Url;
+use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
+use Drupal\Tests\rest\Functional\EntityResource\EntityTest\EntityTestResourceTestBase;
+
+/**
+ * Tests the 'daterange' field's normalization.
+ *
+ * @group datetime_range
+ */
+class EntityTestDateRangeTest extends EntityTestResourceTestBase {
+
+  use AnonResourceTestTrait;
+
+  /**
+   * The ISO date string to use throughout the test.
+   *
+   * @var string
+   */
+  protected static $dateString = '2017-03-01T20:02:00';
+
+  /**
+   * Datetime Range test field name.
+   *
+   * @var string
+   */
+  protected static $fieldName = 'field_daterange';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['datetime_range', 'entity_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Add datetime_range field.
+    FieldStorageConfig::create([
+      'field_name' => static::$fieldName,
+      'type' => 'daterange',
+      'entity_type' => static::$entityTypeId,
+      'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_ALLDAY],
+    ])
+      ->save();
+
+    FieldConfig::create([
+      'field_name' => static::$fieldName,
+      'entity_type' => static::$entityTypeId,
+      'bundle' => $this->entity->bundle(),
+    ])
+      ->save();
+
+    // Reload entity so that it has the new field.
+    $this->entity = $this->entityStorage->load($this->entity->id());
+    $this->entity->set(static::$fieldName, [
+      'value' => static::$dateString,
+      'end_value' => static::$dateString,
+    ]);
+    $this->entity->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function createEntity() {
+    $entity_test = EntityTest::create([
+      'name' => 'Llama',
+      'type' => static::$entityTypeId,
+    ]);
+    $entity_test->setOwnerId(0);
+    $entity_test->save();
+
+    return $entity_test;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedNormalizedEntity() {
+    return parent::getExpectedNormalizedEntity() + [
+      static::$fieldName => [
+        [
+          'value' => '2017-03-01T20:02:00+11:00',
+          'end_value' => '2017-03-01T20:02:00+11:00',
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getNormalizedPostEntity() {
+    return parent::getNormalizedPostEntity() + [
+        static::$fieldName => [
+          [
+            'value' => '2017-03-01T20:02:00',
+            'end_value' => '2017-03-01T20:02:00',
+          ],
+        ],
+      ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {
+    parent::assertNormalizationEdgeCases($method, $url, $request_options);
+
+    // @todo
+  }
+
+}
diff --git a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php
index ffce48c..0c75233 100644
--- a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php
+++ b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php
@@ -90,7 +90,7 @@ protected function getExpectedNormalizedEntity() {
     return parent::getExpectedNormalizedEntity() + [
       static::$fieldName => [
         [
-          'value' => $this->entity->get(static::$fieldName)->value,
+          'value' => '2017-03-01T20:02:00+11:00',
         ],
       ],
     ];
diff --git a/core/modules/hal/src/Normalizer/TimestampItemNormalizer.php b/core/modules/hal/src/Normalizer/TimestampItemNormalizer.php
index c389e79..f292940 100644
--- a/core/modules/hal/src/Normalizer/TimestampItemNormalizer.php
+++ b/core/modules/hal/src/Normalizer/TimestampItemNormalizer.php
@@ -4,15 +4,18 @@
 
 use Drupal\Core\Field\FieldItemInterface;
 use Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem;
+use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
 use Drupal\serialization\Normalizer\TimeStampItemNormalizerTrait;
 
 /**
  * Converts values for TimestampItem to and from common formats for hal.
+ *
+ * Overrides FieldItemNormalizer to
+ * - during normalization, add the 'format' key to assist consumers
+ * - during denormalization, use \Drupal\serialization\Normalizer\TimestampNormalizer
  */
 class TimestampItemNormalizer extends FieldItemNormalizer {
 
-  use TimeStampItemNormalizerTrait;
-
   /**
    * The interface or class that this Normalizer supports.
    *
@@ -24,8 +27,18 @@ class TimestampItemNormalizer extends FieldItemNormalizer {
    * {@inheritdoc}
    */
   protected function normalizedFieldValues(FieldItemInterface $field_item, $format, array $context) {
-    $normalized = parent::normalizedFieldValues($field_item, $format, $context);
-    return $this->processNormalizedValues($normalized);
+    return parent::normalizedFieldValues($field_item, $format, $context) + [
+      // 'format' is not a property on Timestamp objects. This is present to
+      // assist consumers of this data.
+      'format' => \DateTime::RFC3339,
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function constructValue($data, $context) {
+    return $this->serializer->denormalize($data, Timestamp::class, NULL, $context);
   }
 
 }
diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml
index dca6094..c883410 100644
--- a/core/modules/serialization/serialization.services.yml
+++ b/core/modules/serialization/serialization.services.yml
@@ -58,6 +58,16 @@ services:
       # Priority must be higher than serializer.normalizer.field_item and lower
       # than hal normalizers.
       - { name: normalizer, priority: 8, bc: bc_timestamp_normalizer_unix, bc_config_name: 'serialization.settings' }
+  serializer.normalizer.timestamp:
+    class: Drupal\serialization\Normalizer\TimestampNormalizer
+    tags:
+      # Priority must be higher than serializer.normalizer.primitive_data.
+      - { name: normalizer, priority: 20, bc: bc_timestamp_normalizer_unix, bc_config_name: 'serialization.settings' }
+  serializer.normalizer.datetimeiso8601:
+    class: \Drupal\serialization\Normalizer\DateTimeIso8601Normalizer
+    tags:
+      # Priority must be higher than serializer.normalizer.primitive_data.
+      - { name: normalizer, priority: 20 }
   serializer.normalizer.password_field_item:
       class: Drupal\serialization\Normalizer\NullNormalizer
       arguments: ['Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem']
diff --git a/core/modules/serialization/src/Normalizer/DateTimeIso8601Normalizer.php b/core/modules/serialization/src/Normalizer/DateTimeIso8601Normalizer.php
new file mode 100644
index 0000000..9951e11
--- /dev/null
+++ b/core/modules/serialization/src/Normalizer/DateTimeIso8601Normalizer.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\serialization\Normalizer;
+
+use Drupal\Core\TypedData\Plugin\DataType\DateTimeIso8601;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
+
+/**
+ * Converts values for the DateTimeIso8601 data type to RFC3339.
+ *
+ * @deprecated in 8.5.0, will be removed in Drupal 9.0.0, use \Drupal\serialization\Normalizer\DateTimeNormalizer instead.
+ */
+class DateTimeIso8601Normalizer extends DateTimeNormalizer {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $supportedInterfaceOrClass = DateTimeIso8601::class;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($datetime, $format = NULL, array $context = []) {
+    $field_item = $datetime->getParent();
+    if ($field_item instanceof DateTimeItem && $field_item->getFieldDefinition()->getFieldStorageDefinition()->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
+      // RFC3339 only covers combined date and time representations. For
+      // date-only representations, we need to use ISO 8601.
+      // @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates
+      return $datetime->getDateTime()->format('Y-m-d');
+    }
+    return parent::normalize($datetime, $format, $context);
+  }
+
+
+}
diff --git a/core/modules/serialization/src/Normalizer/DateTimeNormalizer.php b/core/modules/serialization/src/Normalizer/DateTimeNormalizer.php
new file mode 100644
index 0000000..b43f635
--- /dev/null
+++ b/core/modules/serialization/src/Normalizer/DateTimeNormalizer.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\serialization\Normalizer;
+
+use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
+use Drupal\Core\TypedData\Type\DateTimeInterface;
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
+
+/**
+ * Converts values for datetime objects to RFC3339 and from common formats.
+ *
+ * @internal
+ *
+ * Note that this is class can become the 'serializer.normalizer.datetime'
+ * service in Drupal 9.0.0 and allow the 'serializer.normalizer.datetimeiso8601'
+ * service to be removed in Drupal 9.0.0. That is not possible today, because
+ * this class also works for \Drupal\Core\TypedData\Plugin\DataType\Timestamp
+ * objects, but those must be ignored while the 'bc_timestamp_normalizer_unix'
+ * BC flag is enabled. If this class were already an active service, it'd cause
+ * 'timestamp' fields to not have (numeric) UNIX timestamps as normalized values
+ * anymore, which would break BC.
+ */
+class DateTimeNormalizer extends NormalizerBase implements DenormalizerInterface {
+
+  /**
+   * Allowed datetime formats for the denormalizer.
+   *
+   * The list is chosen to be unambiguous and language neutral, but also common
+   * for data interchange.
+   *
+   * @var string[]
+   *
+   * @see http://php.net/manual/en/datetime.createfromformat.php
+   */
+  protected $allowedFormats = [
+    'RFC 3339' => \DateTime::RFC3339,
+    'ISO 8601' => \DateTime::ISO8601,
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $supportedInterfaceOrClass = DateTimeInterface::class;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function normalize($datetime, $format = NULL, array $context = []) {
+    return $datetime->getDateTime()->format(\DateTime::RFC3339);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = []) {
+    // Loop through the allowed formats and create a \DateTime from the
+    // input data if it matches the defined pattern. Since the formats are
+    // unambiguous (i.e., they reference an absolute time with a defined time
+    // zone), only one will ever match.
+
+    // First check for a provided format.
+    if (!empty($context['datetime_format']) && in_array($context['datetime_format'], $this->allowedFormats)) {
+      return \DateTime::createFromFormat($context['datetime_format'], $data['value']);
+    }
+    // Otherwise, loop through formats.
+    else {
+      foreach ($this->allowedFormats as $format) {
+        $date = \DateTime::createFromFormat($format, $data['value']);
+        if ($date !== FALSE) {
+          return $date;
+        }
+      }
+    }
+
+    $format_strings = [];
+
+    foreach ($this->allowedFormats as $label => $format) {
+      $format_strings[] = "\"$format\" ($label)";
+    }
+
+    $formats = implode(', ', $format_strings);
+    throw new UnexpectedValueException(sprintf('The specified date "%s" is not in an accepted format: %s.', $data['value'], $formats));
+  }
+
+}
diff --git a/core/modules/serialization/src/Normalizer/TimeStampItemNormalizerTrait.php b/core/modules/serialization/src/Normalizer/TimeStampItemNormalizerTrait.php
index 78f6030..f251f25 100644
--- a/core/modules/serialization/src/Normalizer/TimeStampItemNormalizerTrait.php
+++ b/core/modules/serialization/src/Normalizer/TimeStampItemNormalizerTrait.php
@@ -4,8 +4,12 @@
 
 use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 
+@trigger_error(__NAMESPACE__ . '\TimeStampItemNormalizerTrait is deprecated in Drupal 8.5.0 and will be removed in Drupal 9.0.0. Use \Drupal\serialization\Normalizer\TimestampNormalizer instead.', E_USER_DEPRECATED);
+
 /**
  * A trait for TimestampItem normalization functionality.
+ *
+ * @deprecated in 8.5.0, use \Drupal\serialization\Normalizer\TimestampNormalizer instead.
  */
 trait TimeStampItemNormalizerTrait {
 
diff --git a/core/modules/serialization/src/Normalizer/TimestampItemNormalizer.php b/core/modules/serialization/src/Normalizer/TimestampItemNormalizer.php
index 704b22f..c6437f6 100644
--- a/core/modules/serialization/src/Normalizer/TimestampItemNormalizer.php
+++ b/core/modules/serialization/src/Normalizer/TimestampItemNormalizer.php
@@ -3,15 +3,15 @@
 namespace Drupal\serialization\Normalizer;
 
 use Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem;
-use Symfony\Component\Serializer\Exception\InvalidArgumentException;
+use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
 
 /**
  * Converts values for TimestampItem to and from common formats.
+ *
+ * Overrides FieldItemNormalizer to use\Drupal\serialization\Normalizer\TimestampNormalizer
  */
 class TimestampItemNormalizer extends FieldItemNormalizer {
 
-  use TimeStampItemNormalizerTrait;
-
   /**
    * The interface or class that this Normalizer supports.
    *
@@ -22,21 +22,22 @@ class TimestampItemNormalizer extends FieldItemNormalizer {
   /**
    * {@inheritdoc}
    */
-  public function normalize($field_item, $format = NULL, array $context = []) {
-    $data = parent::normalize($field_item, $format, $context);
-
-    return $this->processNormalizedValues($data);
+  public function normalize($object, $format = NULL, array $context = []) {
+    return parent::normalize($object, $format, $context) + [
+      // 'format' is not a property on Timestamp objects. This is present to
+      // assist consumers of this data.
+      'format' => \DateTime::RFC3339,
+    ];
   }
 
   /**
    * {@inheritdoc}
    */
-  public function denormalize($data, $class, $format = NULL, array $context = []) {
-    if (empty($data['value'])) {
-      throw new InvalidArgumentException('No "value" attribute present');
+  protected function constructValue($data, $context) {
+    if (!empty($data['format'])) {
+      $context['datetime_format'] = $data['format'];
     }
-
-    return parent::denormalize($data, $class, $format, $context);
+    return $this->serializer->denormalize($data, Timestamp::class, NULL, $context);
   }
 
 }
diff --git a/core/modules/serialization/src/Normalizer/TimestampNormalizer.php b/core/modules/serialization/src/Normalizer/TimestampNormalizer.php
new file mode 100644
index 0000000..123f105
--- /dev/null
+++ b/core/modules/serialization/src/Normalizer/TimestampNormalizer.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\serialization\Normalizer;
+
+use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
+
+/**
+ * Converts values for the Timestamp data type to and from common formats.
+ */
+class TimestampNormalizer extends DateTimeNormalizer {
+
+  /**
+   * Allowed timestamps formats for the denormalizer.
+   *
+   * The denormalizer allows deserialization to timestamps from three
+   * different formats. Validation of the input data and creation of the
+   * numerical timestamp value is handled with \DateTime::createFromFormat().
+   * The list is chosen to be unambiguous and language neutral, but also common
+   * for data interchange.
+   *
+   * @var string[]
+   *
+   * @see http://php.net/manual/en/datetime.createfromformat.php
+   */
+  protected $allowedFormats = [
+    'UNIX timestamp' => 'U',
+    'ISO 8601' => \DateTime::ISO8601,
+    'RFC 3339' => \DateTime::RFC3339,
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $supportedInterfaceOrClass = Timestamp::class;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = []) {
+    $denormalized = parent::denormalize($data, $class, $format, $context);
+    return ['value' => $denormalized->getTimestamp()];
+  }
+
+}
diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeNormalizerTest.php
new file mode 100644
index 0000000..f7f4d79
--- /dev/null
+++ b/core/modules/serialization/tests/src/Unit/Normalizer/DateTimeNormalizerTest.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\Tests\serialization\Unit\Normalizer;
+
+use Drupal\Component\Datetime\DateTimePlus;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\TypedData\Plugin\DataType\DateTimeIso8601;
+use Drupal\Core\TypedData\Plugin\DataType\IntegerData;
+use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
+use Drupal\Core\TypedData\Type\DateTimeInterface;
+use Drupal\serialization\Normalizer\DateTimeNormalizer;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+
+/**
+ * Unit test coverage for @DataTypes implementing DateTimeInterface.
+ *
+ * @group serialization
+ * @coversDefaultClass \Drupal\serialization\Normalizer\DateTimeNormalizer
+ * @see \Drupal\Core\TypedData\Type\DateTimeInterface
+ */
+class DateTimeNormalizerTest extends UnitTestCase {
+
+  /**
+   * The tested data type's normalizer.
+   *
+   * @var \Drupal\serialization\Normalizer\DateTimeNormalizer
+   */
+  protected $normalizer;
+
+  /**
+   * The tested data type.
+   *
+   * @var \Drupal\Core\TypedData\Type\DateTimeInterface
+   */
+  protected $data;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->normalizer = new DateTimeNormalizer();
+    $this->data = $this->prophesize(DateTimeInterface::class);
+  }
+
+  /**
+   * @covers ::supportsNormalization
+   */
+  public function testSupportsNormalization() {
+    $this->assertTrue($this->normalizer->supportsNormalization($this->data->reveal()));
+
+    $datetimeiso8601= $this->prophesize(DateTimeIso8601::class);
+    $this->assertTrue($this->normalizer->supportsNormalization($datetimeiso8601->reveal()));
+
+    $integer = $this->prophesize(IntegerData::class);
+    $this->assertFalse($this->normalizer->supportsNormalization($integer->reveal()));
+  }
+
+  /**
+   * @covers ::supportsDenormalization
+   */
+  public function testSupportsDenormalization() {
+    $this->assertTrue($this->normalizer->supportsDenormalization($this->data->reveal(), DateTimeInterface::class));
+  }
+
+  /**
+   * @covers ::normalize
+   */
+  public function testNormalize() {
+    $random_rfc_3339_string = $this->randomMachineName();
+
+    $drupal_date_time = $this->prophesize(DrupalDateTime::class);
+    $drupal_date_time->format(\DateTime::RFC3339)
+      ->willReturn($random_rfc_3339_string);
+
+    $this->data->getDateTime()
+      ->willReturn($drupal_date_time->reveal());
+
+    $normalized = $this->normalizer->normalize($this->data->reveal());
+    $this->assertSame($random_rfc_3339_string, $normalized);
+  }
+
+  /**
+   * Tests the denormalize function with good data.
+   *
+   * @covers ::denormalize
+   * @dataProvider providerTestDenormalizeValidFormats
+   */
+  public function testDenormalizeValidFormats($value, $expected) {
+    $normalized = ['value' => $value];
+
+    $denormalized = $this->normalizer->denormalize($normalized, DateTimeInterface::class, NULL, []);
+    $this->assertSame(0, $denormalized->getTimestamp() - $expected->getTimestamp());
+    $this->assertEquals($expected, $denormalized);
+  }
+
+  /**
+   * Data provider for testDenormalizeValidFormats.
+   *
+   * @return array
+   */
+  public function providerTestDenormalizeValidFormats() {
+    $data = [];
+
+    $data['RFC3339'] = ['2016-11-06T09:02:00+00:00', new \DateTimeImmutable('2016-11-06T09:02:00+00:00')];
+    $data['RFC3339 +0100'] = ['2016-11-06T09:02:00+01:00', new \DateTimeImmutable('2016-11-06T09:02:00+01:00')];
+    $data['RFC3339 -0600'] = ['2016-11-06T09:02:00-06:00', new \DateTimeImmutable('2016-11-06T09:02:00-06:00')];
+
+    $data['ISO8601'] = ['2016-11-06T09:02:00+0000', new \DateTimeImmutable('2016-11-06T09:02:00+00:00')];
+    $data['ISO8601 +0100'] = ['2016-11-06T09:02:00+0100', new \DateTimeImmutable('2016-11-06T09:02:00+01:00')];
+    $data['ISO8601 -0600'] = ['2016-11-06T09:02:00-0600', new \DateTimeImmutable('2016-11-06T09:02:00-06:00')];
+
+    return $data;
+  }
+
+  /**
+   * Tests the denormalize function with bad data.
+   *
+   * @covers ::denormalize
+   */
+  public function testDenormalizeException() {
+    $this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "Y-m-d\TH:i:sP" (RFC 3339), "Y-m-d\TH:i:sO" (ISO 8601).');
+
+    $normalized = ['value' => '2016/11/06 09:02am GMT'];
+
+    $this->normalizer->denormalize($normalized, DateTimeInterface::class, NULL, []);
+  }
+
+}
diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php
index c4e3514..626b766 100644
--- a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php
+++ b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php
@@ -5,21 +5,21 @@
 use Drupal\Core\Field\Plugin\Field\FieldType\CreatedItem;
 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem;
+use Drupal\Core\TypedData\DataDefinitionInterface;
+use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
 use Drupal\serialization\Normalizer\TimestampItemNormalizer;
 use Drupal\Tests\UnitTestCase;
-use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 use Symfony\Component\Serializer\Serializer;
 
 /**
- * Tests that entities can be serialized to supported core formats.
+ * Tests that TimestampItem (de)normalization uses Timestamp (de)normalization.
  *
  * @group serialization
  * @coversDefaultClass \Drupal\serialization\Normalizer\TimestampItemNormalizer
+ * @see \Drupal\serialization\Normalizer\TimestampNormalizer
  */
 class TimestampItemNormalizerTest extends UnitTestCase {
 
-  use InternalTypedDataTestTrait;
-
   /**
    * @var \Drupal\serialization\Normalizer\TimestampItemNormalizer
    */
@@ -68,88 +68,78 @@ public function testSupportsDenormalization() {
   }
 
   /**
-   * Tests the normalize function.
-   *
    * @covers ::normalize
+   * @see \Drupal\Tests\serialization\Unit\Normalizer\TimestampNormalizerTest
    */
   public function testNormalize() {
-    $expected = ['value' => '2016-11-06T09:02:00+00:00', 'format' => \DateTime::RFC3339];
-
+    // Mock TimestampItem @FieldType, which contains a Timestamp @DataType,
+    // which has a DataDefinition.
+    $data_definition = $this->prophesize(DataDefinitionInterface::class);
+    $data_definition->isInternal()
+      ->willReturn(FALSE)
+      ->shouldBeCalled();
+    $timestamp = $this->prophesize(Timestamp::class);
+    $timestamp->getDataDefinition()
+      ->willReturn($data_definition->reveal())
+      ->shouldBeCalled();
+    $timestamp = $timestamp->reveal();
     $timestamp_item = $this->createTimestampItemProphecy();
-    $timestamp_item->getIterator()
-      ->willReturn(new \ArrayIterator(['value' => 1478422920]));
-
-    $value_property = $this->getTypedDataProperty(FALSE);
     $timestamp_item->getProperties(TRUE)
-      ->willReturn(['value' => $value_property])
+      ->willReturn(['value' => $timestamp])
       ->shouldBeCalled();
 
+    // Mock Serializer service, to assert that the Timestamp @DataType
+    // normalizer would be called.
+    $timestamp_datetype_normalization = $this->randomMachineName();
     $serializer_prophecy = $this->prophesize(Serializer::class);
-
-    $serializer_prophecy->normalize($value_property, NULL, [])
-      ->willReturn(1478422920)
+    // This is where \Drupal\serialization\Normalizer\TimestampNormalizer would
+    // be called.
+    $serializer_prophecy->normalize($timestamp, NULL, [])
+      ->willReturn($timestamp_datetype_normalization)
       ->shouldBeCalled();
 
     $this->normalizer->setSerializer($serializer_prophecy->reveal());
 
     $normalized = $this->normalizer->normalize($timestamp_item->reveal());
-    $this->assertSame($expected, $normalized);
+    $this->assertSame(['value' => $timestamp_datetype_normalization, 'format' => \DateTime::RFC3339], $normalized);
   }
 
   /**
-   * Tests the denormalize function with good data.
-   *
    * @covers ::denormalize
-   * @dataProvider providerTestDenormalizeValidFormats
    */
-  public function testDenormalizeValidFormats($value, $expected) {
-    $normalized = ['value' => $value];
+  public function testDenormalize() {
+    $timestamp_item_normalization = [
+      'value' => $this->randomMachineName(),
+      'format' => \DateTime::RFC3339,
+    ];
+    $timestamp_data_denormalization = [
+      'value' => $this->randomMachineName(),
+    ];
 
     $timestamp_item = $this->createTimestampItemProphecy();
-    // The field item should be set with the expected timestamp.
-    $timestamp_item->setValue(['value' => $expected])
+    // The field item should get the Timestamp @DataType denormalization set as
+    // a value, in FieldItemNormalizer::denormalize().
+    $timestamp_item->setValue($timestamp_data_denormalization)
       ->shouldBeCalled();
 
-    $context = ['target_instance' => $timestamp_item->reveal()];
-
-    $denormalized = $this->normalizer->denormalize($normalized, TimestampItem::class, NULL, $context);
-    $this->assertTrue($denormalized instanceof TimestampItem);
-  }
-
-  /**
-   * Data provider for testDenormalizeValidFormats.
-   *
-   * @return array
-   */
-  public function providerTestDenormalizeValidFormats() {
-    $expected_stamp = 1478422920;
-
-    $data = [];
-
-    $data['U'] = [$expected_stamp, $expected_stamp];
-    $data['RFC3339'] = ['2016-11-06T09:02:00+00:00', $expected_stamp];
-    $data['RFC3339 +0100'] = ['2016-11-06T09:02:00+01:00', $expected_stamp - 1 * 3600];
-    $data['RFC3339 -0600'] = ['2016-11-06T09:02:00-06:00', $expected_stamp + 6 * 3600];
-
-    $data['ISO8601'] = ['2016-11-06T09:02:00+0000', $expected_stamp];
-    $data['ISO8601 +0100'] = ['2016-11-06T09:02:00+0100', $expected_stamp - 1 * 3600];
-    $data['ISO8601 -0600'] = ['2016-11-06T09:02:00-0600', $expected_stamp + 6 * 3600];
+    $context = [
+      'target_instance' => $timestamp_item->reveal(),
+      'datetime_format' => \DateTime::RFC3339,
+    ];
 
-    return $data;
-  }
-
-  /**
-   * Tests the denormalize function with bad data.
-   *
-   * @covers ::denormalize
-   */
-  public function testDenormalizeException() {
-    $this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "U" (UNIX timestamp), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:sP" (RFC 3339).');
+    // Mock Serializer service, to assert that the Timestamp @DataType
+    // denormalizer would be called.
+    $serializer_prophecy = $this->prophesize(Serializer::class);
+    // This is where \Drupal\serialization\Normalizer\TimestampNormalizer would
+    // be called.
+    $serializer_prophecy->denormalize($timestamp_item_normalization, Timestamp::class, NULL, $context)
+      ->willReturn($timestamp_data_denormalization)
+      ->shouldBeCalled();
 
-    $context = ['target_instance' => $this->createTimestampItemProphecy()->reveal()];
+    $this->normalizer->setSerializer($serializer_prophecy->reveal());
 
-    $normalized = ['value' => '2016/11/06 09:02am GMT'];
-    $this->normalizer->denormalize($normalized, TimestampItem::class, NULL, $context);
+    $denormalized = $this->normalizer->denormalize($timestamp_item_normalization, TimestampItem::class, NULL, $context);
+    $this->assertTrue($denormalized instanceof TimestampItem);
   }
 
   /**
diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampNormalizerTest.php
new file mode 100644
index 0000000..1a2f654
--- /dev/null
+++ b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampNormalizerTest.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\Tests\serialization\Unit\Normalizer;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\TypedData\Plugin\DataType\IntegerData;
+use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
+use Drupal\Core\TypedData\Type\DateTimeInterface;
+use Drupal\serialization\Normalizer\TimestampNormalizer;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+
+/**
+ * Unit test coverage for the "Timestamp" @DataType.
+ *
+ * @group serialization
+ * @coversDefaultClass \Drupal\serialization\Normalizer\TimestampNormalizer
+ * @see \Drupal\Core\TypedData\Plugin\DataType\Timestamp
+ */
+class TimestampNormalizerTest extends UnitTestCase {
+
+  /**
+   * The tested data type's normalizer.
+   *
+   * @var \Drupal\serialization\Normalizer\TimestampNormalizer
+   */
+  protected $normalizer;
+
+  /**
+   * The tested data type.
+   *
+   * @var \Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem
+   */
+  protected $data;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->normalizer = new TimestampNormalizer();
+    $this->data = $this->prophesize(Timestamp::class);
+  }
+
+  /**
+   * @covers ::supportsNormalization
+   */
+  public function testSupportsNormalization() {
+    $this->assertTrue($this->normalizer->supportsNormalization($this->data->reveal()));
+
+    $integer = $this->prophesize(IntegerData::class);
+    $this->assertFalse($this->normalizer->supportsNormalization($integer->reveal()));
+
+    $datetime = $this->prophesize(DateTimeInterface::class);
+    $this->assertFalse($this->normalizer->supportsNormalization($datetime->reveal()));
+  }
+
+  /**
+   * @covers ::supportsDenormalization
+   */
+  public function testSupportsDenormalization() {
+    $this->assertTrue($this->normalizer->supportsDenormalization($this->data->reveal(), Timestamp::class));
+  }
+
+  /**
+   * @covers ::normalize
+   */
+  public function testNormalize() {
+    $random_rfc_3339_string = $this->randomMachineName();
+
+    $drupal_date_time = $this->prophesize(DrupalDateTime::class);
+    $drupal_date_time->format(\DateTime::RFC3339)
+      ->willReturn($random_rfc_3339_string);
+
+    $this->data->getDateTime()
+      ->willReturn($drupal_date_time->reveal());
+
+    $normalized = $this->normalizer->normalize($this->data->reveal());
+    $this->assertSame($random_rfc_3339_string, $normalized);
+  }
+
+  /**
+   * Tests the denormalize function with good data.
+   *
+   * @covers ::denormalize
+   * @dataProvider providerTestDenormalizeValidFormats
+   */
+  public function testDenormalizeValidFormats($value, $expected) {
+    $normalized = ['value' => $value];
+
+    $denormalized = $this->normalizer->denormalize($normalized, Timestamp::class, NULL, []);
+    $this->assertSame(['value' => $expected], $denormalized);
+  }
+
+  /**
+   * Data provider for testDenormalizeValidFormats.
+   *
+   * @return array
+   */
+  public function providerTestDenormalizeValidFormats() {
+    $expected_stamp = 1478422920;
+
+    $data = [];
+
+    $data['U'] = [$expected_stamp, $expected_stamp];
+    $data['RFC3339'] = ['2016-11-06T09:02:00+00:00', $expected_stamp];
+    $data['RFC3339 +0100'] = ['2016-11-06T09:02:00+01:00', $expected_stamp - 1 * 3600];
+    $data['RFC3339 -0600'] = ['2016-11-06T09:02:00-06:00', $expected_stamp + 6 * 3600];
+
+    $data['ISO8601'] = ['2016-11-06T09:02:00+0000', $expected_stamp];
+    $data['ISO8601 +0100'] = ['2016-11-06T09:02:00+0100', $expected_stamp - 1 * 3600];
+    $data['ISO8601 -0600'] = ['2016-11-06T09:02:00-0600', $expected_stamp + 6 * 3600];
+
+    return $data;
+  }
+
+  /**
+   * Tests the denormalize function with bad data.
+   *
+   * @covers ::denormalize
+   */
+  public function testDenormalizeException() {
+    $this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "U" (UNIX timestamp), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:sP" (RFC 3339).');
+
+    $normalized = ['value' => '2016/11/06 09:02am GMT'];
+
+    $this->normalizer->denormalize($normalized, Timestamp::class, NULL, []);
+  }
+
+}
