diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index 47e1f305e5e8838862b3a8bd4b93d7f93529a909..a77333712ae05e5acf7a75a26d40ec2f7480b577 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -45,6 +45,9 @@ protected function prepareEntity(EntityInterface $node) {
       // Remove the log message from the original node entity.
       $node->log = NULL;
     }
+    if (!empty($node->published)) {
+      $node->published_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));
 
@@ -258,6 +261,14 @@ public function form(array $form, array &$form_state, EntityInterface $node) {
       '#default_value' => $node->status,
     );
 
+    $form['options']['published_date'] = array(
+      '#type' => 'textfield',
+      '#title' => t('First 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->published_date) ? date_format(date_create($node->published_date), 'Y-m-d H:i:s O') : format_date($node->published, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->published_date) ? date_format(date_create($node->published_date), 'O') : format_date($node->published, 'custom', 'O'))),
+      '#default_value' => !empty($node->published_date) ? $node->published_date : '',
+    );
+
     $form['options']['promote'] = array(
       '#type' => 'checkbox',
       '#title' => t('Promoted to front page'),
@@ -330,6 +341,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.
+    $published_date = new DrupalDateTime($node->published_date);
+    if ($published_date->hasErrors()) {
+      form_set_error('published_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 d855384e95a821682ff64e63e5bf5a926ed66920..90152cac1c728b10f7b72084623e36e3cfebd7fb 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 ($node->status == NODE_PUBLISHED && 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 6cf9fac395570f996207fa8f171da26cc42c70ba..60a9b25bb74c232f851f466b95eec6222f9defd0 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
@@ -142,6 +142,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 f86bc522c1ad694d8b094d417010b7b26e26a82b..f6f488071cea0b100df45f14bea1af0e5d12fed4 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' => NODE_PUBLISHED,
     );
 
     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' => NODE_PUBLISHED,
       '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' => NODE_NOT_PUBLISHED,
+    );
+
+    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 = NODE_PUBLISHED;
+
+    $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 = NODE_NOT_PUBLISHED;
+
+    $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 = NODE_PUBLISHED;
+
+    $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 12a6945bc18e711de125719b54707076f81e3def..ac88175ead47719de2bd243c2b138d425a001fab 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)),
@@ -690,7 +697,6 @@ function node_update_8012() {
   update_module_enable(array('history'));
 }
 
-
 /**
  * Renames global revision permissions to use the word 'all'.
  */
