diff --git a/core/lib/Drupal/Core/Datetime/Element/Datelist.php b/core/lib/Drupal/Core/Datetime/Element/Datelist.php
index 2f253b2..b560065 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datelist.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datelist.php
@@ -55,19 +55,21 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
     $date = NULL;
     if ($input !== FALSE) {
       $return = $input;
-      if (isset($input['ampm'])) {
-        if ($input['ampm'] == 'pm' && $input['hour'] < 12) {
-          $input['hour'] += 12;
+      if (is_null(static::checkEmptyInputs($input, $parts))) {
+        if (isset($input['ampm'])) {
+          if ($input['ampm'] == 'pm' && $input['hour'] < 12) {
+            $input['hour'] += 12;
+          }
+          elseif ($input['ampm'] == 'am' && $input['hour'] == 12) {
+            $input['hour'] -= 12;
+          }
+          unset($input['ampm']);
         }
-        elseif ($input['ampm'] == 'am' && $input['hour'] == 12) {
-          $input['hour'] -= 12;
+        $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL;
+        $date = DrupalDateTime::createFromArray($input, $timezone);
+        if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
+          static::incrementRound($date, $increment);
         }
-        unset($input['ampm']);
-      }
-      $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL;
-      $date = DrupalDateTime::createFromArray($input, $timezone);
-      if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
-        static::incrementRound($date, $increment);
       }
     }
     else {
@@ -300,6 +302,7 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat
     $input_exists = FALSE;
     $input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
     if ($input_exists) {
+      $first_empty = static::checkEmptyInputs($input, $element['#date_part_order']);
 
       // If there's empty input and the field is not required, set it to empty.
       if (empty($input['year']) && empty($input['month']) && empty($input['day']) && !$element['#required']) {
@@ -309,6 +312,10 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat
       elseif (empty($input['year']) && empty($input['month']) && empty($input['day']) && $element['#required']) {
         $form_state->setError($element, t('The %field date is required.'));
       }
+      elseif (!is_null($first_empty)) {
+        $message = t('The %field date is invalid. A value must be selected for %part.', array('%field' => !empty($element['#title']) ? $element['#title'] : '', '%part' => $first_empty));
+        $form_state->setError($element, $message);
+      }
       else {
         // If the input is valid, set it.
         $date = $input['object'];
@@ -317,13 +324,37 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat
         }
         // If the input is invalid, set an error.
         else {
-          $form_state->setError($element, t('The %field date is invalid.'));
+          $form_state->setError($element, t('The %field date is invalid.', array('%field' => !empty($element['#title']) ? $element['#title'] : '')));
         }
       }
     }
   }
 
   /**
+   * Checks the input array for empty values.
+   *
+   * Input array keys are checked against values in the parts array. Elements
+   * not in the parts array are ignored. If no empty values are found, null is
+   * returned. Otherwise, returns the first key in the input array that has an
+   * empty value.
+   *
+   * @param array $input
+   *
+   * @param array $parts
+   *
+   * @return string
+   *
+   */
+  protected static function checkEmptyInputs($input, $parts) {
+    foreach ($parts as $part) {
+      if (empty($input[$part])) {
+        return $part;
+      }
+    }
+    return null;
+  }
+
+  /**
    * Rounds minutes and seconds to nearest requested value.
    *
    * @param $date
diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
index 60637ff..43b4ad7 100644
--- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php
+++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
@@ -478,6 +478,33 @@ function testDatelistWidget() {
     $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
     $this->assertOptionSelected("edit-$field_name-0-value-hour", '17', 'Correct hour selected.');
     $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
+
+    // Test the widget for partial completion of fields.
+    entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
+      ->setComponent($field_name, array(
+        'type' => 'datetime_datelist',
+        'settings' => array(
+          'increment' => 1,
+          'date_order' => 'YMD',
+          'time_type' => '24',
+        ),
+      ))
+      ->save();
+    \Drupal::entityManager()->clearCachedFieldDefinitions();
+
+    // Display creation form.
+    $this->drupalGet('entity_test/add');
+
+    // Submit a partial date and ensure and error message is provided
+    $date_value = array('year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => '');
+    $edit = array();
+    foreach ($date_value as $part => $value) {
+      $edit["{$field_name}[0][value][$part]"] = $value;
+    }
+
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertText('error has been found');
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php
index 2592333..5291fc6 100644
--- a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php
+++ b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php
@@ -249,6 +249,22 @@ public function testDateTimezoneWithDateTimeObject() {
   }
 
   /**
+   * Tests the checkArray method.
+   *
+   * @param mixed $expected
+   *   Expected result of checkArray method
+   * @param array $date_value
+   *   Input argument for DateTimePlus::checkArray().
+   *
+   * @dataProvider providerTestCheckArray
+  *
+   * @covers ::checkArray
+   */
+  public function testCheckArray($expected, $date_value) {
+    $this->assertEquals($expected, DateTimePlus::checkArray($date_value));
+  }
+
+  /**
    * Provides data for date tests.
    *
    * @return array
@@ -533,4 +549,32 @@ public function providerTestDateTimestamp() {
     );
   }
 
+  /**
+   * Data provider for testCheckArray().
+   *
+   * @return array
+   *   An array of arrays, each containing:
+   *    - 'expected' - The expected result of DateTimePlusTest::checkArray().
+   *    - 'input' - Input for DateTimePlusTest::checkArray().
+   *
+   * @see testCheckArray()
+   */
+  public function providerTestCheckArray() {
+
+    $data = array(
+      // Checks for completion of date array
+      array(FALSE, array('year' => 2013, 'month' => '', 'day' => '', 'hour' => '', 'minute' => '')),
+      array(FALSE, array('year' => 2013, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => '')),
+      array(FALSE, array('year' => 2013, 'month' => '12', 'day' => '11', 'hour' => '', 'minute' => '')),
+      array(FALSE, array('year' => 2013, 'month' => '12', 'day' => '11', 'hour' => '10', 'minute' => '')),
+      array(TRUE, array('year' => 2013, 'month' => '12', 'day' => '11', 'hour' => '10', 'minute' => '09')),
+
+      // Checks for invalid time pairings
+      array(FALSE, array('year' => 2013, 'month' => '12', 'day' => '11', 'hour' => '10', 'minute' => '61')),
+      array(FALSE, array('year' => 2013, 'month' => '12', 'day' => '11', 'hour' => '25', 'minute' => '09')),
+    );
+
+    return $data;
+  }
+
 }
