diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 62f2d6f..ca44bc8 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -540,9 +540,11 @@ function template_preprocess_datetime_form(&$variables) {
  */
 function template_preprocess_datetime_wrapper(&$variables) {
   $element = $variables['element'];
+  $element['#title_display'] = isset($element['#title_display']) ? $element['#title_display'] : 'before';
 
   if (!empty($element['#title'])) {
     $variables['title'] = $element['#title'];
+    $variables['title_display'] = $element['#title_display'];
   }
 
   // Suppress error messages.
diff --git a/core/lib/Drupal/Core/Datetime/Element/Datelist.php b/core/lib/Drupal/Core/Datetime/Element/Datelist.php
index 15ce2f3..d748265 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datelist.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datelist.php
@@ -310,7 +310,7 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat
       }
       // If there's empty input and the field is required, set an error.
       elseif (empty($input['year']) && empty($input['month']) && empty($input['day']) && $element['#required']) {
-        $form_state->setError($element, t('The %field date is required.'));
+        $form_state->setError($element, t('The %field date is required.', array('%field' => !empty($element['#title']) ? $element['#title'] : '')));
       }
       elseif (!empty($all_empty)) {
         foreach ($all_empty as $value){
diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
index dfe750e..64da665 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
@@ -28,7 +28,6 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     // title because the actual title display is handled at a higher level by
     // the Field module.
 
-    $element['#theme_wrappers'][] = 'datetime_wrapper';
     $element['#attributes']['class'][] = 'container-inline';
 
     $element['value'] = array(
@@ -37,6 +36,8 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#date_increment' => 1,
       '#date_timezone' => drupal_get_user_timezone(),
       '#required' => $element['#required'],
+      '#title' => $element['#title'],
+      '#title_display' => 'invisible',
     );
 
     if ($items[$delta]->date) {
diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
index 14ad190..646a152 100644
--- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php
+++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
@@ -725,6 +725,7 @@ function testInvalidField() {
     $this->fieldStorage->setSetting('datetime_type', 'datetime');
     $this->fieldStorage->save();
     $field_name = $this->fieldStorage->getName();
+    $field_label = $this->field->getLabel();
 
     // Display creation form.
     $this->drupalGet('entity_test/add');
@@ -738,7 +739,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => '12:00:00',
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', 'Empty date value has been caught.');
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), 'Empty date value has been caught.');
 
     $date_value = 'aaaa-12-01';
     $edit = array(
@@ -746,7 +747,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => '00:00:00',
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', format_string('Invalid year value %date has been caught.', array('%date' => $date_value)));
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), format_string('Invalid year value %date has been caught.', array('%date' => $date_value)));
 
     $date_value = '2012-75-01';
     $edit = array(
@@ -754,7 +755,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => '00:00:00',
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', format_string('Invalid month value %date has been caught.', array('%date' => $date_value)));
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), format_string('Invalid month value %date has been caught.', array('%date' => $date_value)));
 
     $date_value = '2012-12-99';
     $edit = array(
@@ -762,7 +763,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => '00:00:00',
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', format_string('Invalid day value %date has been caught.', array('%date' => $date_value)));
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), format_string('Invalid day value %date has been caught.', array('%date' => $date_value)));
 
     $date_value = '2012-12-01';
     $time_value = '';
@@ -771,7 +772,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => $time_value,
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', 'Empty time value has been caught.');
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), 'Empty time value has been caught.');
 
     $date_value = '2012-12-01';
     $time_value = '49:00:00';
@@ -780,7 +781,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => $time_value,
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', format_string('Invalid hour value %time has been caught.', array('%time' => $time_value)));
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), format_string('Invalid hour value %time has been caught.', array('%time' => $time_value)));
 
     $date_value = '2012-12-01';
     $time_value = '12:99:00';
@@ -789,7 +790,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => $time_value,
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', format_string('Invalid minute value %time has been caught.', array('%time' => $time_value)));
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), format_string('Invalid minute value %time has been caught.', array('%time' => $time_value)));
 
     $date_value = '2012-12-01';
     $time_value = '12:15:99';
@@ -798,7 +799,7 @@ function testInvalidField() {
       "{$field_name}[0][value][time]" => $time_value,
     );
     $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->assertText('date is invalid', format_string('Invalid second value %time has been caught.', array('%time' => $time_value)));
+    $this->assertText(format_string('The @field_name date is invalid', array('@field_name' => $field_label)), format_string('Invalid second value %time has been caught.', array('%time' => $time_value)));
   }
 
   /**
diff --git a/core/modules/system/templates/datetime-wrapper.html.twig b/core/modules/system/templates/datetime-wrapper.html.twig
index 8430baa..5258361 100644
--- a/core/modules/system/templates/datetime-wrapper.html.twig
+++ b/core/modules/system/templates/datetime-wrapper.html.twig
@@ -19,6 +19,7 @@
   set title_classes = [
     required ? 'js-form-required',
     required ? 'form-required',
+    title_display == 'invisible' ? 'visually-hidden'
   ]
 %}
 {% if title %}
diff --git a/core/themes/classy/templates/form/datetime-wrapper.html.twig b/core/themes/classy/templates/form/datetime-wrapper.html.twig
index 3f6aa59..efd06e0 100644
--- a/core/themes/classy/templates/form/datetime-wrapper.html.twig
+++ b/core/themes/classy/templates/form/datetime-wrapper.html.twig
@@ -18,6 +18,7 @@
     'label',
     required ? 'js-form-required',
     required ? 'form-required',
+    title_display == 'invisible' ? 'visually-hidden'
   ]
 %}
 {% if title %}
diff --git a/core/themes/stable/templates/form/datetime-wrapper.html.twig b/core/themes/stable/templates/form/datetime-wrapper.html.twig
index 3c37ffd..cff8d8f 100644
--- a/core/themes/stable/templates/form/datetime-wrapper.html.twig
+++ b/core/themes/stable/templates/form/datetime-wrapper.html.twig
@@ -17,6 +17,7 @@
   set title_classes = [
     required ? 'js-form-required',
     required ? 'form-required',
+    title_display == 'invisible' ? 'visually-hidden'
   ]
 %}
 {% if title %}
