diff --git a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
index f5ee395..65fa188 100644
--- a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
@@ -22,11 +22,19 @@
 interface EntityChangedInterface {
 
   /**
-   * Returns the timestamp of the last entity change.
+   * Returns the timestamp of the last entity change for current translation.
    *
    * @return int
    *   The timestamp of the last entity save operation.
    */
   public function getChangedTime();
 
+  /**
+   * Returns the timestamp of the last entity change across all translations.
+   *
+   * @return int
+   *   The timestamp of the last entity save operation across all
+   *   translations.
+   */
+  public function getChangedTimeAcrossTranslations();
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityChangedTrait.php b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php
new file mode 100644
index 0000000..5b34d1a
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityChangedTrait.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides a trait for accessing changed time.
+ */
+trait EntityChangedTrait {
+
+  /**
+   * Returns the timestamp of the last entity change across all translations.
+   *
+   * @return int
+   *   The timestamp of the last entity save operation across all
+   *   translations.
+   */
+  public function getChangedTimeAcrossTranslations() {
+    $changed = $this->getUntranslated()->getChangedTime();
+    foreach ($this->getTranslationLanguages(FALSE) as $language) {
+      $translation_changed = $this->getTranslation($language->getId())->getChangedTime();
+      $changed = max($translation_changed, $changed);
+    }
+    return $changed;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
index fe81a63..8fd44da 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
@@ -23,8 +23,9 @@ public function validate($entity, Constraint $constraint) {
       /** @var \Drupal\Core\Entity\EntityInterface $entity */
       if (!$entity->isNew()) {
         $saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
-
-        if ($saved_entity && $saved_entity->getChangedTime() > $entity->getChangedTime()) {
+        // A change to any other translation must add a violation to the current
+        // translation because there might be untranslatable shared fields.
+        if ($saved_entity && $saved_entity->getChangedTimeAcrossTranslations() > $entity->getChangedTime()) {
           $this->context->addViolation($constraint->message);
         }
       }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
index 81625d4..ceb9088 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
@@ -30,7 +30,48 @@ class ChangedItem extends CreatedItem {
    */
   public function preSave() {
     parent::preSave();
-    $this->value = REQUEST_TIME;
+
+    $entity = $this->getEntity();
+    if ($entity->isNew()){
+      // Allow the changed timestamp to be set manually on entity creation,
+      // for example when migration is running.
+      if (!$this->value) {
+        $this->value = REQUEST_TIME;
+      }
+    }
+    else {
+      // Don't allow an illegal value.
+      if (!$this->value) {
+        $this->value = REQUEST_TIME;
+      }
+      else {
+        $field_name = $this->getFieldDefinition()->getName();
+        // We have to clone the original because getTranslation() will modify
+        // the state of original.
+        $original = clone $entity->original;
+        if ($this->getFieldDefinition()->isTranslatable()) {
+          $original = $original->getTranslation($entity->language()->getId());
+        }
+        // If the timestamp has not been set explicitly auto detect a
+        // modification of the current translation and set the timestamp if
+        // needed. An example of setting the timestamp explicitly is
+        // \Drupal\content_translation\ContentTranslationMetadataWrapperInterface::setChangedTime().
+        if ($this->value == $original->{$field_name}->value) {
+          foreach ($entity->getFieldDefinitions() as $other_field_definition) {
+            $other_field_name = $other_field_definition->getName();
+            if ($other_field_name != $field_name && $other_field_definition->isTranslatable()) {
+              $items = $entity->get($other_field_name)->filterEmptyItems();
+              $original_items = $original->get($other_field_name)
+                ->filterEmptyItems();
+              if (!$items->equals($original_items)) {
+                $this->value = REQUEST_TIME;
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
   }
 
 }
diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php
index 046640c..7092c11 100644
--- a/core/modules/block_content/src/Entity/BlockContent.php
+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -8,6 +8,7 @@
 namespace Drupal\block_content\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -66,6 +67,8 @@
  */
 class BlockContent extends ContentEntityBase implements BlockContentInterface {
 
+  use EntityChangedTrait;
+
   /**
    * The theme the block is being created in.
    *
diff --git a/core/modules/comment/src/CommentInterface.php b/core/modules/comment/src/CommentInterface.php
index c141706..0d558b5 100644
--- a/core/modules/comment/src/CommentInterface.php
+++ b/core/modules/comment/src/CommentInterface.php
@@ -197,14 +197,6 @@ public function getCreatedTime();
   public function setCreatedTime($created);
 
   /**
-   * Returns the timestamp of when the comment was updated.
-   *
-   * @return int
-   *   The timestamp of when the comment was updated.
-   */
-  public function getChangedTime();
-
-  /**
    * Checks if the comment is published.
    *
    * @return bool
diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php
index 588c831..c37641c 100644
--- a/core/modules/comment/src/CommentStatistics.php
+++ b/core/modules/comment/src/CommentStatistics.php
@@ -126,8 +126,10 @@ public function create(FieldableEntityInterface $entity, $fields) {
       }
       // Default to REQUEST_TIME when entity does not have a changed property.
       $last_comment_timestamp = REQUEST_TIME;
+      // @todo Make comment statistics language aware and add some tests. See
+      //   https://www.drupal.org/node/2318875
       if ($entity instanceof EntityChangedInterface) {
-        $last_comment_timestamp = $entity->getChangedTime();
+        $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations();
       }
       $query->values(array(
         'entity_id' => $entity->id(),
@@ -243,9 +245,9 @@ public function update(CommentInterface $comment) {
         ->fields(array(
           'cid' => 0,
           'comment_count' => 0,
-          // Use the created date of the entity if it's set, or default to
+          // Use the changed date of the entity if it's set, or default to
           // REQUEST_TIME.
-          'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME,
+          'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME,
           'last_comment_name' => '',
           'last_comment_uid' => $last_comment_uid,
         ))
diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php
index f83f54b..bc061a8 100644
--- a/core/modules/comment/src/Entity/Comment.php
+++ b/core/modules/comment/src/Entity/Comment.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Number;
 use Drupal\Core\Entity\ContentEntityBase;
 use Drupal\comment\CommentInterface;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -58,6 +59,8 @@
  */
 class Comment extends ContentEntityBase implements CommentInterface {
 
+  use EntityChangedTrait;
+
   /**
    * The thread for which a lock was acquired.
    */
diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php
index 9205de8..c01e32f 100644
--- a/core/modules/comment/src/Form/CommentAdminOverview.php
+++ b/core/modules/comment/src/Form/CommentAdminOverview.php
@@ -213,7 +213,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = '
             '#url' => $commented_entity->urlInfo(),
           ),
         ),
-        'changed' => $this->dateFormatter->format($comment->getChangedTime(), 'short'),
+        'changed' => $this->dateFormatter->format($comment->getChangedTimeAcrossTranslations(), 'short'),
       );
       $comment_uri_options = $comment->urlInfo()->getOptions();
       $links = array();
diff --git a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
index a5eaad8..c45c0f7 100644
--- a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
+++ b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
@@ -61,7 +61,7 @@ function testCommentTokenReplacement() {
     $tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id()));
     $tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options);
     $tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getCreatedTime(), 2, $language_interface->getId());
-    $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTime(), 2, $language_interface->getId());
+    $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTimeAcrossTranslations(), 2, $language_interface->getId());
     $tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
     $tests['[comment:parent:title]'] = SafeMarkup::checkPlain($parent_comment->getSubject());
     $tests['[comment:entity]'] = SafeMarkup::checkPlain($node->getTitle());
diff --git a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
index 4a0a65b..b0ced47 100644
--- a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
+++ b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
@@ -7,8 +7,6 @@
 
 namespace Drupal\content_translation;
 
-use Drupal\Core\Entity\EntityChangedInterface;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\user\UserInterface;
 
 /**
@@ -17,7 +15,7 @@
  * This acts as a wrapper for an entity translation object, encapsulating the
  * logic needed to retrieve translation metadata.
  */
-interface ContentTranslationMetadataWrapperInterface extends EntityChangedInterface {
+interface ContentTranslationMetadataWrapperInterface {
 
   /**
    * Retrieves the source language for this translation.
@@ -110,6 +108,14 @@ public function getCreatedTime();
   public function setCreatedTime($timestamp);
 
   /**
+   * Returns the timestamp of the last entity change from current translation.
+   *
+   * @return int
+   *   The timestamp of the last entity save operation.
+   */
+  public function getChangedTime();
+
+  /**
    * Sets the translation modification timestamp.
    *
    * @param int $timestamp
diff --git a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
index 0db24c8..2b4e3cc 100644
--- a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
@@ -26,7 +26,7 @@ class ContentTestTranslationUITest extends ContentTranslationUITest {
    */
   protected function setUp() {
     // Use the entity_test_mul as this has multilingual property support.
-    $this->entityTypeId = 'entity_test_mul';
+    $this->entityTypeId = 'entity_test_mul_changed';
     parent::setUp();
   }
 
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
index 5a7429c..3e740f4 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
@@ -8,6 +8,7 @@
 namespace Drupal\content_translation\Tests;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Field\Entity\BaseFieldOverride;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Url;
@@ -42,6 +43,7 @@ function testTranslationUI() {
     $this->doTestPublishedStatus();
     $this->doTestAuthoringInfo();
     $this->doTestTranslationEdit();
+    $this->doTestTranslationChanged();
     $this->doTestTranslationDeletion();
   }
 
@@ -366,6 +368,19 @@ protected function getValue(EntityInterface $translation, $property, $langcode)
   }
 
   /**
+   * Returns the name of the field that implements the changed timestamp.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being tested.
+   *
+   * @return string
+   *   The the field name.
+   */
+  protected function getChangedFieldName($entity) {
+    return $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
+  }
+
+  /**
    * Tests edit content translation.
    */
   protected function doTestTranslationEdit() {
@@ -384,4 +399,68 @@ protected function doTestTranslationEdit() {
     }
   }
 
+  /**
+   * Tests the basic translation workflow.
+   */
+  protected function doTestTranslationChanged() {
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+    $changed_field_name = $this->getChangedFieldName($entity);
+    $base_field_override = BaseFieldOverride::createFromBaseFieldDefinition($entity->getFieldDefinition($changed_field_name), $entity->bundle());
+
+    foreach (array(FALSE, TRUE) as $translatable_changed_field) {
+      $base_field_override->setTranslatable($translatable_changed_field);
+      $base_field_override->save();
+
+      for ($i = 1; $i <= 2; $i++) {
+        foreach ($entity->getTranslationLanguages() as $language) {
+          // Ensure different timestamps.
+          sleep(1);
+
+          $langcode = $language->getId();
+
+          $edit = array(
+            $this->fieldName . '[0][value]' => $this->randomString(),
+          );
+          $edit_path = $entity->urlInfo('edit-form', array('language' => $language));
+          $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode));
+
+          $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+          $this->assertEqual(
+            $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(),
+            format_string('Changed time for language %language is the latest change over all languages.', array('%language' => $language->getName()))
+          );
+
+          if ($i > 1) {
+            // Because the base of this test created the translations without a
+            // sleep command, the changed timestamps might not differ until the
+            // second run of that loop.
+            $timestamps = array();
+            foreach ($entity->getTranslationLanguages() as $language) {
+              $next_timestamp = $entity->getTranslation($language->getId())
+                ->getChangedTime();
+              if (!in_array($next_timestamp, $timestamps)) {
+                $timestamps[] = $next_timestamp;
+              }
+            }
+
+            if ($translatable_changed_field) {
+              $this->assertEqual(
+                count($timestamps), count($entity->getTranslationLanguages()),
+                'All timestamps from all languages are different.'
+              );
+            }
+            else {
+              $this->assertEqual(
+                count($timestamps), 1,
+                'All timestamps from all languages are identical.'
+              );
+            }
+          }
+        }
+      }
+    }
+
+    $base_field_override->delete();
+  }
+
 }
diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php
index ff38ab3..8a5bd42 100644
--- a/core/modules/file/src/Entity/File.php
+++ b/core/modules/file/src/Entity/File.php
@@ -8,10 +8,10 @@
 namespace Drupal\file\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\file\FileInterface;
 use Drupal\user\UserInterface;
 
@@ -38,6 +38,8 @@
  */
 class File extends ContentEntityBase implements FileInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/file/src/Tests/SaveTest.php b/core/modules/file/src/Tests/SaveTest.php
index cc12515..666b08d 100644
--- a/core/modules/file/src/Tests/SaveTest.php
+++ b/core/modules/file/src/Tests/SaveTest.php
@@ -22,8 +22,6 @@ function testFileSave() {
       'filename' => 'druplicon.txt',
       'uri' => 'public://druplicon.txt',
       'filemime' => 'text/plain',
-      'created' => 1,
-      'changed' => 1,
       'status' => FILE_STATUS_PERMANENT,
     ));
     file_put_contents($file->getFileUri(), 'hello world');
@@ -64,8 +62,6 @@ function testFileSave() {
       'filename' => 'DRUPLICON.txt',
       'uri' => 'public://DRUPLICON.txt',
       'filemime' => 'text/plain',
-      'created' => 1,
-      'changed' => 1,
       'status' => FILE_STATUS_PERMANENT,
     ));
     file_put_contents($uppercase_file->getFileUri(), 'hello world');
diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
index d6610a0..2b1c571 100644
--- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
@@ -8,6 +8,7 @@
 namespace Drupal\menu_link_content\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -52,6 +53,8 @@
  */
 class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterface {
 
+  use EntityChangedTrait;
+
   /**
    * A flag for whether this entity is wrapped in a plugin instance.
    *
diff --git a/core/modules/menu_link_content/src/Tests/LinksTest.php b/core/modules/menu_link_content/src/Tests/LinksTest.php
index 86a484b..81d48a4 100644
--- a/core/modules/menu_link_content/src/Tests/LinksTest.php
+++ b/core/modules/menu_link_content/src/Tests/LinksTest.php
@@ -138,7 +138,7 @@ public function testCreateLink() {
       'title' => 'Test Link',
     );
     $link->link->options = $options;
-    $link->changed->value = REQUEST_TIME - 5;
+    $link->changed->value = 0;
     $link->save();
     // Make sure the changed timestamp is updated.
     $this->assertEqual($link->getChangedTime(), REQUEST_TIME, 'Changing a menu link sets "changed" timestamp.');
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 4550a6d..fd26eb0 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -741,8 +741,8 @@ function node_page_title(NodeInterface $node) {
  * @param $nid
  *   The ID of a node.
  * @param string $langcode
- *   (optional) The language the node has been last modified in. Defaults to the
- *   node language.
+ *   (optional) The language to get the last changed time for. If omitted the
+ *   last changed time across all translations will be returned.
  *
  * @return string
  *   A unix timestamp indicating the last time the node was changed.
@@ -751,8 +751,9 @@ function node_page_title(NodeInterface $node) {
  *       for validation, which will be done by EntityChangedConstraintValidator.
  */
 function node_last_changed($nid, $langcode = NULL) {
-  $changed = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid)->getChangedTime();
-  return $changed ? $changed : FALSE;
+  $node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid);
+  $changed = $langcode ? $node->getTranslation($langcode)->getChangedTime() : $node->getChangedTimeAcrossTranslations();
+  return $changed ?: FALSE;
 }
 
 /**
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 7ae8f36..6dd1229 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -69,6 +70,8 @@
  */
 class Node extends ContentEntityBase implements NodeInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 44fccbf..d88393d 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -289,7 +289,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
   public function validate(array $form, FormStateInterface $form_state) {
     $node = parent::validate($form, $form_state);
 
-    if ($node->id() && (node_last_changed($node->id(), $this->getFormLangcode($form_state)) > $node->getChangedTime())) {
+    if ($node->id() && (node_last_changed($node->id()) > $node->getChangedTimeAcrossTranslations())) {
       $form_state->setErrorByName('changed', $this->t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
     }
 
diff --git a/core/modules/node/src/Tests/NodeLastChangedTest.php b/core/modules/node/src/Tests/NodeLastChangedTest.php
index 4444cc1..23fa4b4 100644
--- a/core/modules/node/src/Tests/NodeLastChangedTest.php
+++ b/core/modules/node/src/Tests/NodeLastChangedTest.php
@@ -38,7 +38,7 @@ function testNodeLastChanged() {
 
     // Test node last changed timestamp.
     $changed_timestamp = node_last_changed($node->id());
-    $this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.');
+    $this->assertEqual($changed_timestamp, $node->getChangedTimeAcrossTranslations(), 'Expected last changed timestamp returned.');
 
     $changed_timestamp = node_last_changed($node->id(), $node->language()->getId());
     $this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.');
diff --git a/core/modules/node/src/Tests/NodeSaveTest.php b/core/modules/node/src/Tests/NodeSaveTest.php
index c9dc9c0..c045e2a 100644
--- a/core/modules/node/src/Tests/NodeSaveTest.php
+++ b/core/modules/node/src/Tests/NodeSaveTest.php
@@ -120,8 +120,8 @@ function testTimestamps() {
 
     entity_create('node', $edit)->save();
     $node = $this->drupalGetNodeByTitle($edit['title']);
-    $this->assertEqual($node->getCreatedTime(), 280299600, 'Creating a node uses user-set "created" timestamp.');
-    $this->assertNotEqual($node->getChangedTime(), 979534800, 'Creating a node does not use user-set "changed" timestamp.');
+    $this->assertEqual($node->getCreatedTime(), 280299600, 'Creating a node programmatically uses programmatically set "created" timestamp.');
+    $this->assertEqual($node->getChangedTime(), 979534800, 'Creating a node programmatically uses programmatically set "changed" timestamp.');
 
     // Update the timestamps.
     $node->setCreatedTime(979534800);
@@ -130,7 +130,10 @@ function testTimestamps() {
     $node->save();
     $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
     $this->assertEqual($node->getCreatedTime(), 979534800, 'Updating a node uses user-set "created" timestamp.');
-    $this->assertNotEqual($node->getChangedTime(), 280299600, 'Updating a node does not use user-set "changed" timestamp.');
+    // Allowing setting changed timestamps is required, see
+    // Drupal\content_translation\ContentTranslationMetadataWrapper::setChangedTime($timestamp)
+    // for example.
+    $this->assertEqual($node->getChangedTime(), 280299600, 'Updating a node uses user-set "changed" timestamp.');
   }
 
   /**
diff --git a/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php
index 9f81cb5..81b67d3 100644
--- a/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php
+++ b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php
@@ -8,6 +8,7 @@
 namespace Drupal\shortcut\Tests;
 
 use Drupal\content_translation\Tests\ContentTranslationUITest;
+use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Language\Language;
 
 /**
@@ -102,4 +103,16 @@ protected function doTestTranslationEdit() {
     }
   }
 
+  /**
+   * Tests the basic translation workflow.
+   */
+  protected function doTestTranslationChanged() {
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+
+    $this->assertFalse(
+      $entity instanceof EntityChangedInterface,
+      format_string('%entity is not implementing EntityChangedInterface.' , array('%entity' => $this->entityTypeId))
+    );
+  }
+
 }
diff --git a/core/modules/system/src/Tests/Entity/ContentEntityChangedTest.php b/core/modules/system/src/Tests/Entity/ContentEntityChangedTest.php
new file mode 100644
index 0000000..0a38c2e
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/ContentEntityChangedTest.php
@@ -0,0 +1,364 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\Entity\ContentEntityChangedTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\user\UserInterface;
+
+/**
+ * Tests basic EntityChangedInterface functionality.
+ *
+ * @group Entity
+ */
+class ContentEntityChangedTest extends EntityUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['language', 'user', 'system', 'field', 'text', 'filter', 'entity_test'];
+
+  /**
+   * @inheritdoc
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Enable an additional language.
+    ConfigurableLanguage::createFromLangcode('de')->save();
+
+    $this->installEntitySchema('entity_test_mul_changed');
+    $this->installEntitySchema('entity_test_mulrev_changed');
+  }
+
+  /**
+   * Tests basic EntityChangedInterface functionality.
+   */
+  public function testChanged() {
+    $user1 = $this->createUser();
+    $user2 = $this->createUser();
+
+    // Create some test entities.
+    $entity = entity_create('entity_test_mul_changed', array(
+      'name' => $this->randomString(),
+      'user_id' => $user1->id(),
+      'language' => 'en',
+    ));
+    $entity->save();
+
+    $this->assertTrue(
+      $entity->getChangedTime() >= REQUEST_TIME,
+      'Changed time of original language is valid.'
+    );
+
+    // We can't assert equality here because the created time is set to the
+    // request time, while instances of ChangedTestItem use the current
+    // timestamp every time.
+    $this->assertTrue(
+      ($entity->getChangedTime() >= $entity->get('created')->value) &&
+      (($entity->getChangedTime() - $entity->get('created')->value) <= time() - REQUEST_TIME),
+      'Changed and created time of original language can be assumed to be identical.'
+    );
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of original language is the same as changed time across all translations.'
+    );
+
+    $changed_en = $entity->getChangedTime();
+
+    $german = $entity->addTranslation('de');
+
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertTrue(
+      $german->getChangedTime() > $entity->getChangedTime(),
+      'Changed time of German translation is newer then the original language.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of the German translation is the newest time across all translations.'
+    );
+
+    $changed_de = $german->getChangedTime();
+
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $changed_de,
+      'Changed time of the German translation did not change.'
+    );
+
+    $entity->setOwner($user2);
+
+    $entity->save();
+
+    $this->assertTrue(
+      $entity->getChangedTime() > $changed_en,
+      'Changed time of original language did change.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $changed_de,
+      'Changed time of the German translation did not change.'
+    );
+
+    $this->assertTrue(
+      $entity->getChangedTime() > $german->getChangedTime(),
+      'Changed time of original language is newer then the German translation.'
+    );
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of the original language is the newest time across all translations.'
+    );
+
+    $changed_en = $entity->getChangedTime();
+
+    // Save entity without any changes.
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $changed_de,
+      'Changed time of the German translation did not change.'
+    );
+
+    // Current state: changed time of original language (en) is newer than the
+    // changed time of the German translation. Now test that entity queries work
+    // as expected.
+    $storage = $this->entityManager->getStorage('entity_test_mul_changed');
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_en)->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time of original language.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_en, '=', 'en')->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time of original language.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_de, '=', 'en')->execute();
+
+    $this->assertFalse(
+      $ids,
+      'There\'s no original entity stored having the changed time of the German translation.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_en)->condition('default_langcode', '1')->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time of default language.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_de)->condition('default_langcode', '1')->execute();
+
+    $this->assertFalse(
+      $ids,
+      'There\'s no entity stored using the default language having the changed time of the German translation.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_de)->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time of German translation.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_de, '=', 'de')->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time of German translation.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_en, '=', 'de')->execute();
+
+    $this->assertFalse(
+      $ids,
+      'There\'s no German translation stored having the changed time of the original language.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_de, '>')->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time regardless of translation.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_en, '<')->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time regardless of translation.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', 0, '>')->execute();
+
+    $this->assertEqual(
+      reset($ids), $entity->id(),
+      'Entity query can access changed time regardless of translation.'
+    );
+
+    $query = $storage->getQuery();
+    $ids = $query->condition('changed', $changed_en, '>')->execute();
+
+    $this->assertFalse(
+      $ids,
+      'Entity query can access changed time regardless of translation.'
+    );
+  }
+
+  /**
+   * Tests revisionable EntityChangedInterface functionality.
+   */
+  public function testRevisionChanged() {
+    $user1 = $this->createUser();
+    $user2 = $this->createUser();
+
+    // Create some test entities.
+    $entity = entity_create('entity_test_mulrev_changed', array(
+      'name' => $this->randomString(),
+      'user_id' => $user1->id(),
+      'language' => 'en',
+    ));
+    $entity->save();
+
+    $this->assertTrue(
+      $entity->getChangedTime() >= REQUEST_TIME,
+      'Changed time of original language is valid.'
+    );
+
+    // We can't assert equality here because the created time is set to the
+    // request time while instances of ChangedTestItem use the current
+    // timestamp every time.
+    $this->assertTrue(
+      ($entity->getChangedTime() >= $entity->get('created')->value) &&
+      (($entity->getChangedTime() - $entity->get('created')->value) <= time() - REQUEST_TIME),
+      'Changed and created time of original language can be assumed to be identical.'
+    );
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of original language is the same as changed time across all translations.'
+    );
+
+    $changed_en = $entity->getChangedTime();
+
+    $entity->setNewRevision();
+    // Save entity without any changes but create new revision.
+    $entity->save();
+    // A new revision without any changes should not set a new changed time.
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $entity->setOwner($user2);
+    $entity->setNewRevision();
+    $entity->save();
+
+    $this->assertTrue(
+      $entity->getChangedTime() > $changed_en,
+      'Changed time of original language has been updated by new revision.'
+    );
+
+    $changed_en = $entity->getChangedTime();
+
+    $entity->addTranslation('de');
+
+    $german = $entity->getTranslation('de');
+
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertTrue(
+      $german->getChangedTime() > $entity->getChangedTime(),
+      'Changed time of German translation is newer then the original language.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of the German translation is the newest time across all translations.'
+    );
+
+    $changed_de = $german->getChangedTime();
+
+    $entity->setNewRevision();
+    // Save entity without any changes but create new revision.
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $changed_de,
+      'Changed time of the German translation did not change.'
+    );
+
+    $german->setOwner($user2);
+    $entity->setNewRevision();
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertTrue(
+      $german->getChangedTime() > $changed_de,
+      'Changed time of German translation did change.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of the German translation is the newest time across all translations.'
+    );
+  }
+
+}
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 4b26cfb..dd44c1f 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -58,6 +58,7 @@ function entity_test_entity_types($filter = NULL) {
   if ($filter != ENTITY_TEST_TYPES_REVISABLE) {
     $types[] = 'entity_test_mul';
     $types[] = 'entity_test_mul_langcode_key';
+    $types[] = 'entity_test_mul_changed';
   }
   if ($filter != ENTITY_TEST_TYPES_MULTILINGUAL) {
     $types[] = 'entity_test_rev';
@@ -66,6 +67,7 @@ function entity_test_entity_types($filter = NULL) {
     $types[] = 'entity_test_base_field_display';
   }
   $types[] = 'entity_test_mulrev';
+  $types[] = 'entity_test_mulrev_changed';
 
   return array_combine($types, $types);
 }
@@ -459,6 +461,20 @@ function entity_test_entity_test_mul_translation_delete(EntityInterface $transla
 /**
  * Implements hook_ENTITY_TYPE_translation_insert() for 'entity_test_mulrev'.
  */
+function entity_test_entity_test_mul_changed_translation_insert(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mul_changed_translation_insert', $translation->language()->getId());
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_translation_delete().
+ */
+function entity_test_entity_test_mul_changed_translation_delete(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mul_changed_translation_delete', $translation->language()->getId());
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_translation_insert().
+ */
 function entity_test_entity_test_mulrev_translation_insert(EntityInterface $translation) {
   _entity_test_record_hooks('entity_test_mulrev_translation_insert', $translation->language()->getId());
 }
@@ -471,6 +487,20 @@ function entity_test_entity_test_mulrev_translation_delete(EntityInterface $tran
 }
 
 /**
+ * Implements hook_ENTITY_TYPE_translation_insert() for 'entity_test_mulrev'.
+ */
+function entity_test_entity_test_mulrev_changed_translation_insert(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mulrev_changed_translation_insert', $translation->language()->getId());
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_translation_delete().
+ */
+function entity_test_entity_test_mulrev_changed_translation_delete(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mulrev_changed_translation_delete', $translation->language()->getId());
+}
+
+/**
  * Implements hook_ENTITY_TYPE_translation_insert() for 'entity_test_mul_langcode_key'.
  */
 function entity_test_entity_test_mul_langcode_key_translation_insert(EntityInterface $translation) {
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php
index 9f92e7a..61200f7 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\entity_test\Entity;
 
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -32,6 +33,8 @@
  */
 class EntityTestConstraints extends EntityTest implements EntityChangedInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
new file mode 100644
index 0000000..93af073
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\Entity\EntityTestMulDefaultValue.
+ */
+
+namespace Drupal\entity_test\Entity;
+
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+
+/**
+ * Defines the test entity class.
+ *
+ * @ContentEntityType(
+ *   id = "entity_test_mul_changed",
+ *   label = @Translation("Test entity - data table"),
+ *   handlers = {
+ *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
+ *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
+ *     "form" = {
+ *       "default" = "Drupal\entity_test\EntityTestForm",
+ *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
+ *     },
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData"
+ *   },
+ *   base_table = "entity_test_mul_changed",
+ *   data_table = "entity_test_mul_changed_property",
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "bundle" = "type",
+ *     "label" = "name",
+ *     "langcode" = "langcode"
+ *   },
+ *   links = {
+ *     "canonical" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
+ *     "edit-form" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
+ *     "delete-form" = "/entity_test/delete/entity_test_mul_changed/{entity_test_mul_changed}",
+ *   },
+ *   field_ui_base_route = "entity.entity_test_mul_changed.admin_form",
+ * )
+ */
+class EntityTestMulChanged extends EntityTestMul implements EntityChangedInterface {
+
+  use EntityChangedTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    $fields['changed'] = BaseFieldDefinition::create('changed_test')
+      ->setLabel(t('Changed'))
+      ->setDescription(t('The time that the entity was last edited.'))
+      ->setTranslatable(TRUE);
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    // Ensure a new timestamp.
+    sleep(1);
+    parent::save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChangedTime() {
+    return $this->get('changed')->value;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
new file mode 100644
index 0000000..67e772b
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\Entity\EntityTestMulRevChanged.
+ */
+
+namespace Drupal\entity_test\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\entity_test\Entity\EntityTestRev;
+
+/**
+ * Defines the test entity class.
+ *
+ * @ContentEntityType(
+ *   id = "entity_test_mulrev_changed",
+ *   label = @Translation("Test entity - revisions and data table"),
+ *   handlers = {
+ *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
+ *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
+ *     "form" = {
+ *       "default" = "Drupal\entity_test\EntityTestForm",
+ *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
+ *     },
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData"
+ *   },
+ *   base_table = "entity_test_mulrev_changed",
+ *   data_table = "entity_test_mulrev_changed_property",
+ *   revision_table = "entity_test_mulrev_changed_revision",
+ *   revision_data_table = "entity_test_mulrev_changed_property_revision",
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "bundle" = "type",
+ *     "revision" = "revision_id",
+ *     "label" = "name",
+ *     "langcode" = "langcode",
+ *   },
+ *   links = {
+ *     "canonical" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}",
+ *     "delete-form" = "/entity_test/delete/entity_test_mulrev_changed/{entity_test_mulrev_changed}",
+ *     "edit-form" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}",
+ *     "revision" = "/entity_test_mulrev_changed/{entity_test_mulrev_changed}/revision/{entity_test_mulrev_changed_revision}/view",
+ *   }
+ * )
+ */
+class EntityTestMulRevChanged extends EntityTestMulChanged {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    $fields['revision_id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Revision ID'))
+      ->setDescription(t('The version id of the test entity.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+    $fields['langcode']->setRevisionable(TRUE);
+    $fields['name']->setRevisionable(TRUE);
+    $fields['user_id']->setRevisionable(TRUE);
+    $fields['changed']->setRevisionable(TRUE);
+
+    return $fields;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedTestItem.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedTestItem.php
new file mode 100644
index 0000000..012134d
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedTestItem.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\Plugin\Field\FieldType\ChangedTestItem.
+ */
+
+namespace Drupal\entity_test\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\Plugin\Field\FieldType\ChangedItem;
+
+/**
+ * Defines the 'changed_test' entity field type.
+ *
+ * Wraps Drupal\Core\Field\Plugin\Field\FieldType\ChangedItem.
+ *
+ * @FieldType(
+ *   id = "changed_test",
+ *   label = @Translation("Last changed"),
+ *   description = @Translation("An entity field containing a UNIX timestamp of when the entity has been last updated."),
+ *   no_ui = TRUE,
+ *   list_class = "\Drupal\Core\Field\ChangedFieldItemList"
+ * )
+ *
+ * @see \Drupal\Core\Entity\EntityChangedInterface
+ */
+class ChangedTestItem extends ChangedItem {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave() {
+    parent::preSave();
+
+    if ($this->value == REQUEST_TIME) {
+      // During a test the request time is immutable. To allow tests of the
+      // algorithm of
+      // Drupal\Core\Field\Plugin\Field\FieldType\ChangedItem::preSave() we need
+      // to set a real time value here.
+      $this->value = time();
+    }
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 7ca9dd7..db02d8a 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -8,6 +8,7 @@
 namespace Drupal\taxonomy\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -56,6 +57,8 @@
  */
 class Term extends ContentEntityBase implements TermInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index fa4db6f..5982f8b 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -8,6 +8,7 @@
 namespace Drupal\user\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -62,6 +63,8 @@
  */
 class User extends ContentEntityBase implements UserInterface {
 
+  use EntityChangedTrait;
+
   /**
    * Stores a reference for a reusable anonymous user entity.
    *
