diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index e92930ab62..00ad0641ea 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Field\ChangedFieldItemList;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -288,6 +289,7 @@ protected function doSave($id, EntityInterface $entity) {
     }
 
     $this->populateAffectedRevisionTranslations($entity);
+    $this->updateChangedTimestamp($entity);
     $this->doSaveFieldItems($entity);
 
     return $return;
@@ -597,6 +599,42 @@ protected function populateAffectedRevisionTranslations(ContentEntityInterface $
   }
 
   /**
+   * Updates the entity changed timestamp.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   An entity object being saved.
+   */
+  protected function updateChangedTimestamp(ContentEntityInterface $entity) {
+    // We have to ensure that we detect changes after the entity presave hooks
+    // have been executed as they still might alter field values, therefore we
+    // execute explicitly the preSave method of any changed field afterwards
+    // once more. In the most of the cases the entity will have only a single
+    // changed field, therefore it makes more sense to iterate through the
+    // fields, find the changed fields and then iterate over each entity
+    // translation and call the field preSave method on the changed field for
+    // that translation. In case the changed field is not translatable then the
+    // field preSave method will be executed only once on the default entity
+    // translation.
+    // For consistency with ::invokeFieldMethod() we have to ensure that the
+    // field preSave method is invoked as first on the current entity
+    // translation and then on all other entity translations.
+    $langcodes = array_keys($entity->getTranslationLanguages());
+    $langcodes = array_diff($langcodes, [$entity->language()->getId()]);
+    foreach ($entity->getFields(FALSE) as $field_name => $items) {
+      if ($items instanceof ChangedFieldItemList) {
+        // First invoke the field preSave method on the current entity
+        // translation and then on all other translations.
+        $items->preSave();
+        if ($items->getFieldDefinition()->isTranslatable()) {
+          foreach ($langcodes as $langcode) {
+            $entity->getTranslation($langcode)->get($field_name)->preSave();
+          }
+        }
+      }
+    }
+  }
+
+  /**
    * Ensures integer entity IDs are valid.
    *
    * The identifier sanitization provided by this method has been introduced
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index bcf55f48b2..6416ca9324 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -479,6 +479,9 @@ function entity_test_entity_presave(EntityInterface $entity) {
   if ($entity->getEntityType()->id() == 'entity_view_display') {
     $entity->setThirdPartySetting('entity_test', 'foo', 'bar');
   }
+  if (!empty($entity->changeTitleInPresaveHook) && ($label_field_name = $entity->getEntityType()->getKey('label'))) {
+    $entity->set($label_field_name, $entity->changeTitleInPresaveHook);
+  }
 }
 
 /**
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php
index 66e1abb2c5..d988e83cd0 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php
@@ -546,6 +546,36 @@ public function testRevisionChanged() {
   }
 
   /**
+   * Tests updated changed timestamp on changing field value in a presave hook.
+   */
+  public function testChangingInPresaveHook() {
+    $user = $this->createUser();
+    // Create a test entity with an old changed timestamp in order to easy test
+    // that it has been updated.
+    $entity_original_label = 'original entity label';
+    $entity = EntityTestMulRevChanged::create([
+      'name' => $entity_original_label,
+      'user_id' => $user->id(),
+      'language' => 'en',
+      'changed' => REQUEST_TIME - 1,
+    ]);
+    $entity->save();
+
+    // Ensure that the label method is returning the expected label.
+    $this->assertEquals($entity_original_label, $entity->label());
+
+    $changed_timestamp = $entity->getChangedTime();
+    $entity_label_presave_hook = 'original entity label changed';
+    $entity->changeTitleInPresaveHook = $entity_label_presave_hook;
+    $entity->save();
+    // Ensure that the label has been updated in the presave hook.
+    // @see entity_test_entity_presave()
+    $this->assertEquals($entity_label_presave_hook, $entity->label());
+    // Ensure that the changed timestamp has been updated.
+    $this->assertTrue($entity->getChangedTime() > $changed_timestamp);
+  }
+
+  /**
    * Retrieves the revision translation affected flag value.
    *
    * @param \Drupal\entity_test\Entity\EntityTestMulRevChanged $entity