@@ -707,6 +713,97 @@ function node_update_8013() {
 }
 
 /**
+ * Create a published column for nodes.
+ */
+function node_update_8014(&$sandbox) {
+  // This is a multi-pass update. On the first call we need to update the
+  // database schema and initialize some variables.
+  if (!isset($sandbox['total'])) {
+    // Add a published column to the node table.
+    $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);
+
+    // Create a table to store temporary data for this update.
+    db_create_table('node_update_8014', array(
+      'description' => 'Stores temporary data for node_update_8014.',
+      'fields' => array(
+        'nid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+        'timestamp' => array(
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'created' => array(
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+      ),
+      'primary key' => array('nid'),
+    ));
+
+    // Get the nid and timestamp of the first revision with a status of 1 for
+    // each node that has one, along with the created date for that node. Store
+    // this data in the temporary table.
+    $query = db_select('node_revision', 'r');
+    $query->leftJoin('node', 'n', 'r.nid = n.nid');
+    $query
+      ->fields('r', array('nid', 'timestamp'))
+      ->fields('n', array('created'))
+      ->condition('r.status', 1)
+      ->groupBy('r.nid');
+    db_insert('node_update_8014')
+      ->from($query)
+      ->execute();
+
+    // Initialize update variables.
+    $sandbox['last'] = 0;
+    $sandbox['count'] = 0;
+    $sandbox['total'] = db_query('SELECT COUNT(*) FROM {node_update_8014}')->fetchField();
+  }
+  else {
+    // We do each pass in batches of 1000.
+    $batch = 1000;
+
+    $result = db_query_range('SELECT * FROM {node_update_8014}', $sandbox['last'], $batch)->fetchAllAssoc('nid');
+    foreach ($result as $nid => $record) {
+      $sandbox['count'] += 1;
+      // See if the node has multiple revisions. If it does, use the revision
+      // timestamp. If not then use the node created date.
+      $revisions = db_query('SELECT COUNT(*) FROM {node_revision} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
+      $published = ($revisions > 1) ? $record->timestamp : $record->created;
+      db_update('node')
+        ->condition('nid', $nid)
+        ->fields(array('published' => $published))
+        ->execute();
+    }
+    $sandbox['last'] += $batch;
+  }
+
+  if ($sandbox['count'] < $sandbox['total']) {
+    $sandbox['#finished'] = FALSE;
+  }
+  else {
+    db_drop_table('node_update_8014');
+    $sandbox['#finished'] = TRUE;
+  }
+}
+
+/**
  * @} 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 eb56876e7b882097e7ece60c803b050955ef8285..05dc397c1182ffb00b3e8721ddccb90238018d23 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -991,6 +991,14 @@ function node_submit(Node $node) {
     $node->created = REQUEST_TIME;
   }
 
+  if (!empty($node->published_date)) {
+    $node_published = new DrupalDateTime($node->published_date);
+    $node->published = $node_published->getTimestamp();
+  }
+  else {
+    $node->published = 0;
+  }
+
   $node->validated = TRUE;
 
   return $node;
diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc
index c882450217afc71b2fcee950cec63ae3d8735c1c..a0e5e3c5eef1f0fd30f0723eecb640a95b3f03e8 100644
--- a/core/modules/node/node.tokens.inc
+++ b/core/modules/node/node.tokens.inc
@@ -74,6 +74,11 @@ function node_token_info() {
     'description' => t("The date the node was most recently updated."),
     'type' => 'date',
   );
+  $node['published'] = array(
+    'name' => t("Date published"),
+    'description' => t("The date the node was first published."),
+    'type' => 'date',
+  );
   $node['author'] = array(
     'name' => t("Author"),
     'description' => t("The author of the node."),
@@ -194,6 +199,10 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr
         case 'changed':
           $replacements[$original] = format_date($node->changed, 'medium', '', NULL, $langcode);
           break;
+
+        case 'published':
+          $replacements[$original] = format_date($node->published, 'medium', '', NULL, $langcode);
+          break;
       }
     }
 
@@ -209,6 +218,10 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr
     if ($changed_tokens = token_find_with_prefix($tokens, 'changed')) {
       $replacements += token_generate('date', $changed_tokens, array('date' => $node->changed), $options);
     }
+
+    if ($published_tokens = token_find_with_prefix($tokens, 'published')) {
+      $replacements += token_generate('date', $published_tokens, array('date' => $node->published), $options);
+    }
   }
 
   return $replacements;
diff --git a/core/modules/node/node.views.inc b/core/modules/node/node.views.inc
index 8f8d75f193e3a002e2985839856c58e3fb1e8a12..e099971bb821f86333606703daca4d5e2a250e7f 100644
--- a/core/modules/node/node.views.inc
+++ b/core/modules/node/node.views.inc
@@ -118,6 +118,22 @@ function node_views_data() {
     ),
   );
 
+  // published field
+  $data['node']['published'] = array(
+    'title' => t('Published date'),
+    'help' => t('The date the content was first published.'),
+    'field' => array(
+      'id' => 'date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'id' => 'date'
+    ),
+    'filter' => array(
+      'id' => 'date',
+    ),
+  );
+
   // Content type
   $data['node']['type'] = array(
     'title' => t('Type'), // The item it appears as on the UI,
@@ -383,6 +399,60 @@ function node_views_data() {
     ),
   );
 
+  $data['node']['published_fulldate'] = array(
+    'title' => t('Published date'),
+    'help' => t('Date in the form of CCYYMMDD.'),
+    'argument' => array(
+      'field' => 'published',
+      'id' => 'node_published_fulldate',
+    ),
+  );
+
+  $data['node']['published_year_month'] = array(
+    'title' => t('Published year + month'),
+    'help' => t('Date in the form of YYYYMM.'),
+    'argument' => array(
+      'field' => 'published',
+      'id' => 'node_published_year_month',
+    ),
+  );
+
+  $data['node']['published_year'] = array(
+    'title' => t('Published year'),
+    'help' => t('Date in the form of YYYY.'),
+    'argument' => array(
+      'field' => 'published',
+      'id' => 'node_published_year',
+    ),
+  );
+
+  $data['node']['published_month'] = array(
+    'title' => t('Published month'),
+    'help' => t('Date in the form of MM (01 - 12).'),
+    'argument' => array(
+      'field' => 'published',
+      'id' => 'node_published_month',
+    ),
+  );
+
+  $data['node']['published_day'] = array(
+    'title' => t('Published day'),
+    'help' => t('Date in the form of DD (01 - 31).'),
+    'argument' => array(
+      'field' => 'published',
+      'id' => 'node_published_day',
+    ),
+  );
+
+  $data['node']['published_week'] = array(
+    'title' => t('Published week'),
+    'help' => t('Date in the form of WW (01 - 53).'),
+    'argument' => array(
+      'field' => 'published',
+      'id' => 'node_published_week',
+    ),
+  );
+
   // uid field
   $data['node']['uid'] = array(
     'title' => t('Author uid'),
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 36b59c9122d41bfeeedd7898b695cec47a09b257..0c2fd99278aafeb73c15d49da1c75f782f12c436 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -134,6 +134,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 0000000000000000000000000000000000000000..2391c1f9899fd9117f49fca147311c5473daa4db
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/NodePublishUpgradePathTest.php
@@ -0,0 +1,58 @@
+<?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 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 that node.published column was added and filled with expected data.
+   */
+  public function testNodePublishedTimestampUpgrade() {
+    $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
+    $nodes = node_load_multiple(NULL);
+    foreach ($nodes as $node) {
+      $revisions = db_query('SELECT COUNT(*) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField();
+      $timestamp = '';
+      if ($revisions > 1) {
+        $timestamp = db_query('SELECT timestamp FROM {node_revision} WHERE nid = :nid AND status = 1 GROUP BY nid', array(':nid' => $node->nid))->fetchField();
+      }
+      else {
+        $timestamp = db_query('SELECT created FROM {node} WHERE nid = :nid AND status = 1', array(':nid' => $node->nid))->fetchField();
+      }
+      if (!empty($timestamp)) {
+        $this->assertEqual($timestamp, $node->published, 'Published content has published timestamp.');
+      }
+      else {
+        $this->assertEqual(0, $node->published, 'Unpublished content does not have published timestamp.');
+      }
+    }
+  }
+}
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/argument/Date.php b/core/modules/views/lib/Drupal/views/Plugin/views/argument/Date.php
index ce3f52f72389ac20612fafad0416c650a83cd8b7..6186afd1ebe0d6892b0153835359cd6bd5e3d3a8 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/argument/Date.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/argument/Date.php
@@ -43,7 +43,9 @@ function default_argument_form(&$form, &$form_state) {
     parent::default_argument_form($form, $form_state);
     $form['default_argument_type']['#options'] += array('date' => t('Current date'));
     $form['default_argument_type']['#options'] += array('node_created' => t("Current node's creation time"));
-    $form['default_argument_type']['#options'] += array('node_changed' => t("Current node's update time"));  }
+    $form['default_argument_type']['#options'] += array('node_changed' => t("Current node's update time"));
+    $form['default_argument_type']['#options'] += array('node_published' => t("Current node's publish time"));
+  }
 
   /**
    * Set the empty argument value to the current date,
@@ -53,7 +55,7 @@ function get_default_argument($raw = FALSE) {
     if (!$raw && $this->options['default_argument_type'] == 'date') {
       return date($this->definition['format'], REQUEST_TIME);
     }
-    elseif (!$raw && in_array($this->options['default_argument_type'], array('node_created', 'node_changed'))) {
+    elseif (!$raw && in_array($this->options['default_argument_type'], array('node_created', 'node_changed', 'node_published'))) {
       foreach (range(1, 3) as $i) {
         $node = menu_get_object('node', $i);
         if (!empty($node)) {
@@ -74,6 +76,9 @@ function get_default_argument($raw = FALSE) {
       elseif ($this->options['default_argument_type'] == 'node_changed') {
         return date($this->definition['format'], $node->changed);
       }
+      elseif ($this->options['default_argument_type'] == 'node_published') {
+        return date($this->definition['format'], $node->published);
+      }
     }
 
     return parent::get_default_argument($raw);
