diff --git a/core/lib/Drupal/Core/Datetime/Element/Datelist.php b/core/lib/Drupal/Core/Datetime/Element/Datelist.php
index 2f253b29e61930ec38fb0d9c1c22e4fe10d5726c..2caa4cad6b963c1ebe1f069ddb892ba9c2b527df 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 (empty(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 {
@@ -250,7 +252,7 @@ public static function processDatelist(&$element, FormStateInterface $form_state
           $title = '';
       }
 
-      $default = !empty($element['#value'][$part]) ? $element['#value'][$part] : '';
+      $default = isset($element['#value'][$part]) && trim($element['#value'][$part]) != '' ? $element['#value'][$part] : '';
       $value = $date instanceOf DrupalDateTime && !$date->hasErrors() ? $date->format($format) : $default;
       if (!empty($value) && $part != 'ampm') {
         $value = intval($value);
@@ -265,7 +267,7 @@ public static function processDatelist(&$element, FormStateInterface $form_state
         '#attributes' => $element['#attributes'],
         '#options' => $options,
         '#required' => $element['#required'],
-        '#error_no_message' => TRUE,
+        '#error_no_message' => FALSE,
       );
     }
 
@@ -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) {
+      $all_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,11 @@ 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 (!empty($all_empty)) {
+        foreach ($all_empty as $value){
+          $form_state->setError($element[$value], t('A value must be selected for %part.', array('%part' => $value)));
+        }
+      }
       else {
         // If the input is valid, set it.
         $date = $input['object'];
@@ -317,12 +325,34 @@ 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. Returns an array representing elements
+   * from the input array that have no value. If no empty values are found,
+   * returned array is empty.
+   *
+   * @param array $input
+   *   Array of individual inputs to check for value.
+   * @param array $parts
+   *   Array to check input against, ignoring elements not in this array.
+   *
+   * @return array
+   *   Array of keys from the input array that have no value, may be empty.
+   */
+  protected static function checkEmptyInputs($input, $parts) {
+    // Filters out empty array values, any valid value would have a string length.
+    $filtered_input = array_filter($input, 'strlen');
+    return array_diff($parts, array_keys($filtered_input));
+  }
+
   /**
    * Rounds minutes and seconds to nearest requested value.
    *
diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
index 50bd608d0c9300c7d770333eae26a62150272e42..d5d14a4dc3f5324be4abd257e4f4c36ca0516475 100644
--- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php
+++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
@@ -519,6 +519,84 @@ 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();
+
+    // Test the widget for validation notifications.
+    foreach ($this->datelistDataProvider() as $data) {
+      list($date_value, $expected) = $data;
+
+      // Display creation form.
+      $this->drupalGet('entity_test/add');
+
+      // Submit a partial date and ensure and error message is provided.
+      $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(t($expected));
+    }
+
+    // Test the widget for complete input with zeros as part of selections.
+    $this->drupalGet('entity_test/add');
+
+    $date_value = array('year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0');
+    $edit = array();
+    foreach ($date_value as $part => $value) {
+      $edit["{$field_name}[0][value][$part]"] = $value;
+    }
+
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $this->assertResponse(200);
+    preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
+    $id = $match[1];
+    $this->assertText(t('entity_test @id has been created.', array('@id' => $id)));
+
+    // Test the widget to ensure zeros are not deselected on validation.
+    $this->drupalGet('entity_test/add');
+
+    $date_value = array('year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => '0');
+    $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->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.');
+ }
+
+  /**
+   * The data provider for testing the validation of the datelist widget.
+   *
+   * @return array
+   *   An array of datelist input permutations to test.
+   */
+  protected function datelistDataProvider() {
+    return [
+      // Year only selected, validation error on Month, Day, Hour, Minute.
+      [['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], '4 errors have been found: Month, Day, Hour, Minute'],
+      // Year and Month selected, validation error on Day, Hour, Minute.
+      [['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''], '3 errors have been found: Day, Hour, Minute'],
+      // Year, Month and Day selected, validation error on Hour, Minute.
+      [['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''], '2 errors have been found: Hour, Minute'],
+      // Year, Month, Day and Hour selected, validation error on Minute only.
+      [['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''], '1 error has been found: Minute'],
+    ];
   }
 
   /**