diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php
index 7c34ed1..5f74152 100644
--- a/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php
+++ b/core/modules/datetime_range/src/Plugin/Field/FieldType/DateRangeItem.php
@@ -19,7 +19,8 @@
  *   description = @Translation("Create and store date ranges."),
  *   default_widget = "daterange_default",
  *   default_formatter = "daterange_default",
- *   list_class = "\Drupal\datetime_range\Plugin\Field\FieldType\DateRangeFieldItemList"
+ *   list_class = "\Drupal\datetime_range\Plugin\Field\FieldType\DateRangeFieldItemList",
+ *   constraints = {"DateRangeFormat" = {}, "DateRangeStartEnd" = {}}
  * )
  */
 class DateRangeItem extends DateTimeItem {
diff --git a/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraint.php b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraint.php
new file mode 100644
index 0000000..ecfe31c
--- /dev/null
+++ b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraint.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\datetime_range\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Validation constraint for DateTime items to ensure the format is correct.
+ *
+ * @Constraint(
+ *   id = "DateRangeFormat",
+ *   label = @Translation("Daterange formats valid for Daterange type.", context = "Validation"),
+ * )
+ */
+class DateRangeFormatConstraint extends Constraint {
+
+  /**
+   * Message for when the start value isn't a string.
+   *
+   * @var string
+   */
+  public $badStartType = "The date range start value must be a string.";
+
+  /**
+   * Message for when the end value isn't a string.
+   *
+   * @var string
+   */
+  public $badEndType = "The date range end value must be a string.";
+
+  /**
+   * Message for when the start value isn't in the proper format.
+   *
+   * @var string
+   */
+  public $badStartFormat = "The date range start value '@value' is invalid for the format '@format'";
+
+  /**
+   * Message for when the end value isn't in the proper format.
+   *
+   * @var string
+   */
+  public $badEndFormat = "The date range end value '@end_value' is invalid for the format '@format'";
+
+  /**
+   * Message for when the start value did not parse properly.
+   *
+   * @var string
+   */
+  public $badStartValue = "The date range start value '@value' did not parse properly for the format '@format'";
+
+  /**
+   * Message for when the end value did not parse properly.
+   *
+   * @var string
+   */
+  public $badEndValue = "The date range end value '@end_value' did not parse properly for the format '@format'";
+
+}
diff --git a/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraintValidator.php b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraintValidator.php
new file mode 100644
index 0000000..7e892ee
--- /dev/null
+++ b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeFormatConstraintValidator.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\datetime_range\Plugin\Validation\Constraint;
+
+use Drupal\Component\Datetime\DateTimePlus;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Constraint validator for DateRange items to ensure the format is correct.
+ */
+class DateRangeFormatConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($item, Constraint $constraint) {
+    $stop = FALSE;
+
+    /** @var $item \Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem */
+    if (!isset($item)) {
+      return;
+    }
+
+    $value = $item->getValue()['value'];
+    $end_value = $item->getValue()['end_value'];
+
+    if (!is_string($value)) {
+      $this->context->addViolation($constraint->badStartType);
+      $stop = TRUE;
+    }
+    if (!is_string($end_value)) {
+      $this->context->addViolation($constraint->badEndType);
+      $stop = TRUE;
+    }
+
+    if ($stop) {
+      return;
+    }
+
+    $datetime_type = $item->getFieldDefinition()->getSetting('datetime_type');
+    if ($datetime_type === DateRangeItem::DATETIME_TYPE_DATE) {
+      $format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
+    }
+    else {
+      $format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
+    }
+
+    $start_date = NULL;
+    try {
+      $start_date = DateTimePlus::createFromFormat($format, $value, new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->context->addViolation($constraint->badStartFormat, [
+        '@value' => $value,
+        '@format' => $format,
+      ]);
+      $stop = TRUE;
+    }
+    catch (\UnexpectedValueException $e) {
+      $this->context->addViolation($constraint->badStartValue, [
+        '@value' => $value,
+        '@format' => $format,
+      ]);
+      $stop = TRUE;
+    }
+
+    $end_date = NULL;
+    try {
+      $end_date = DateTimePlus::createFromFormat($format, $end_value, new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->context->addViolation($constraint->badEndFormat, [
+        '@end_value' => $end_value,
+        '@format' => $format,
+      ]);
+      $stop = TRUE;
+    }
+    catch (\UnexpectedValueException $e) {
+      $this->context->addViolation($constraint->badEndValue, [
+        '@end_value' => $end_value,
+        '@format' => $format,
+      ]);
+      $stop = TRUE;
+    }
+
+    if ($stop) {
+      return;
+    }
+
+    if ($start_date === NULL || $start_date->hasErrors()) {
+      $this->context->addViolation($constraint->badStartFormat, [
+        '@value' => $value,
+        '@format' => $format,
+      ]);
+    }
+
+    if ($end_date === NULL || $end_date->hasErrors()) {
+      $this->context->addViolation($constraint->badEndFormat, [
+        '@end_value' => $end_value,
+        '@format' => $format,
+      ]);
+    }
+  }
+
+}
diff --git a/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeStartEndConstraint.php b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeStartEndConstraint.php
new file mode 100644
index 0000000..74e8fa3
--- /dev/null
+++ b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeStartEndConstraint.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\datetime_range\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Validation constraint for DateTime items to ensure the format is correct.
+ *
+ * @Constraint(
+ *   id = "DateRangeStartEnd",
+ *   label = @Translation("Daterange formats valid for Daterange type.", context = "Validation"),
+ * )
+ */
+class DateRangeStartEndConstraint extends Constraint {
+
+  /**
+   * Message for when the end value is before the start value.
+   *
+   * @var string
+   */
+  public $badStartEnd = "The date range end value '@end_value' must be equal to or after the start value '@value'";
+
+}
diff --git a/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeStartEndConstraintValidator.php b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeStartEndConstraintValidator.php
new file mode 100644
index 0000000..1e5aaf1
--- /dev/null
+++ b/core/modules/datetime_range/src/Plugin/Validation/Constraint/DateRangeStartEndConstraintValidator.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\datetime_range\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Constraint validator for DateRange items to ensure the format is correct.
+ */
+class DateRangeStartEndConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($item, Constraint $constraint) {
+    if (!isset($item)) {
+      return;
+    }
+
+    /** @var $item \Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem */
+    $value = $item->getValue()['value'];
+    $end_value = $item->getValue()['end_value'];
+
+    // We cannot get the computed values from the item at this point, so we
+    // have to use string comparison. This may give false error messages, but
+    // the format constraints will also fail and provide a proper message for
+    // the user.
+    if (is_string($value) && is_string($end_value)) {
+      if ($value > $end_value) {
+        $this->context->addViolation($constraint->badStartEnd, [
+          '@value' => $value,
+          '@end_value' => $end_value,
+        ]);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestAlldayTest.php b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestAlldayTest.php
new file mode 100644
index 0000000..f74bc4b
--- /dev/null
+++ b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestAlldayTest.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Drupal\Tests\datetime_range\Functional\EntityResource\EntityTest;
+
+use Drupal\Core\Url;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
+
+/**
+ * Tests the daterange field constraint with 'allday' items.
+ *
+ * @group datetime
+ */
+class EntityTestAlldayTest extends EntityTestDaterangeTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $startDateString = '2017-02-28T13:00:00';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $endDateString = '2017-03-01T12:59:59';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $datetimeType = DateRangeItem::DATETIME_TYPE_ALLDAY;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {
+    parent::assertNormalizationEdgeCases($method, $url, $request_options);
+
+    if ($this->entity->getEntityType()->hasKey('bundle')) {
+      $fieldName = static::$fieldName;
+      $fieldFormat = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
+
+      // DX: 422 when date type is incorrect.
+
+      $value = ['2017', '03', '01', '21', '53', '00'];
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = ['2017', '03', '02', '21', '53', '00'];
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value must be a string.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = ['2017', '03', '01', '21', '53', '00'];
+      $end_value = ['2017', '03', '02', '21', '53', '00'];
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value must be a string.\n{$fieldName}.0: The date range end value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when date format is incorrect.
+
+      $value = '2017-03-01';
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = '2017-03-02';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = '2017-03-01';
+      $end_value = '2017-03-02';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' is invalid for the format '{$fieldFormat}'\n{$fieldName}.0: The date range end value '{$end_value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when date format is incorrect.
+
+      $value = '2016-13-55T20:02:00';
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = '2018-13-56T20:02:00';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = '2016-13-55T20:02:00';
+      $end_value = '2018-13-56T20:02:00';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0: The date range end value '{$end_value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when the end date is before the start date.
+
+      $value = static::$endDateString;
+      $end_value = static::$startDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' must be equal to or after the start value '{$value}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+    }
+  }
+
+}
diff --git a/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDateonlyTest.php b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDateonlyTest.php
new file mode 100644
index 0000000..72f1e3b
--- /dev/null
+++ b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDateonlyTest.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Drupal\Tests\datetime_range\Functional\EntityResource\EntityTest;
+
+use Drupal\Core\Url;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
+
+/**
+ * Tests the daterange field constraint with 'date' items.
+ *
+ * @group datetime
+ */
+class EntityTestDateonlyTest extends EntityTestDaterangeTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $startDateString = '2017-03-01';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $endDateString = '2017-03-02';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $datetimeType = DateRangeItem::DATETIME_TYPE_DATE;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {
+    parent::assertNormalizationEdgeCases($method, $url, $request_options);
+
+    if ($this->entity->getEntityType()->hasKey('bundle')) {
+      $fieldName = static::$fieldName;
+      $fieldFormat = DateTimeItemInterface::DATE_STORAGE_FORMAT;
+
+      // DX: 422 when date type is incorrect.
+
+      $value = ['2017', '03', '01'];
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = ['2017', '03', '02'];
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value must be a string.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = ['2017', '03', '01'];
+      $end_value = ['2017', '03', '02'];
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value must be a string.\n{$fieldName}.0: The date range end value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when date format is incorrect.
+
+      $value = '2017-03-01T20:02:00';
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = '2017-03-02T20:02:00';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = '2017-03-01T20:02:00';
+      $end_value = '2017-03-02T20:02:00';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' is invalid for the format '{$fieldFormat}'\n{$fieldName}.0: The date range end value '{$end_value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when date format is incorrect.
+
+      $value = '2016-13-55';
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = '2018-13-56';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = '2016-13-55';
+      $end_value = '2018-13-56';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0: The date range end value '{$end_value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when the end date is before the start date.
+
+      $value = static::$endDateString;
+      $end_value = static::$startDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' must be equal to or after the start value '{$value}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+    }
+  }
+
+}
diff --git a/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDaterangeTest.php b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDaterangeTest.php
new file mode 100644
index 0000000..83919ef
--- /dev/null
+++ b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDaterangeTest.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Drupal\Tests\datetime_range\Functional\EntityResource\EntityTest;
+
+use Drupal\Core\Url;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
+
+/**
+ * Tests the daterange field constraint with 'datetime' items.
+ *
+ * @group datetime
+ */
+class EntityTestDaterangeTest extends EntityTestDaterangeTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $startDateString = '2017-03-01T20:02:00';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $endDateString = '2017-03-02T20:02:00';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $datetimeType = DateRangeItem::DATETIME_TYPE_DATETIME;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {
+    parent::assertNormalizationEdgeCases($method, $url, $request_options);
+
+    if ($this->entity->getEntityType()->hasKey('bundle')) {
+      $fieldName = static::$fieldName;
+      $fieldFormat = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
+
+      // DX: 422 when date type is incorrect.
+
+      $value = ['2017', '03', '01', '21', '53', '00'];
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = ['2017', '03', '02', '21', '53', '00'];
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value must be a string.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = ['2017', '03', '01', '21', '53', '00'];
+      $end_value = ['2017', '03', '02', '21', '53', '00'];
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value must be a string.\n{$fieldName}.0: The date range end value must be a string.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when date format is incorrect.
+
+      $value = '2017-03-01';
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = '2017-03-02';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = '2017-03-01';
+      $end_value = '2017-03-02';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' is invalid for the format '{$fieldFormat}'\n{$fieldName}.0: The date range end value '{$end_value}' is invalid for the format '{$fieldFormat}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when date format is incorrect.
+
+      $value = '2016-13-55T20:02:00';
+      $end_value = static::$endDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = static::$startDateString;
+      $end_value = '2018-13-56T20:02:00';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      $value = '2016-13-55T20:02:00';
+      $end_value = '2018-13-56T20:02:00';
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range start value '{$value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0: The date range end value '{$end_value}' did not parse properly for the format '{$fieldFormat}'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+
+      // DX: 422 when the end date is before the start date.
+
+      $value = static::$endDateString;
+      $end_value = static::$startDateString;
+      $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The date range end value '{$end_value}' must be equal to or after the start value '{$value}'\n";
+      $this->doEdgeCaseCall($method, $url, $request_options, $value, $end_value, $message);
+    }
+  }
+
+}
diff --git a/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDaterangeTestBase.php b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDaterangeTestBase.php
new file mode 100644
index 0000000..d989ec4
--- /dev/null
+++ b/core/modules/datetime_range/tests/src/Functional/EntityResource/EntityTest/EntityTestDaterangeTestBase.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\Tests\datetime_range\Functional\EntityResource\EntityTest;
+
+use Drupal\Core\Url;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
+use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
+use Drupal\Tests\rest\Functional\EntityResource\EntityTest\EntityTestResourceTestBase;
+use GuzzleHttp\RequestOptions;
+
+/**
+ * Base class for daterange field constraints.
+ */
+class EntityTestDaterangeTestBase extends EntityTestResourceTestBase {
+
+  use AnonResourceTestTrait;
+
+  /**
+   * The starting ISO date string to use throughout the test.
+   *
+   * @var string
+   */
+  protected static $startDateString;
+
+  /**
+   * The ending ISO date string to use throughout the test.
+   *
+   * @var string
+   */
+  protected static $endDateString;
+
+  /**
+   * Daterange test field name.
+   *
+   * @var string
+   */
+  protected static $fieldName = 'field_daterange';
+
+  /**
+   * Daterange field type.
+   */
+  protected static $datetimeType = DateRangeItem::DATETIME_TYPE_DATETIME;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['datetime', 'datetime_range', 'entity_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Add daterange field.
+    FieldStorageConfig::create([
+      'field_name' => static::$fieldName,
+      'type' => 'daterange',
+      'entity_type' => static::$entityTypeId,
+      'settings' => ['datetime_type' => static::$datetimeType],
+    ])
+      ->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::$startDateString,
+      'end_value' => static::$endDateString,
+    ]);
+    $this->entity->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function createEntity() {
+    $entity_test = EntityTest::create([
+      'name' => 'Llama',
+      'type' => static::$entityTypeId,
+      static::$fieldName => [
+        'value' => static::$startDateString,
+        'end_value' => static::$endDateString,
+      ],
+    ]);
+    $entity_test->setOwnerId(0);
+    $entity_test->save();
+
+    return $entity_test;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedNormalizedEntity() {
+    return parent::getExpectedNormalizedEntity() + [
+        static::$fieldName => [
+          [
+            'value' => $this->entity->get(static::$fieldName)->value,
+            'end_value' => $this->entity->get(static::$fieldName)->end_value,
+          ],
+        ],
+      ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getNormalizedPostEntity() {
+    return parent::getNormalizedPostEntity() + [
+        static::$fieldName => [
+          [
+            'value' => static::$startDateString,
+            'end_value' => static::$endDateString,
+          ],
+        ],
+      ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {
+    parent::assertNormalizationEdgeCases($method, $url, $request_options);
+  }
+
+  /**
+   * Performs a REST call to test an edge case.
+   *
+   * @param string $method
+   *   HTTP method.
+   * @param \Drupal\Core\Url $url
+   *   URL to request.
+   * @param array $request_options
+   *   Request options to apply.
+   * @var mixed $value
+   *   The test start date value.
+   * @var mixed $end_value
+   *   The test end date value.
+   * @var string $message
+   *   The expected error message.
+   */
+  protected function doEdgeCaseCall($method, Url $url, array $request_options, $value, $end_value, $message) {
+    $normalization = $this->getNormalizedPostEntity();
+    $normalization[static::$fieldName][0]['value'] = $value;
+    $normalization[static::$fieldName][0]['end_value'] = $end_value;
+
+    $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
+    $response = $this->request($method, $url, $request_options);
+    $this->assertResourceErrorResponse(422, $message, $response);
+  }
+
+}
