diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index 0b892eb..29d5af3 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -44,6 +44,9 @@ protected function prepareEntity(EntityInterface $node) {
       // Remove the log message from the original node entity.
       $node->log = NULL;
     }
+    if (!empty($node->published)) {
+      $node->pub_date = format_date($node->published, 'custom', 'Y-m-d H:i:s O');
+    }
     // Always use the default revision setting.
     $node->setNewRevision(in_array('revision', $node_options));
 
@@ -222,6 +225,14 @@ public function form(array $form, array &$form_state, EntityInterface $node) {
       '#default_value' => $node->status,
     );
 
+    $form['options']['pub_date'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Published on'),
+      '#maxlength' => 25,
+      '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->pub_date) ? date_format(date_create($node->pub_date), 'Y-m-d H:i:s O') : format_date($node->published, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->pub_date) ? date_format(date_create($node->pub_date), 'O') : format_date($node->published, 'custom', 'O'))),
+      '#default_value' => !empty($node->pub_date) ? $node->pub_date : '',
+    );
+
     $form['options']['promote'] = array(
       '#type' => 'checkbox',
       '#title' => t('Promoted to front page'),
@@ -294,6 +305,12 @@ public function validate(array $form, array &$form_state) {
       form_set_error('date', t('You have to specify a valid date.'));
     }
 
+    // Validate the "published on" field.
+    $pub_date = new DrupalDateTime($node->pub_date);
+    if ($pub_date->hasErrors()) {
+      form_set_error('pub_date', t('You have to specify a valid publication date.'));
+    }
+
     // Invoke hook_validate() for node type specific validation and
     // hook_node_validate() for miscellaneous validation needed by modules.
     // Can't use node_invoke() or module_invoke_all(), because $form_state must
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php
index d855384..d5b6a55 100644
--- a/core/modules/node/lib/Drupal/node/NodeStorageController.php
+++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php
@@ -95,6 +95,11 @@ protected function invokeHook($hook, EntityInterface $node) {
   protected function preSave(EntityInterface $node) {
     // Before saving the node, set changed and revision times.
     $node->changed = REQUEST_TIME;
+
+    // If this is being published for the first time, set the published time.
+    if (!empty($node->status) && empty($node->published)) {
+      $node->published = REQUEST_TIME;
+    }
   }
 
   /**
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index 47de776..96afc65 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -141,6 +141,13 @@ class Node extends Entity implements ContentEntityInterface {
   public $changed;
 
   /**
+   * The node publication timestamp.
+   *
+   * @var integer
+   */
+  public $published;
+
+  /**
    * The node comment status indicator.
    *
    * COMMENT_NODE_HIDDEN => no comments
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeSaveTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeSaveTest.php
index f86bc52..156b70d 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeSaveTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeSaveTest.php
@@ -72,7 +72,7 @@ function testImport() {
   }
 
   /**
-   * Verifies accuracy of the "created" and "changed" timestamp functionality.
+   * Verifies the "created", "changed" and "published" timestamp functionality.
    */
   function testTimestamps() {
     // Use the default timestamps.
@@ -80,20 +80,24 @@ function testTimestamps() {
       'uid' => $this->web_user->uid,
       'type' => 'article',
       'title' => $this->randomName(8),
+      'status' => 1,
     );
 
     entity_create('node', $edit)->save();
     $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertEqual($node->created, REQUEST_TIME, 'Creating a node sets default "created" timestamp.');
     $this->assertEqual($node->changed, REQUEST_TIME, 'Creating a node sets default "changed" timestamp.');
+    $this->assertEqual($node->published, REQUEST_TIME, 'Creating a published node sets default "published" timestamp.');
 
     // Store the timestamps.
     $created = $node->created;
     $changed = $node->changed;
+    $published = $node->published;
 
     $node->save();
     $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
     $this->assertEqual($node->created, $created, 'Updating a node preserves "created" timestamp.');
+    $this->assertEqual($node->published, $published, 'Updating a node preserves "published" timestamp.');
 
     // Programmatically set the timestamps using hook_node_presave.
     $node->title = 'testing_node_presave';
@@ -102,29 +106,75 @@ function testTimestamps() {
     $node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE);
     $this->assertEqual($node->created, 280299600, 'Saving a node uses "created" timestamp set in presave hook.');
     $this->assertEqual($node->changed, 979534800, 'Saving a node uses "changed" timestamp set in presave hook.');
+    $this->assertEqual($node->published, 946684800, 'Saving a node uses "published" timestamp set in presave hook.');
 
     // Programmatically set the timestamps on the node.
     $edit = array(
       'uid' => $this->web_user->uid,
       'type' => 'article',
       'title' => $this->randomName(8),
+      'status' => 1,
       'created' => 280299600, // Sun, 19 Nov 1978 05:00:00 GMT
       'changed' => 979534800, // Drupal 1.0 release.
+      'published' => 946684800, // Sat, 1 Jan 2000 00:00:00 GMT
     );
 
     entity_create('node', $edit)->save();
     $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertEqual($node->created, 280299600, 'Creating a node uses user-set "created" timestamp.');
     $this->assertNotEqual($node->changed, 979534800, 'Creating a node does not use user-set "changed" timestamp.');
+    $this->assertEqual($node->published, 946684800, 'Creating a node uses user-set "published" timestamp.');
 
     // Update the timestamps.
     $node->created = 979534800;
-    $node->changed = 280299600;
+    $node->changed = 946684800;
+    $node->published = 280299600;
 
     $node->save();
     $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
     $this->assertEqual($node->created, 979534800, 'Updating a node uses user-set "created" timestamp.');
-    $this->assertNotEqual($node->changed, 280299600, 'Updating a node does not use user-set "changed" timestamp.');
+    $this->assertNotEqual($node->changed, 946684800, 'Updating a node does not use user-set "changed" timestamp.');
+    $this->assertEqual($node->published, 280299600, 'Updating a node uses user-set "published" timestamp.');
+
+    // Save an unpublished node.
+    $edit = array(
+      'uid' => $this->web_user->uid,
+      'type' => 'article',
+      'title' => $this->randomName(8),
+      'status' => 0,
+    );
+
+    entity_create('node', $edit)->save();
+    $node = $this->drupalGetNodeByTitle($edit['title']);
+    $this->assertFalse($node->published, 'Creating an unpublished node does not set "published" timestamp.');
+
+    $node->save();
+    $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
+    $this->assertFalse($node->published, 'Updating an unpublished node does not set "published" timestamp.');
+
+    // Publish the node.
+    $node->status = 1;
+
+    $node->save();
+    $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
+    $this->assertEqual($node->published, REQUEST_TIME, 'Publishing a node sets default "published" timestamp.');
+
+    // Store "published" timestamp.
+    $published = $node->published;
+
+    // Unpublish the node.
+    $node->status = 0;
+
+    $node->save();
+    $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
+    $this->assertEqual($node->published, $published, 'Unpublishing a node preserves "published" timestamp.');
+
+    // Publish the node again.
+    $node->status = 1;
+
+    $node->save();
+    $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
+    $this->assertEqual($node->published, $published, 'Re-publishing a node preserves "published" timestamp.');
   }
 
   /**
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index 697d3a7..7a91751 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -79,6 +79,12 @@ function node_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
+      'published' => array(
+        'description' => 'The Unix timestamp when the node was first published.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
       'comment' => array(
         'description' => 'Whether comments are allowed on this node: 0 = no, 1 = closed (read only), 2 = open (read/write).',
         'type' => 'int',
@@ -114,6 +120,7 @@ function node_schema() {
     'indexes' => array(
       'node_changed'        => array('changed'),
       'node_created'        => array('created'),
+      'node_published'      => array('published'),
       'node_frontpage'      => array('promote', 'status', 'sticky', 'created'),
       'node_status_type'    => array('status', 'type', 'nid'),
       'node_title_type'     => array('title', array('type', 4)),
@@ -717,7 +724,6 @@ function node_update_8012() {
   update_module_enable(array('history'));
 }
 
-
 /**
  * Renames global revision permissions to use the word 'all'.
  */
@@ -734,6 +740,29 @@ function node_update_8013() {
 }
 
 /**
+ * Create a published column for nodes.
+ */
+function node_update_8014() {
+  $spec = array(
+    'description' => 'The Unix timestamp when the node was first published.',
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0,
+  );
+  $keys = array(
+    'indexes' => array(
+      'node_published' => array('published'),
+    ),
+  );
+  db_add_field('node', 'published', $spec, $keys);
+  // Set the published timestamp to changed for already published nodes.
+  db_update('node')
+    ->condition('status', 1, '=')
+    ->expression('published', 'changed')
+    ->execute();
+}
+
+/**
  * @} End of "addtogroup updates-7.x-to-8.x"
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index ea2b4ce..6e8b6b9 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -981,6 +981,14 @@ function node_submit(Node $node) {
     $node->created = REQUEST_TIME;
   }
 
+  if (!empty($node->pub_date)) {
+    $node_published = new DrupalDateTime($node->pub_date);
+    $node->published = $node_published->getTimestamp();
+  }
+  elseif (!empty($node->status)) {
+    $node->published = REQUEST_TIME;
+  }
+
   $node->validated = TRUE;
 
   return $node;
diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module
index 5e0a7ac..31fefdf 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -133,6 +133,8 @@ function node_test_node_presave(Node $node) {
     $node->created = 280299600;
     // Drupal 1.0 release.
     $node->changed = 979534800;
+    // Sat, 1 Jan 2000 00:00:00 GMT
+    $node->published = 946684800;
   }
   // Determine changes.
   if (!empty($node->original) && $node->original->title == 'test_changes') {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/NodePublishUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/NodePublishUpgradePathTest.php
new file mode 100644
index 0000000..a9dfd9f
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/NodePublishUpgradePathTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Upgrade\NodePublishUpgradePathTest.
+ */
+
+namespace Drupal\system\Tests\Upgrade;
+
+/**
+ * Performs major version release upgrade tests on a populated database.
+ *
+ * Loads an installation of Drupal 7.x and runs the upgrade process on it.
+ *
+ * The install contains the minimal profile modules (along with generated
+ * content) so that an update from of a site under this profile may be tested.
+ */
+class NodePublishUpgradePathTest extends UpgradePathTestBase {
+  public static function getInfo() {
+    return array(
+      'name'  => 'Node Publishing Upgrade Path Test',
+      'description'  => 'Upgrade tests for adding published timestamp.',
+      'group' => 'Upgrade path',
+    );
+  }
+
+  public function setUp() {
+    // Path to the database dump files.
+    $this->databaseDumpFiles = array(
+      drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.filled.standard_all.database.php.gz',
+    );
+    parent::setUp();
+  }
+
+  /**
+   * Tests a successful point release update.
+   */
+  public function testNodePublishedTimestampUpgrade() {
+    $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
+    $nodes = node_load_multiple(NULL);
+    foreach ($nodes as $node) {
+      if ($node->status == 1) {
+        $this->assertEqual($node->changed, $node->published, 'Published content has published timestamp.');
+      }
+      else {
+        $this->assertEqual(0, $node->published, 'Unpublished content does not have published timestamp.');
+      }
+    }
+  }
+}
