From 87cfde30b6d18fd3303e001c345d03b52c87c5cd Mon Sep 17 00:00:00 2001
From: Brian Weaver <weaver.299@osu.edu>
Date: Wed, 19 Feb 2020 13:46:55 -0500
Subject: [PATCH] Update scheduler to 1.1

---
 composer.json                                 |   2 +-
 composer.lock                                 |  22 +-
 vendor/composer/installed.json                |  24 +-
 web/modules/scheduler/.travis.yml             |  46 +-
 web/modules/scheduler/composer.json           |  67 +--
 .../config/install/scheduler.settings.yml     |   1 +
 ...views.view.scheduler_scheduled_content.yml | 455 ++++++++++++++----
 .../config/schema/scheduler.schema.yml        |   6 +
 web/modules/scheduler/drupalci.yml            |  12 +
 web/modules/scheduler/drush.services.yml      |   8 +
 web/modules/scheduler/scheduler.admin.inc     |  17 +-
 web/modules/scheduler/scheduler.api.php       | 120 ++++-
 web/modules/scheduler/scheduler.drush.inc     |   6 +-
 web/modules/scheduler/scheduler.info.yml      |  10 +-
 web/modules/scheduler/scheduler.install       |   6 +-
 web/modules/scheduler/scheduler.module        |  87 +++-
 web/modules/scheduler/scheduler.services.yml  |   9 +-
 ...uler_handler_field_scheduler_countdown.inc | 157 ------
 .../scheduler_rules_integration/composer.json |  10 +
 .../scheduler_rules_integration.info.yml      |   9 +-
 ...stingNodeIsScheduledForPublishingEvent.php |   2 +-
 ...ingNodeIsScheduledForUnpublishingEvent.php |   2 +-
 .../NewNodeIsScheduledForPublishingEvent.php  |   2 +-
 ...NewNodeIsScheduledForUnpublishingEvent.php |   2 +-
 .../SchedulerHasPublishedThisNodeEvent.php    |   2 +-
 .../SchedulerHasUnpublishedThisNodeEvent.php  |   2 +-
 .../src/Plugin/RulesAction/PublishNow.php     |   2 +-
 .../RulesAction/RemovePublishingDate.php      |   2 +-
 .../RulesAction/RemoveUnpublishingDate.php    |   2 +-
 .../Plugin/RulesAction/SetPublishingDate.php  |   2 +-
 .../RulesAction/SetUnpublishingDate.php       |   2 +-
 .../src/Plugin/RulesAction/UnpublishNow.php   |   2 +-
 .../src/Commands/SchedulerCommands.php        |  64 +++
 .../Controller/LightweightCronController.php  |  42 +-
 .../scheduler/src/Form/SchedulerAdminForm.php |   2 +-
 .../scheduler/src/Form/SchedulerCronForm.php  |  22 +-
 .../SchedulerPublishOnConstraintValidator.php |   2 +-
 ...chedulerUnpublishOnConstraintValidator.php |   2 +-
 web/modules/scheduler/src/SchedulerEvent.php  |   2 +-
 .../scheduler/src/SchedulerManager.php        | 294 +++++++----
 .../scheduler_access_test.info.yml            |   9 +-
 .../scheduler_api_test.info.yml               |   9 +-
 .../scheduler_api_test.install                |   2 +-
 .../scheduler_api_test.module                 | 101 +++-
 .../Functional/SchedulerAdminSettingsTest.php |   8 +-
 .../tests/src/Functional/SchedulerApiTest.php | 130 ++++-
 .../src/Functional/SchedulerBasicTest.php     |  14 +-
 .../Functional/SchedulerBrowserTestBase.php   |  40 +-
 .../Functional/SchedulerDefaultTimeTest.php   |  58 ++-
 .../Functional/SchedulerDeleteNodeTest.php    |  18 +-
 .../Functional/SchedulerDevelGenerateTest.php |  20 +-
 .../Functional/SchedulerFieldsDisplayTest.php |  98 +++-
 .../SchedulerLightweightCronTest.php          |   8 +-
 .../SchedulerMetaInformationTest.php          |   6 +-
 .../Functional/SchedulerMultilingualTest.php  |  24 +-
 .../Functional/SchedulerNodeAccessTest.php    |   2 +-
 .../SchedulerNonEnabledTypeTest.php           |  14 +-
 .../src/Functional/SchedulerPastDatesTest.php |  89 ++--
 .../Functional/SchedulerPermissionsTest.php   |  70 ++-
 .../src/Functional/SchedulerRequiredTest.php  |  10 +-
 .../Functional/SchedulerRevisioningTest.php   |  56 +--
 .../Functional/SchedulerRulesActionsTest.php  |  19 +-
 .../SchedulerRulesConditionsTest.php          |  17 +-
 .../Functional/SchedulerRulesEventsTest.php   |  25 +-
 .../Functional/SchedulerStatusReportTest.php  |  10 +-
 .../Functional/SchedulerTokenReplaceTest.php  |  14 +-
 .../Functional/SchedulerValidationTest.php    |  26 +-
 67 files changed, 1676 insertions(+), 749 deletions(-)
 create mode 100644 web/modules/scheduler/drupalci.yml
 create mode 100644 web/modules/scheduler/drush.services.yml
 delete mode 100644 web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc
 create mode 100644 web/modules/scheduler/scheduler_rules_integration/composer.json
 create mode 100644 web/modules/scheduler/src/Commands/SchedulerCommands.php

diff --git a/composer.json b/composer.json
index 0468fb882b..ed6c28cfd2 100644
--- a/composer.json
+++ b/composer.json
@@ -157,7 +157,7 @@
         "drupal/redirect": "^1.3",
         "drupal/redis": "1.0",
         "drupal/roleassign": "^1.0@alpha",
-        "drupal/scheduler": "1.0",
+        "drupal/scheduler": "^1.0",
         "drupal/search_api": "1.1",
         "drupal/search_api_db": "1.1",
         "drupal/simple_gmap": "^2.0",
diff --git a/composer.lock b/composer.lock
index 10bd4605f0..53bbfb9dda 100644
--- a/composer.lock
+++ b/composer.lock
@@ -6816,23 +6816,24 @@
         },
         {
             "name": "drupal/scheduler",
-            "version": "1.0.0",
+            "version": "1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/scheduler.git",
-                "reference": "8.x-1.0"
+                "reference": "8.x-1.1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.0.zip",
-                "reference": "8.x-1.0",
-                "shasum": "213025b64b417f459b73d260d7287ca1f916d587"
+                "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.1.zip",
+                "reference": "8.x-1.1",
+                "shasum": "52ff9c778f183d970ec2d57dfc1ba45282054e97"
             },
             "require": {
                 "drupal/core": "*"
             },
             "require-dev": {
-                "drupal/devel_generate": "*",
+                "drupal/coder": "^8.3.3",
+                "drupal/devel_generate": "^2.0",
                 "drupal/rules": "*"
             },
             "type": "drupal-module",
@@ -6841,12 +6842,17 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.0",
-                    "datestamp": "1510690385",
+                    "version": "8.x-1.1",
+                    "datestamp": "1566578555",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
                     }
+                },
+                "drush": {
+                    "services": {
+                        "drush.services.yml": "^9"
+                    }
                 }
             },
             "notification-url": "https://packages.drupal.org/8/downloads",
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index fdf4eedc43..88ade6e93d 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -7030,24 +7030,25 @@
     },
     {
         "name": "drupal/scheduler",
-        "version": "1.0.0",
-        "version_normalized": "1.0.0.0",
+        "version": "1.1.0",
+        "version_normalized": "1.1.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/scheduler.git",
-            "reference": "8.x-1.0"
+            "reference": "8.x-1.1"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.0.zip",
-            "reference": "8.x-1.0",
-            "shasum": "213025b64b417f459b73d260d7287ca1f916d587"
+            "url": "https://ftp.drupal.org/files/projects/scheduler-8.x-1.1.zip",
+            "reference": "8.x-1.1",
+            "shasum": "52ff9c778f183d970ec2d57dfc1ba45282054e97"
         },
         "require": {
             "drupal/core": "*"
         },
         "require-dev": {
-            "drupal/devel_generate": "*",
+            "drupal/coder": "^8.3.3",
+            "drupal/devel_generate": "^2.0",
             "drupal/rules": "*"
         },
         "type": "drupal-module",
@@ -7056,12 +7057,17 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.0",
-                "datestamp": "1510690385",
+                "version": "8.x-1.1",
+                "datestamp": "1566578555",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
                 }
+            },
+            "drush": {
+                "services": {
+                    "drush.services.yml": "^9"
+                }
             }
         },
         "installation-source": "dist",
diff --git a/web/modules/scheduler/.travis.yml b/web/modules/scheduler/.travis.yml
index 19c0dac9c9..8871a6f56c 100644
--- a/web/modules/scheduler/.travis.yml
+++ b/web/modules/scheduler/.travis.yml
@@ -3,22 +3,32 @@ language: php
 # sudo here.
 sudo: true
 
+notifications:
+  email:
+    recipients:
+      - jonathan1055@sandfordsolutions.com
+
 php:
-  - 5.5
   - 5.6
   - 7.0
 
+services:
+  - mysql
+
 env:
   global:
-    # Make the script re-usable for other modules.
     - MODULE=scheduler
   matrix:
-    - DRUPAL_CORE=8.3.x
-    - DRUPAL_CORE=8.4.x
-    - DRUPAL_CORE=8.5.x
+    - DRUPAL_CORE=8.6.x
+    - DRUPAL_CORE=8.7.x
+    - DRUPAL_CORE=8.8.x
 
 matrix:
   fast_finish: true
+  # Core 8.8 no longer supports PHP5, so don't run that combination.
+  exclude:
+    - php: 5.6
+      env: DRUPAL_CORE=8.8.x
 
 # Be sure to cache composer downloads.
 cache:
@@ -49,18 +59,22 @@ before_script:
   - DRUPAL_ROOT=$(pwd)
   - echo $DRUPAL_ROOT
 
-  # Make a directory for module and copy the built source into it.
+  # Make a directory for our module and copy the built source into it.
   - mkdir $DRUPAL_ROOT/modules/$MODULE
   - cp -R $TRAVIS_BUILD_DIR/* $DRUPAL_ROOT/modules/$MODULE/
 
   # Install dependencies.
   - travis_retry git clone --branch 8.x-3.x --depth 1 https://github.com/fago/rules.git modules/rules
-  - travis_retry git clone --branch 8.x-1.x --depth 1 http://git.drupal.org/project/devel.git modules/devel
-  - travis_retry git clone --branch 8.x-1.x --depth 1 http://git.drupal.org/project/typed_data.git modules/typed_data
+  - travis_retry git clone --branch 8.x-2.1 --depth 1 https://git.drupalcode.org/project/devel.git modules/devel
+  - travis_retry git clone --branch 8.x-1.x --depth 1 https://git.drupalcode.org/project/typed_data.git modules/typed_data
 
-  # Run composer install for Drupal 8.1 and up. We need an up-to-date composer
-  # when installing Drupal 8.1.
+  # Run composer self-update and install.
   - travis_retry composer self-update && travis_retry composer install
+  # If running Core 8.6 or 8.7 the following script will upgrade to phpunit 6
+  # which is required in PHP7. The script has now been deleted from Core 8.8.
+  - if [[ $DRUPAL_CORE == "8.6.x" || $DRUPAL_CORE == "8.7.x" ]]; then
+      travis_retry composer run-script drupal-phpunit-upgrade;
+    fi
 
   # Coder is already installed as part of composer install. We just need to set
   # the installed_paths to pick up the Drupal standards.
@@ -76,14 +90,14 @@ script:
   # Run the PHPUnit tests.
   - ./vendor/bin/phpunit -c ./core/phpunit.xml.dist ./modules/$MODULE/tests/
 
-  # Change directory to our module.
+  # Check for coding standards. First change directory to our module.
   - cd $DRUPAL_ROOT/modules/$MODULE
 
-  # Check for coding standards. First list all the sniffs to be used.
+  # List all the sniffs that were used.
   - $DRUPAL_ROOT/vendor/bin/phpcs -e
 
-  # Show the coding standards violations in detail.
-  - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1
+  # Show the violations in detail and do not fail for any errors or warnings.
+  - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_warnings_on_exit 1 --runtime-set ignore_errors_on_exit 1
 
-  # Run again to give a summary and total count.
-  - $DRUPAL_ROOT/vendor/bin/phpcs --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 --report=summary
+  # Run again to give a summary and total count, and fail for errors (not warnings).
+  - $DRUPAL_ROOT/vendor/bin/phpcs --report=summary --runtime-set ignore_warnings_on_exit 1
diff --git a/web/modules/scheduler/composer.json b/web/modules/scheduler/composer.json
index 0085b59527..6a1534ff36 100644
--- a/web/modules/scheduler/composer.json
+++ b/web/modules/scheduler/composer.json
@@ -1,33 +1,44 @@
 {
-  "name": "drupal/scheduler",
-  "description": "Automatically publish and unpublish content at specified dates and times.",
-  "type": "drupal-module",
-  "homepage": "https://drupal.org/project/scheduler",
-  "authors": [
-    {
-      "name": "Eric Schaefer (Eric Schaefer)",
-      "homepage": "https://www.drupal.org/u/eric-schaefer",
-      "role": "Maintainer"
+    "name": "drupal/scheduler",
+    "description": "Automatically publish and unpublish content at specified dates and times.",
+    "type": "drupal-module",
+    "license": "GPL-2.0+",
+    "homepage": "https://drupal.org/project/scheduler",
+    "require-dev": {
+        "drupal/devel_generate": "^2.0",
+        "drupal/coder": "^8.3.3"
     },
-    {
-      "name": "Jonathan Smith (jonathan1055)",
-      "homepage": "https://www.drupal.org/u/jonathan1055",
-      "role": "Maintainer"
+    "authors": [
+        {
+            "name": "Eric Schaefer (Eric Schaefer)",
+            "homepage": "https://www.drupal.org/u/eric-schaefer",
+            "role": "Maintainer"
+        },
+        {
+            "name": "Jonathan Smith (jonathan1055)",
+            "homepage": "https://www.drupal.org/u/jonathan1055",
+            "role": "Maintainer"
+        },
+        {
+            "name": "Pieter Frenssen (pfrenssen)",
+            "homepage": "https://www.drupal.org/u/pfrenssen",
+            "role": "Maintainer"
+        },
+        {
+            "name": "Rick Manelius (rickmanelius)",
+            "homepage": "https://www.drupal.org/u/rickmanelius",
+            "role": "Maintainer"
+        }
+    ],
+    "support": {
+        "issues": "https://www.drupal.org/project/issues/scheduler",
+        "source": "http://cgit.drupalcode.org/scheduler"
     },
-    {
-      "name": "Pieter Frenssen (pfrenssen)",
-      "homepage": "https://www.drupal.org/u/pfrenssen",
-      "role": "Maintainer"
-    },
-    {
-      "name": "Rick Manelius (rickmanelius)",
-      "homepage": "https://www.drupal.org/u/rickmanelius",
-      "role": "Maintainer"
+    "extra": {
+        "drush": {
+            "services": {
+                "drush.services.yml": "^9"
+            }
+        }
     }
-  ],
-  "support": {
-    "issues": "https://www.drupal.org/project/issues/scheduler",
-    "source": "http://cgit.drupalcode.org/scheduler"
-  },
-  "license": "GPL-2.0+"
 }
diff --git a/web/modules/scheduler/config/install/scheduler.settings.yml b/web/modules/scheduler/config/install/scheduler.settings.yml
index 4c82158c86..1af27d4cad 100644
--- a/web/modules/scheduler/config/install/scheduler.settings.yml
+++ b/web/modules/scheduler/config/install/scheduler.settings.yml
@@ -6,6 +6,7 @@ default_expand_fieldset: 'when_required'
 default_fields_display_mode: 'vertical_tab'
 default_publish_enable: false
 default_publish_past_date: 'error'
+default_publish_past_date_created: false
 default_publish_required: false
 default_publish_revision: false
 default_publish_touch: false
diff --git a/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml b/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml
index 2b1c39d21f..98549cb439 100644
--- a/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml
+++ b/web/modules/scheduler/config/install/views.view.scheduler_scheduled_content.yml
@@ -7,11 +7,11 @@ dependencies:
     - user
 id: scheduler_scheduled_content
 label: 'Scheduled content'
-module: node
+module: views
 description: 'Find and manage scheduled content.'
-tag: default
-base_table: node_field_data
-base_field: nid
+tag: ''
+base_table: node_field_revision
+base_field: vid
 core: 8.x
 display:
   default:
@@ -20,8 +20,15 @@ display:
         type: scheduler
       cache:
         type: tag
+        options: {  }
       query:
         type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
       exposed_form:
         type: basic
         options:
@@ -36,6 +43,9 @@ display:
         type: full
         options:
           items_per_page: 50
+          offset: 0
+          id: 0
+          total_pages: null
           tags:
             previous: '‹ previous'
             next: 'next ›'
@@ -110,8 +120,6 @@ display:
               empty_column: false
               responsive: ''
             operations:
-              sortable: false
-              default_sort_order: asc
               align: ''
               separator: ''
               empty_column: false
@@ -125,27 +133,80 @@ display:
           id: node_bulk_form
           table: node
           field: node_bulk_form
+          relationship: nid
+          group_type: group
+          admin_label: ''
           label: ''
           exclude: false
           alter:
             alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
           element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
           element_default_classes: true
           empty: ''
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
           plugin_id: node_bulk_form
           entity_type: node
         title:
           id: title
-          table: node_field_data
+          table: node_field_revision
           field: title
+          relationship: none
+          group_type: group
+          admin_label: ''
           label: Title
           exclude: false
           alter:
             alter_text: false
+            make_link: false
+            absolute: false
+            trim: false
+            word_boundary: false
+            ellipsis: false
+            strip_tags: false
+            html: false
+          element_type: ''
           element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
           element_default_classes: true
           empty: ''
           hide_empty: false
@@ -156,12 +217,23 @@ display:
           type: string
           settings:
             link_to_entity: true
+          click_sort_column: value
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
           plugin_id: field
         type:
           id: type
           table: node_field_data
           field: type
-          relationship: none
+          relationship: nid
           group_type: group
           admin_label: ''
           label: 'Content Type'
@@ -226,12 +298,45 @@ display:
           id: name
           table: users_field_data
           field: name
-          relationship: uid
+          relationship: revision_uid
+          group_type: group
+          admin_label: ''
           label: Author
           exclude: false
           alter:
             alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
           element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
           element_default_classes: true
           empty: ''
           hide_empty: false
@@ -239,33 +344,91 @@ display:
           hide_alter_empty: true
           plugin_id: field
           type: user_name
+          click_sort_column: value
+          settings:
+            link_to_entity: true
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
           entity_type: user
           entity_field: name
         status:
           id: status
-          table: node_field_data
+          table: node_field_revision
           field: status
+          relationship: none
+          group_type: group
+          admin_label: ''
           label: Status
           exclude: false
           alter:
             alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
           element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
           element_default_classes: true
           empty: ''
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          click_sort_column: value
           type: boolean
           settings:
             format: custom
             format_custom_true: Published
             format_custom_false: Unpublished
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
           plugin_id: field
           entity_type: node
           entity_field: status
         publish_on:
           id: publish_on
-          table: node_field_data
+          table: node_field_revision
           field: publish_on
           relationship: none
           group_type: group
@@ -311,15 +474,28 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
-          date_format: short
-          custom_date_format: ''
-          timezone: ''
+          click_sort_column: value
+          type: timestamp
+          settings:
+            date_format: short
+            custom_date_format: ''
+            timezone: ''
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
           entity_type: node
           entity_field: publish_on
-          plugin_id: date
+          plugin_id: field
         unpublish_on:
           id: unpublish_on
-          table: node_field_data
+          table: node_field_revision
           field: unpublish_on
           relationship: none
           group_type: group
@@ -365,15 +541,28 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
-          date_format: short
-          custom_date_format: ''
-          timezone: ''
+          click_sort_column: value
+          type: timestamp
+          settings:
+            date_format: short
+            custom_date_format: ''
+            timezone: ''
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
           entity_type: node
           entity_field: unpublish_on
-          plugin_id: date
+          plugin_id: field
         operations:
           id: operations
-          table: node
+          table: node_revision
           field: operations
           relationship: none
           group_type: group
@@ -420,59 +609,94 @@ display:
           empty_zero: false
           hide_alter_empty: true
           destination: true
+          entity_type: node
           plugin_id: entity_operations
       filters:
-        status:
-          id: status
-          table: node_field_data
-          field: status
+        latest_revision:
+          id: latest_revision
+          table: node_revision
+          field: latest_revision
           relationship: none
           group_type: group
           admin_label: ''
           operator: '='
-          value: '1'
+          value: ''
           group: 1
-          exposed: true
+          exposed: false
           expose:
             operator_id: ''
-            label: Status
+            label: ''
             description: ''
             use_operator: false
-            operator: status_op
-            identifier: status
+            operator: ''
+            identifier: ''
             required: false
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
-          is_grouped: true
+              anonymous: '0'
+              authenticated: '0'
+              administrator: '0'
+          is_grouped: false
           group_info:
-            label: 'Published status'
+            label: ''
             description: ''
-            identifier: status
+            identifier: ''
             optional: true
             widget: select
             multiple: false
             remember: false
             default_group: All
             default_group_multiple: {  }
-            group_items:
-              1:
-                title: Published
-                operator: '='
-                value: '1'
-              2:
-                title: Unpublished
-                operator: '='
-                value: '0'
-          plugin_id: boolean
+            group_items: {  }
           entity_type: node
-          entity_field: status
+          plugin_id: latest_revision
+        title:
+          id: title
+          table: node_field_revision
+          field: title
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: contains
+          value: ''
+          group: 1
+          exposed: true
+          expose:
+            operator_id: title_op
+            label: Title
+            description: ''
+            use_operator: false
+            operator: title_op
+            identifier: title
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              anonymous: '0'
+              authenticated: '0'
+              administrator: '0'
+            placeholder: ''
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: string
+          entity_type: node
+          entity_field: title
         type:
           id: type
           table: node_field_data
           field: type
-          relationship: none
+          relationship: nid
           group_type: group
           admin_label: ''
           operator: in
@@ -481,7 +705,7 @@ display:
           exposed: true
           expose:
             operator_id: type_op
-            label: Type
+            label: 'Content type'
             description: ''
             use_operator: false
             operator: type_op
@@ -490,8 +714,8 @@ display:
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
             reduce: false
           is_grouped: false
@@ -509,49 +733,57 @@ display:
           plugin_id: bundle
           entity_type: node
           entity_field: type
-        title:
-          id: title
-          table: node_field_data
-          field: title
+        status:
+          id: status
+          table: node_field_revision
+          field: status
           relationship: none
           group_type: group
           admin_label: ''
-          operator: contains
+          operator: '='
           value: ''
           group: 1
           exposed: true
           expose:
-            operator_id: title_op
-            label: Title
+            operator_id: ''
+            label: Status
             description: ''
             use_operator: false
-            operator: title_op
-            identifier: title
+            operator: status_op
+            identifier: status
             required: false
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
-          is_grouped: false
+          is_grouped: true
           group_info:
-            label: ''
+            label: 'Published status'
             description: ''
-            identifier: ''
+            identifier: status
             optional: true
             widget: select
             multiple: false
             remember: false
             default_group: All
             default_group_multiple: {  }
-            group_items: {  }
-          plugin_id: string
+            group_items:
+              1:
+                title: Published
+                operator: '='
+                value: '1'
+              2:
+                title: Unpublished
+                operator: '='
+                value: '0'
+          plugin_id: boolean
           entity_type: node
-          entity_field: title
+          entity_field: status
         langcode:
           id: langcode
-          table: node_field_data
+          table: node_field_revision
           field: langcode
           relationship: none
           group_type: group
@@ -571,8 +803,8 @@ display:
             remember: false
             multiple: false
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
             reduce: false
           is_grouped: false
@@ -592,7 +824,7 @@ display:
           entity_field: langcode
         publish_on:
           id: publish_on
-          table: node_field_data
+          table: node_field_revision
           field: publish_on
           relationship: none
           group_type: group
@@ -606,40 +838,40 @@ display:
           group: 2
           exposed: false
           expose:
-            operator_id: publish_on_op
-            label: 'Publish on'
+            operator_id: ''
+            label: ''
             description: ''
-            use_operator: true
-            operator: publish_on_op
-            identifier: publish_on
+            use_operator: false
+            operator: ''
+            identifier: ''
             required: false
             remember: false
             multiple: false
+            placeholder: ''
+            min_placeholder: ''
+            max_placeholder: ''
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
           is_grouped: false
           group_info:
-            label: 'Publish on'
-            description: null
-            identifier: publish_on
+            label: ''
+            description: ''
+            identifier: ''
             optional: true
             widget: select
             multiple: false
             remember: false
             default_group: All
             default_group_multiple: {  }
-            group_items:
-              1: {  }
-              2: {  }
-              3: {  }
+            group_items: {  }
           entity_type: node
           entity_field: publish_on
           plugin_id: date
         unpublish_on:
           id: unpublish_on
-          table: node_field_data
+          table: node_field_revision
           field: unpublish_on
           relationship: none
           group_type: group
@@ -653,18 +885,21 @@ display:
           group: 2
           exposed: false
           expose:
-            operator_id: unpublish_on_op
-            label: 'Unpublish on'
+            operator_id: ''
+            label: ''
             description: ''
             use_operator: false
-            operator: unpublish_on_op
-            identifier: unpublish_on
+            operator: ''
+            identifier: ''
             required: false
             remember: false
             multiple: false
+            placeholder: ''
+            min_placeholder: ''
+            max_placeholder: ''
             remember_roles:
-              authenticated: authenticated
               anonymous: '0'
+              authenticated: '0'
               administrator: '0'
           is_grouped: false
           group_info:
@@ -683,6 +918,8 @@ display:
           plugin_id: date
       sorts: {  }
       title: 'Scheduled Content'
+      header: {  }
+      footer: {  }
       empty:
         area_text_custom:
           id: area_text_custom
@@ -697,14 +934,28 @@ display:
           plugin_id: text_custom
       arguments: {  }
       relationships:
-        uid:
-          id: uid
-          table: node_field_data
-          field: uid
-          admin_label: author
-          required: true
+        nid:
+          id: nid
+          table: node_field_revision
+          field: nid
+          admin_label: 'node id'
+          required: false
+          relationship: none
+          group_type: group
+          entity_type: node
+          entity_field: nid
+          plugin_id: standard
+        revision_uid:
+          id: revision_uid
+          table: node_revision
+          field: revision_uid
+          relationship: none
+          group_type: group
+          admin_label: 'revision user id'
+          required: false
+          entity_type: node
+          entity_field: revision_uid
           plugin_id: standard
-      show_admin_links: false
       filter_groups:
         operator: AND
         groups:
@@ -723,7 +974,6 @@ display:
         - url.query_args
         - user
         - 'user.node_grants:view'
-      cacheable: false
       max-age: 0
       tags: {  }
   overview:
@@ -733,6 +983,7 @@ display:
         type: tab
         title: Scheduled
         description: ''
+        expanded: false
         parent: system.admin_content
         weight: -10
         context: '0'
@@ -745,6 +996,7 @@ display:
         weight: -10
       display_extenders: {  }
       display_description: 'Overview of all scheduled content, as a tab on main ''content admin'' page'
+      display_comment: 'Revision nid relationship is required because the content type is only stored at ''content'' level, not ''content revision'' level.'
     display_plugin: page
     display_title: 'Content Overview'
     id: overview
@@ -757,7 +1009,6 @@ display:
         - url.query_args
         - user
         - 'user.node_grants:view'
-      cacheable: false
       max-age: 0
       tags: {  }
   user_page:
@@ -788,7 +1039,7 @@ display:
       arguments:
         uid:
           id: uid
-          table: node_field_data
+          table: node_field_revision
           field: uid
           relationship: none
           group_type: group
@@ -813,11 +1064,16 @@ display:
             sort_order: asc
             number_of_records: 0
             format: default_summary
-          specify_validation: false
+          specify_validation: true
           validate:
-            type: none
+            type: 'entity:user'
             fail: 'not found'
-          validate_options: {  }
+          validate_options:
+            operation: view
+            multiple: 0
+            access: false
+            restrict_roles: false
+            roles: {  }
           break_phrase: false
           not: false
           entity_type: node
@@ -847,6 +1103,5 @@ display:
         - url.query_args
         - user
         - 'user.node_grants:view'
-      cacheable: false
       max-age: 0
       tags: {  }
diff --git a/web/modules/scheduler/config/schema/scheduler.schema.yml b/web/modules/scheduler/config/schema/scheduler.schema.yml
index 10ee7b07ce..765edb4560 100644
--- a/web/modules/scheduler/config/schema/scheduler.schema.yml
+++ b/web/modules/scheduler/config/schema/scheduler.schema.yml
@@ -28,6 +28,9 @@ scheduler.settings:
     default_publish_past_date:
       type: string
       label: 'Default value for nodetype setting publish_past_date'
+    default_publish_past_date_created:
+      type: boolean
+      label: 'Default value for nodetype setting publish_past_date_created'
     default_publish_required:
       type: boolean
       label: 'Default value for nodetype setting publish_required'
@@ -78,6 +81,9 @@ node.type.*.third_party.scheduler:
     publish_past_date:
       type: string
       label: 'Action to be taken for publication dates in the past'
+    publish_past_date_created:
+      type: boolean
+      label: 'Change content creation date for past dates to avoid "changed" being earlier than "created"'
     publish_required:
       type: boolean
       label: 'Require scheduled publishing'
diff --git a/web/modules/scheduler/drupalci.yml b/web/modules/scheduler/drupalci.yml
new file mode 100644
index 0000000000..8e9aa20ae9
--- /dev/null
+++ b/web/modules/scheduler/drupalci.yml
@@ -0,0 +1,12 @@
+build:
+  assessment:
+    validate_codebase:
+      phplint:
+      container_composer:
+      csslint:
+      eslint:
+      phpcs:
+    testing:
+      run_tests.standard:
+        types: 'PHPUnit-Functional'
+        suppress-deprecations: false
diff --git a/web/modules/scheduler/drush.services.yml b/web/modules/scheduler/drush.services.yml
new file mode 100644
index 0000000000..53a9056f4c
--- /dev/null
+++ b/web/modules/scheduler/drush.services.yml
@@ -0,0 +1,8 @@
+services:
+  scheduler.commands:
+    class: \Drupal\scheduler\Commands\SchedulerCommands
+    arguments:
+      - '@scheduler.manager'
+      - '@messenger'
+    tags:
+      - { name: drush.command }
diff --git a/web/modules/scheduler/scheduler.admin.inc b/web/modules/scheduler/scheduler.admin.inc
index 94fb146f90..26654cf45d 100644
--- a/web/modules/scheduler/scheduler.admin.inc
+++ b/web/modules/scheduler/scheduler.admin.inc
@@ -92,6 +92,21 @@ function _scheduler_form_node_type_form_alter(array &$form, FormStateInterface $
       'schedule' => t('Schedule the content for publication on the next cron run'),
     ],
   ];
+  $form['scheduler']['publish']['advanced']['scheduler_publish_past_date_created'] = [
+    '#type' => 'checkbox',
+    '#title' => t("Change content creation time to match the published time for dates before the content was created"),
+    '#description' => t("The created time will only be altered when the scheduled publishing time is earlier than the existing content creation time"),
+    '#default_value' => $type->getThirdPartySetting('scheduler', 'publish_past_date_created', $config->get('default_publish_past_date_created')),
+    // This option is not relevant if the full 'change creation time' option is
+    // selected, or when past dates are not allowed. Hence only show it when
+    // the main option is not checked and the past dates option is not 'error'.
+    '#states' => [
+      'visible' => [
+        ':input[name="scheduler_publish_touch"]' => ['checked' => FALSE],
+        ':input[name="scheduler_publish_past_date"]' => ['!value' => 'error'],
+      ],
+    ],
+  ];
 
   // Unpublishing options.
   $form['scheduler']['unpublish'] = [
@@ -144,7 +159,6 @@ function _scheduler_form_node_type_form_alter(array &$form, FormStateInterface $
       ],
     ],
   ];
-  // @todo Worthwhile to port this to D8 now form displays are configurable?
   $form['scheduler']['node_edit_layout']['scheduler_fields_display_mode'] = [
     '#type' => 'radios',
     '#title' => t('Display scheduling options as'),
@@ -176,6 +190,7 @@ function scheduler_form_node_type_form_builder($entity_type, NodeTypeInterface $
   $type->setThirdPartySetting('scheduler', 'fields_display_mode', $form_state->getValue('scheduler_fields_display_mode'));
   $type->setThirdPartySetting('scheduler', 'publish_enable', $form_state->getValue('scheduler_publish_enable'));
   $type->setThirdPartySetting('scheduler', 'publish_past_date', $form_state->getValue('scheduler_publish_past_date'));
+  $type->setThirdPartySetting('scheduler', 'publish_past_date_created', $form_state->getValue('scheduler_publish_past_date_created'));
   $type->setThirdPartySetting('scheduler', 'publish_required', $form_state->getValue('scheduler_publish_required'));
   $type->setThirdPartySetting('scheduler', 'publish_revision', $form_state->getValue('scheduler_publish_revision'));
   $type->setThirdPartySetting('scheduler', 'publish_touch', $form_state->getValue('scheduler_publish_touch'));
diff --git a/web/modules/scheduler/scheduler.api.php b/web/modules/scheduler/scheduler.api.php
index e55004c243..3304947102 100644
--- a/web/modules/scheduler/scheduler.api.php
+++ b/web/modules/scheduler/scheduler.api.php
@@ -78,7 +78,7 @@ function hook_scheduler_allow_publishing(NodeInterface $node) {
     // If publication is denied then inform the user why. This message will be
     // displayed during node edit and save.
     if (!$allowed) {
-      drupal_set_message(t('The content will only be published after approval by the CEO.'), 'status', FALSE);
+      \Drupal::messenger()->addMessage(t('The content will only be published after approval by the CEO.'), 'status', FALSE);
     }
   }
 
@@ -111,13 +111,129 @@ function hook_scheduler_allow_unpublishing(NodeInterface $node) {
     // If unpublication is denied then inform the user why. This message will be
     // displayed during node edit and save.
     if (!$allowed) {
-      drupal_set_message(t('The competition will only be unpublished after all prizes have been claimed by the winners.'));
+      \Drupal::messenger()->addMessage(t('The competition will only be unpublished after all prizes have been claimed by the winners.'));
     }
   }
 
   return $allowed;
 }
 
+/**
+ * Hook function to hide the Publish On field.
+ *
+ * This hook is called from scheduler_form_node_form_alter(). It gives modules
+ * the ability to hide the scheduler publish_on input field on the node edit
+ * form. Note that it does not give the ability to force the field to be
+ * displayed, as that could override a more significant setting. It can only be
+ * used to hide the field.
+ *
+ * This hook was introduced for scheduler_content_moderation_integration.
+ *
+ * @param array $form
+ *   An associative array containing the structure of the form, as used in
+ *   hook_form_alter().
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *   The current state of the form, as used in hook_form_alter().
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being editted.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
+ *
+ * @return bool
+ *   TRUE to hide the publish_on field.
+ *   FALSE or NULL to leave the setting unchanged.
+ */
+function hook_scheduler_hide_publish_on_field(array $form, FormStateInterface $form_state, NodeInterface $node) {
+  return FALSE;
+}
+
+/**
+ * Hook function to hide the Unpublish On field.
+ *
+ * This hook is called from scheduler_form_node_form_alter(). It gives modules
+ * the ability to hide the scheduler unpublish_on input field on the node edit
+ * form. Note that it does not give the ability to force the field to be
+ * displayed, as that could override a more significant setting. It can only be
+ * used to hide the field.
+ *
+ * This hook was introduced for scheduler_content_moderation_integration.
+ *
+ * @param array $form
+ *   An associative array containing the structure of the form, as used in
+ *   hook_form_alter().
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *   The current state of the form, as used in hook_form_alter().
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being editted.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
+ *
+ * @return bool
+ *   TRUE to hide the unpublish_on field.
+ *   FALSE or NULL to leave the setting unchanged.
+ */
+function hook_scheduler_hide_unpublish_on_field(array $form, FormStateInterface $form_state, NodeInterface $node) {
+  return FALSE;
+}
+
+/**
+ * Hook function to process the publish action for a node.
+ *
+ * This hook is called from schedulerManger::publish() and allows oher modules
+ * to process the publish action on a node during a cron run. The other module
+ * may require different functionality to be executed instead of the default
+ * publish process. If none of the invoked hook functions return a TRUE value
+ * then Scheduler will process the node using the default publish action, just
+ * as if no other hooks had been called.
+ *
+ * This hook was introduced for scheduler_content_moderation_integration.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being published.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
+ *
+ * @return int
+ *   1 if this function has published the node or performed other such action
+ *     meaning that Scheduler should NOT process the default publish action.
+ *   0 if nothing has been done and Scheduler should process the default publish
+ *     action just as if this hook function did not exist.
+ *   -1 if an error has occurred and Scheduler should abandon processing this
+ *     node with no further action and move on to the next one.
+ */
+function hook_scheduler_publish_action(NodeInterface $node) {
+  return 0;
+}
+
+/**
+ * Hook function to process the unpublish action for a node.
+ *
+ * This hook is called from schedulerManger::unpublish() and allows oher modules
+ * to process the unpublish action on a node during a cron run. The other module
+ * may require different functionality to be executed instead of the default
+ * unpublish process. If none of the invoked hook functions return a TRUE value
+ * then Scheduler will process the node using the default unpublish action, just
+ * as if no other hooks had been called.
+ *
+ * This hook was introduced for scheduler_content_moderation_integration.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ *   The $node object of the node being unpublished.
+ *
+ * @see https://www.drupal.org/project/scheduler/issues/2798689
+ *
+ * @return int
+ *   1 if this function has published the node or performed other such action
+ *     meaning that Scheduler should NOT process the default publish action.
+ *   0 if nothing has been done and Scheduler should process the default publish
+ *     action just as if this hook function did not exist.
+ *   -1 if an error has occurred and Scheduler should abandon processing this
+ *     node with no further action and move on to the next one.
+ */
+function hook_scheduler_unpublish_action(NodeInterface $node) {
+  return 0;
+}
+
 /**
  * @} End of "addtogroup hooks".
  */
diff --git a/web/modules/scheduler/scheduler.drush.inc b/web/modules/scheduler/scheduler.drush.inc
index cba3f47925..09ebbfe3d8 100644
--- a/web/modules/scheduler/scheduler.drush.inc
+++ b/web/modules/scheduler/scheduler.drush.inc
@@ -12,7 +12,7 @@ function scheduler_drush_command() {
   $items = [];
 
   $items['scheduler-cron'] = [
-    'description' => 'Lighweight cron to process scheduler tasks.',
+    'description' => 'Lightweight cron to process scheduler tasks.',
     'core' => ['8+'],
     'aliases' => ['sch-cron'],
     'category' => 'scheduler',
@@ -25,10 +25,10 @@ function scheduler_drush_command() {
 }
 
 /**
- * Run lighweight scheduler cron.
+ * Run lightweight scheduler cron.
  */
 function drush_scheduler_cron() {
   \Drupal::service('scheduler.manager')->runLightweightCron();
   $nomsg = drush_get_option('nomsg', NULL);
-  $nomsg ? NULL : drupal_set_message(t('Scheduler lightweight cron completed'));
+  $nomsg ? NULL : \Drupal::messenger()->addMessage(t('Scheduler lightweight cron completed'));
 }
diff --git a/web/modules/scheduler/scheduler.info.yml b/web/modules/scheduler/scheduler.info.yml
index f034a76842..72a5f50c0c 100644
--- a/web/modules/scheduler/scheduler.info.yml
+++ b/web/modules/scheduler/scheduler.info.yml
@@ -1,9 +1,10 @@
 name: Scheduler
 type: module
 description: 'Publish and unpublish content automatically on specified dates and times.'
-# core: 8.x
+core: 8.x
 configure: scheduler.admin_form
 dependencies:
+  - drupal:system (>= 8.5)
   - drupal:datetime
   - drupal:field
   - drupal:node
@@ -14,8 +15,7 @@ libraries:
   - scheduler/admin
   - vertical-tabs
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
diff --git a/web/modules/scheduler/scheduler.install b/web/modules/scheduler/scheduler.install
index 2e4982b896..08970676e3 100644
--- a/web/modules/scheduler/scheduler.install
+++ b/web/modules/scheduler/scheduler.install
@@ -17,7 +17,7 @@ function scheduler_requirements($phase) {
   if ($phase === 'runtime') {
     $user = \Drupal::currentUser();
 
-    $now = REQUEST_TIME;
+    $now = \Drupal::time()->getRequestTime();
     $system_date = \Drupal::config('system.date');
     $date_default_timezone = $system_date->get('timezone.default') ?: date_default_timezone_get();
     $date_formatter = \Drupal::service('date.formatter');
@@ -71,8 +71,6 @@ function scheduler_requirements($phase) {
  * Implements hook_install().
  */
 function scheduler_install() {
-  // Add our base fields to the schema.
-  \Drupal::service('entity.definition_update_manager')->applyUpdates();
   // Set cron access key value, as this is now required in SchedulerCronForm.
   $config = \Drupal::service('config.factory')->getEditable('scheduler.settings');
   $config->set('lightweight_cron_access_key', substr(md5(rand()), 0, 20))
@@ -83,8 +81,6 @@ function scheduler_install() {
  * Implements hook_uninstall().
  */
 function scheduler_uninstall() {
-  // Remove our base fields from the schema.
-  \Drupal::service('entity.definition_update_manager')->applyUpdates();
   // Delete the scheduled content view.
   \Drupal::configFactory()->getEditable('views.view.scheduler_scheduled_content')->delete();
 }
diff --git a/web/modules/scheduler/scheduler.module b/web/modules/scheduler/scheduler.module
index 9f86b4d2ff..a6d7979669 100644
--- a/web/modules/scheduler/scheduler.module
+++ b/web/modules/scheduler/scheduler.module
@@ -72,9 +72,37 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
   $publishing_enabled = $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'));
   $unpublishing_enabled = $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'));
 
-  // If neither publishing nor unpublishing are enabled for this node type then
-  // the only thing to do is remove the fields from the form, then exit.
-  if (!$publishing_enabled && !$unpublishing_enabled) {
+  // Determine if the scheduler fields have been set to hidden (disabled).
+  $display = $form_state->getFormObject()->getFormDisplay($form_state);
+  $publishing_displayed = !empty($display->getComponent('publish_on'));
+  $unpublishing_displayed = !empty($display->getComponent('unpublish_on'));
+
+  /* @var $node \Drupal\node\NodeInterface */
+  $node = $form_state->getFormObject()->getEntity();
+
+  // Invoke all implementations of hook_scheduler_hide_publish_on_field() to
+  // allow other modules to hide the field on the node edit form.
+  if ($publishing_enabled && $publishing_displayed) {
+    $hook = 'scheduler_hide_publish_on_field';
+    foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+      $function = $module . '_' . $hook;
+      $publishing_displayed = ($function($form, $form_state, $node) !== TRUE) && $publishing_displayed;
+    }
+  }
+  // Invoke all implementations of hook_scheduler_hide_unpublish_on_field() to
+  // allow other modules to hide the field on the node edit form.
+  if ($unpublishing_enabled && $unpublishing_displayed) {
+    $hook = 'scheduler_hide_unpublish_on_field';
+    foreach (\Drupal::moduleHandler()->getImplementations($hook) as $module) {
+      $function = $module . '_' . $hook;
+      $unpublishing_displayed = ($function($form, $form_state, $node) !== TRUE) && $unpublishing_displayed;
+    }
+  }
+
+  // If both publishing and unpublishing are either not enabled or are hidden
+  // for this node type then the only thing to do is remove the fields from the
+  // form, then exit.
+  if ((!$publishing_enabled || !$publishing_displayed) && (!$unpublishing_enabled || !$unpublishing_displayed)) {
     unset($form['publish_on']);
     unset($form['unpublish_on']);
     return;
@@ -82,9 +110,6 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
 
   $date_only_allowed = $config->get('allow_date_only');
 
-  /* @var $node \Drupal\node\NodeInterface */
-  $node = $form_state->getFormObject()->getEntity();
-
   // A publish_on date is required if the content type option is set and the
   // node is being created or it currently has a scheduled publishing date.
   $publishing_required = $type->getThirdPartySetting('scheduler', 'publish_required', $config->get('default_publish_required'))
@@ -145,7 +170,7 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
     $descriptions['blank'] = t('Leave the date blank for no scheduled publishing.');
   }
 
-  $form['publish_on']['#access'] = $publishing_enabled;
+  $form['publish_on']['#access'] = $publishing_enabled && $publishing_displayed;
   $form['publish_on']['widget'][0]['value']['#required'] = $publishing_required;
   $form['publish_on']['widget'][0]['value']['#description'] = Xss::filter(implode(' ', $descriptions));
 
@@ -156,16 +181,23 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
     unset($descriptions['blank']);
   }
 
-  $form['unpublish_on']['#access'] = $unpublishing_enabled;
+  $form['unpublish_on']['#access'] = $unpublishing_enabled && $unpublishing_displayed;
   $form['unpublish_on']['widget'][0]['value']['#required'] = $unpublishing_required;
   $form['unpublish_on']['widget'][0]['value']['#description'] = Xss::filter(implode(' ', $descriptions));
 
   if (!\Drupal::currentUser()->hasPermission('schedule publishing of nodes')) {
     // Do not show the scheduler fields for users who do not have permission.
+    // Setting #access to FALSE for 'scheduler_settings' is enough to hide the
+    // fields. Setting FALSE for the individual fields is necessary to keep any
+    // existing scheduled dates preserved and remain unchanged on saving.
     $form['scheduler_settings']['#access'] = FALSE;
+    $form['publish_on']['#access'] = FALSE;
+    $form['unpublish_on']['#access'] = FALSE;
 
     // @todo Find a more elegant solution for bypassing the validation of
     // scheduler fields when the user does not have permission.
+    // Note: This scenario is NOT yet covered by any tests, neither in
+    // SchedulerPermissionsTest.php nor SchedulerRequiredTest.php
     // @see https://www.drupal.org/node/2651448
     $form['publish_on']['widget'][0]['value']['#required'] = FALSE;
     $form['unpublish_on']['widget'][0]['value']['#required'] = FALSE;
@@ -173,14 +205,18 @@ function scheduler_form_node_form_alter(&$form, FormStateInterface $form_state)
 
   // Check which widget type is set for the scheduler fields, and give a warning
   // if the wrong one has been set and provide a hint and link to fix it.
-  $storage_form_display = $form_state->getStorage()['form_display'];
-  $content = $storage_form_display->get('content');
-  $pluginDefinitions = $storage_form_display->get('pluginManager')->getDefinitions();
+  $pluginDefinitions = $display->get('pluginManager')->getDefinitions();
+  if ($publishing_enabled && $publishing_displayed) {
+    $fields_to_check[] = 'publish_on';
+  }
+  if ($unpublishing_enabled && $unpublishing_displayed) {
+    $fields_to_check[] = 'unpublish_on';
+  }
   $correct_widget_id = 'datetime_timestamp_no_default';
-  foreach (['publish_on' => $publishing_enabled, 'unpublish_on' => $unpublishing_enabled] as $field => $enabled) {
-    $actual_widget_id = $content[$field]['type'];
-    if ($enabled && $actual_widget_id != $correct_widget_id) {
-      drupal_set_message(t('The widget for field %field is incorrectly set to %wrong. This should be changed to %correct by an admin user via Field UI <a href="@link">content type form display</a> :not_available', [
+  foreach ($fields_to_check as $field) {
+    $actual_widget_id = $display->getComponent($field)['type'];
+    if ($actual_widget_id != $correct_widget_id) {
+      \Drupal::messenger()->addMessage(t('The widget for field %field is incorrectly set to %wrong. This should be changed to %correct by an admin user via Field UI <a href="@link">content type form display</a> :not_available', [
         '%field' => (string) $form[$field]['widget']['#title'],
         '%correct' => (string) $pluginDefinitions[$correct_widget_id]['label'],
         '%wrong' => (string) $pluginDefinitions[$actual_widget_id]['label'],
@@ -298,6 +334,7 @@ function scheduler_node_view(array &$build, EntityInterface $node, EntityViewDis
 function scheduler_node_presave(EntityInterface $node) {
   $config = \Drupal::config('scheduler.settings');
   $entity = $node->type->entity;
+  $request_time = \Drupal::time()->getRequestTime();
 
   // If there is no entity object or the class is incorrect then stop here. This
   // should not really happen but it has been observed, so better to be safe.
@@ -327,14 +364,14 @@ function scheduler_node_presave(EntityInterface $node) {
       if (rand(1, 100) <= $publishing_percent) {
         // Randomly assign a publish_on value in the range starting with the
         // created date and up to the selected time range in the future.
-        $node->set('publish_on', rand($node->created->value + 1, REQUEST_TIME + $time_range));
+        $node->set('publish_on', rand($node->created->value + 1, $request_time + $time_range));
       }
     }
     if ($unpublishing_percent && in_array($node->getType(), $unpublishing_enabled_types)) {
       if (rand(1, 100) <= $unpublishing_percent) {
         // Randomly assign an unpublish_on value in the range from the later of
         // created date/publish_on date up to the time range in the future.
-        $node->set('unpublish_on', rand(max($node->created->value, $node->publish_on->value), REQUEST_TIME + $time_range));
+        $node->set('unpublish_on', rand(max($node->created->value, $node->publish_on->value), $request_time + $time_range));
       }
     }
   }
@@ -349,19 +386,23 @@ function scheduler_node_presave(EntityInterface $node) {
     // Publish the node immediately if the publication date is in the past.
     $publish_immediately = $entity->getThirdPartySetting('scheduler', 'publish_past_date', $config->get('default_publish_past_date')) == 'publish';
 
-    if ($publication_allowed && $publish_immediately && $node->publish_on->value <= REQUEST_TIME) {
+    if ($publication_allowed && $publish_immediately && $node->publish_on->value <= $request_time) {
       // Trigger the PRE_PUBLISH_INMEDIATELY event so that modules can react
       // before the node has been published.
       $event = new SchedulerEvent($node);
       \Drupal::service('event_dispatcher')->dispatch(SchedulerEvents::PRE_PUBLISH_IMMEDIATELY, $event);
       $node = $event->getNode();
 
+      // Set the 'changed' timestamp to match what would have been done had this
+      // content been published via cron.
+      $node->setChangedTime($node->publish_on->value);
       // If required, set the created date to match published date.
-      if ($entity->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch'))) {
+      if ($entity->getThirdPartySetting('scheduler', 'publish_touch', $config->get('default_publish_touch')) ||
+      ($node->getCreatedTime() > $node->publish_on->value && $entity->getThirdPartySetting('scheduler', 'publish_past_date_created', $config->get('default_publish_past_date_created')))) {
         $node->setCreatedTime($node->publish_on->value);
       }
       $node->publish_on->value = NULL;
-      $node->setPublished(TRUE);
+      $node->setPublished();
 
       // Trigger the PUBLISH_IMMEDIATELY event so that modules can react after
       // the node has been published.
@@ -371,14 +412,14 @@ function scheduler_node_presave(EntityInterface $node) {
     }
     else {
       // Ensure the node is unpublished as it will be published by cron later.
-      $node->setPublished(FALSE);
+      $node->setUnpublished();
 
       // Only inform the user that the node is scheduled if publication has not
       // been prevented by other modules. Those modules have to display a
       // message themselves explaining why publication is denied.
       if ($publication_allowed) {
         $date_formatter = \Drupal::service('date.formatter');
-        drupal_set_message(t('This post is unpublished and will be published @publish_time.', [
+        \Drupal::messenger()->addMessage(t('This post is unpublished and will be published @publish_time.', [
           '@publish_time' => $date_formatter->format($node->publish_on->value, 'long'),
         ]), 'status', FALSE);
       }
@@ -462,7 +503,7 @@ function scheduler_entity_extra_field_info() {
   // content type. This allows admins to adjust the weight of the group, and it
   // works for vertical tabs and separate fieldsets.
   $fields = [];
-  foreach (node_type_get_types() as $type) {
+  foreach (NodeType::loadMultiple() as $type) {
     $publishing_enabled = $type->getThirdPartySetting('scheduler', 'publish_enable', $config->get('default_publish_enable'));
     $unpublishing_enabled = $type->getThirdPartySetting('scheduler', 'unpublish_enable', $config->get('default_unpublish_enable'));
 
diff --git a/web/modules/scheduler/scheduler.services.yml b/web/modules/scheduler/scheduler.services.yml
index 00ab742e8e..512a67a0cc 100644
--- a/web/modules/scheduler/scheduler.services.yml
+++ b/web/modules/scheduler/scheduler.services.yml
@@ -1,7 +1,14 @@
 services:
   scheduler.manager:
     class: Drupal\scheduler\SchedulerManager
-    arguments: ['@date.formatter', '@logger.channel.scheduler', '@module_handler', '@entity.manager', '@config.factory' ]
+    arguments:
+      - '@date.formatter'
+      - '@logger.channel.scheduler'
+      - '@module_handler'
+      - '@entity_type.manager'
+      - '@config.factory'
+      - '@event_dispatcher'
+      - '@datetime.time'
   logger.channel.scheduler:
     class: Drupal\Core\Logger\LoggerChannel
     factory: logger.factory:get
diff --git a/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc b/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc
deleted file mode 100644
index 9794a588d9..0000000000
--- a/web/modules/scheduler/scheduler_handler_field_scheduler_countdown.inc
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-
-namespace Drupal\scheduler;
-
-use Drupal\Core\Form\FormStateInterface;
-
-/**
- * Display scheduled dates in 'countdown' format for use in Views.
- *
- * Field handler to display a countdown until a scheduled action.
- *
- * @TODO Is this file actually used and/or needed in Drupal 8?
- *
- * @see scheduler.views.inc
- * @see http://www.ericschaefer.org/blog/2011/01/09/custom-field-handlers-for-views-2-drupal
- */
-class SchedulerHandlerFieldSchedulerCountdown extends views_handler_field {
-  const SECOND_SCALE = 1;
-  const MINUTE_SCALE = 60;
-  const HOUR_SCALE = 3600;
-  const DAY_SCALE = 86400;
-  const WEEK_SCALE = 604800;
-
-  /**
-   * Add the timestamp_field into the SQL query.
-   *
-   * It is calculated as publish_on - REQUEST_TIME so the result is the number
-   * of seconds from now until publishing. If publish_on is in the past then
-   * NULL is returned.
-   */
-  public function query() {
-    $this->ensure_my_table();
-    $this->node_table = $this->query->ensure_table('node', $this->relationship);
-    $time_field = $this->definition['timestamp_field'];
-    $this->field_alias = $this->query->add_field(NULL, 'CASE WHEN (' . $time_field . ' > ' . REQUEST_TIME . ') THEN (' . $time_field . ' - ' . REQUEST_TIME . ') ELSE NULL END', $this->table_alias . '_' . $this->field);
-  }
-
-  /**
-   * Define our display options and provide defaults.
-   *
-   * @return array
-   *   An associative array containing the options.
-   */
-  public function optionDefinition() {
-    $options = parent::option_definition();
-    $options['countdown_display'] = ['default' => 'smart'];
-    $options['units_display'] = ['default' => 'long'];
-    return $options;
-  }
-
-  /**
-   * Defines the form for the user to select the display options.
-   */
-  public function optionsForm(array &$form, FormStateInterface $form_state) {
-    parent::options_form($form, $form_state);
-    $form['countdown_display'] = [
-      '#title' => t('Display countdown as'),
-      '#type' => 'radios',
-      '#options' => [
-        'smart' => t('Smart mode'),
-        'seconds' => t('Seconds'),
-        'minutes' => t('Minutes'),
-        'hours' => t('Hours'),
-        'days' => t('Days'),
-        'weeks' => t('Weeks'),
-      ],
-      '#default_value' => $this->options['countdown_display'],
-    ];
-    $form['units_display'] = [
-      '#title' => t('Display time units'),
-      '#type' => 'radios',
-      '#options' => [
-        'long' => t('Long (for example 3 days)'),
-        'short' => t('Short (for example 3d)'),
-        'none' => t('No units at all'),
-      ],
-      '#default_value' => $this->options['units_display'],
-    ];
-  }
-
-  /**
-   * Callback function to filter out unwanted values.
-   *
-   * Keep only the array scale values which are smaller than the countdown value
-   * being displayed.
-   */
-  public function scaleFilterCallback($array_value) {
-    return ($this->raw_value >= $array_value);
-  }
-
-  /**
-   * Renders the countdown value in the units required.
-   */
-  public function render($values) {
-    $countdown_display = $this->options['countdown_display'];
-    $this->raw_value = $values->{$this->field_alias};
-
-    $scales = [
-      'weeks' => self::WEEK_SCALE,
-      'days' => self::DAY_SCALE,
-      'hours' => self::HOUR_SCALE,
-      'minutes' => self::MINUTE_SCALE,
-      'seconds' => self::SECOND_SCALE,
-    ];
-    // If the field has been set to 'Smart', determine the right timescale.
-    if ($countdown_display == 'smart') {
-      $scales = array_filter($scales, [$this, 'scale_filter_callback']);
-      $scale = empty($scales) ? self::SECOND_SCALE : reset($scales);
-    }
-    // Otherwise use the fixed display requested.
-    else {
-      $scale = $scales[$countdown_display];
-    }
-
-    // Get the display value by dividing the original value by the scale.
-    $scaled_value = round($this->raw_value / $scale);
-
-    switch ($scale) {
-      case self::SECOND_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 second', '@count seconds', ['@count' => $scaled_value]);
-        $short = t('@counts', ['@count' => $scaled_value]);
-        break;
-
-      case self::MINUTE_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 minute', '@count minutes', ['@count' => $scaled_value]);
-        $short = t('@countm', ['@count' => $scaled_value]);
-        break;
-
-      case self::HOUR_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 hour', '@count hours', ['@count' => $scaled_value]);
-        $short = t('@counth', ['@count' => $scaled_value]);
-        break;
-
-      case self::DAY_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 day', '@count days', ['@count' => $scaled_value]);
-        $short = t('@countd', ['@count' => $scaled_value]);
-        break;
-
-      case self::WEEK_SCALE:
-        $long = \Drupal::translation()->formatPlural($scaled_value, '1 week', '@count weeks', ['@count' => $scaled_value]);
-        $short = t('@countw', ['@count' => $scaled_value]);
-        break;
-    }
-
-    switch ($this->options['units_display']) {
-      case 'long':
-        return $long;
-
-      case 'short':
-        return $short;
-
-      default:
-        return $scaled_value;
-    }
-  }
-
-}
diff --git a/web/modules/scheduler/scheduler_rules_integration/composer.json b/web/modules/scheduler/scheduler_rules_integration/composer.json
new file mode 100644
index 0000000000..c33f5aea34
--- /dev/null
+++ b/web/modules/scheduler/scheduler_rules_integration/composer.json
@@ -0,0 +1,10 @@
+{
+    "name": "drupal/scheduler_rules_integration",
+    "description": "Scheduler sub-module providing conditions, actions and events for use with the Rules module.",
+    "type": "drupal-module",
+    "license": "GPL-2.0+",
+    "require": {
+        "drupal/rules": "^3.x",
+        "drupal/scheduler": "^1.0"
+    }
+}
diff --git a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
index 973d9edba1..ef4c01c6e6 100644
--- a/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
+++ b/web/modules/scheduler/scheduler_rules_integration/scheduler_rules_integration.info.yml
@@ -1,13 +1,12 @@
 name: Scheduler Rules Integration
 type: module
 description: 'Scheduler sub-module providing conditions, actions and events for use with the Rules module.'
-# core: 8.x
+core: 8.x
 dependencies:
   - rules:rules
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
index d1c7a02fb7..5706b208ea 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForPublishingEvent.php
@@ -18,7 +18,7 @@ class ExistingNodeIsScheduledForPublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
index b51a412bdd..10a3163440 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/ExistingNodeIsScheduledForUnpublishingEvent.php
@@ -18,7 +18,7 @@ class ExistingNodeIsScheduledForUnpublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
index 4bafa013fe..8010e1df5c 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForPublishingEvent.php
@@ -18,7 +18,7 @@ class NewNodeIsScheduledForPublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
index d002da6c3f..2e638595b6 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/NewNodeIsScheduledForUnpublishingEvent.php
@@ -18,7 +18,7 @@ class NewNodeIsScheduledForUnpublishingEvent extends Event {
   /**
    * The node which is being scheduled and saved.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
index 266b7650b3..da4afa62df 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasPublishedThisNodeEvent.php
@@ -17,7 +17,7 @@ class SchedulerHasPublishedThisNodeEvent extends Event {
   /**
    * The node which has been processed.
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
index 2ba13099ea..08b6937117 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Event/SchedulerHasUnpublishedThisNodeEvent.php
@@ -17,7 +17,7 @@ class SchedulerHasUnpublishedThisNodeEvent extends Event {
   /**
    * The node which has been processed..
    *
-   * @var Drupal\node\NodeInterface
+   * @var \Drupal\node\NodeInterface
    */
   public $node;
 
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php
index 3727a96662..b15e962371 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/PublishNow.php
@@ -30,7 +30,7 @@ class PublishNow extends RulesActionBase {
    */
   public function doExecute() {
     $node = $this->getContextValue('node');
-    $node->setPublished(TRUE);
+    $node->setPublished();
   }
 
 }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php
index 7969a8e3d1..24951f0297 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemovePublishingDate.php
@@ -37,7 +37,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Remove publishing date" - Scheduled publishing is not enabled for %type content. To prevent this message add the condition "Scheduled publishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php
index 2d48c8cd43..25b4f9852c 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/RemoveUnpublishingDate.php
@@ -37,7 +37,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Remove unpublishing date" -  Scheduled unpublishing is not enabled for %type content. To prevent this message add the condition "Scheduled unpublishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php
index 6d3d676f1e..cdea76dd95 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetPublishingDate.php
@@ -46,7 +46,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Set publishing date" - Scheduled publishing is not enabled for %type content. To prevent this message add the condition "Scheduled publishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php
index 5bfc93f68f..aa352bb733 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/SetUnpublishingDate.php
@@ -46,7 +46,7 @@ public function doExecute() {
       $type_name = node_get_type_label($node);
       $arguments = [
         '%type' => $type_name,
-        'link' => \Drupal::l(t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
+        'link' => \Drupal::l($this->t('@type settings', ['@type' => $type_name]), new Url('entity.node_type.edit_form', ['node_type' => $node->getType()])),
       ];
       \Drupal::logger('scheduler')->warning('Scheduler rules action "Set unpublishing date" - Scheduled unpublishing is not enabled for %type content. To prevent this message add the condition "Scheduled unpublishing is enabled" to your Rule, or enable the Scheduler options via the %type content type settings.', $arguments);
     }
diff --git a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php
index 2be4278806..498e9d66eb 100644
--- a/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php
+++ b/web/modules/scheduler/scheduler_rules_integration/src/Plugin/RulesAction/UnpublishNow.php
@@ -30,7 +30,7 @@ class UnpublishNow extends RulesActionBase {
    */
   public function doExecute() {
     $node = $this->getContextValue('node');
-    $node->setPublished(FALSE);
+    $node->setUnpublished();
   }
 
 }
diff --git a/web/modules/scheduler/src/Commands/SchedulerCommands.php b/web/modules/scheduler/src/Commands/SchedulerCommands.php
new file mode 100644
index 0000000000..b453ba6f5f
--- /dev/null
+++ b/web/modules/scheduler/src/Commands/SchedulerCommands.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\scheduler\Commands;
+
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\scheduler\SchedulerManager;
+use Drush\Commands\DrushCommands;
+
+/**
+ * Drush 9 Scheduler commands for Drupal Core 8.4+.
+ */
+class SchedulerCommands extends DrushCommands {
+
+  /**
+   * The Scheduler manager service.
+   *
+   * @var \Drupal\scheduler\SchedulerManager
+   */
+  protected $schedulerManager;
+
+  /**
+   * The Messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
+  /**
+   * SchedulerCommands constructor.
+   *
+   * @param \Drupal\scheduler\SchedulerManager $schedulerManager
+   *   Scheduler manager service.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   Messenger service.
+   */
+  public function __construct(SchedulerManager $schedulerManager, MessengerInterface $messenger) {
+    parent::__construct();
+    $this->schedulerManager = $schedulerManager;
+    $this->messenger = $messenger;
+  }
+
+  /**
+   * Lightweight cron to process Scheduler module tasks.
+   *
+   * @param array $options
+   *   An associative array of options whose values come from cli, aliases,
+   *   config, etc.
+   *
+   * @option nomsg
+   *   to avoid the "cron completed" message being written to the terminal.
+   * @option nolog
+   *   to overide the site setting and not write 'started' and 'completed'
+   *   messages to the dblog.
+   *
+   * @command scheduler:cron
+   * @aliases sch-cron, scheduler-cron
+   */
+  public function cron(array $options = ['nomsg' => NULL, 'nolog' => NULL]) {
+    $this->manager->runLightweightCron($options);
+
+    $options['nomsg'] ? NULL : $this->messenger->addMessage(dt('Scheduler lightweight cron completed.'));
+  }
+
+}
diff --git a/web/modules/scheduler/src/Controller/LightweightCronController.php b/web/modules/scheduler/src/Controller/LightweightCronController.php
index 44d5c0b346..896497c173 100644
--- a/web/modules/scheduler/src/Controller/LightweightCronController.php
+++ b/web/modules/scheduler/src/Controller/LightweightCronController.php
@@ -4,6 +4,8 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\scheduler\SchedulerManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Response;
 
 /**
@@ -13,18 +15,42 @@
  */
 class LightweightCronController extends ControllerBase {
 
+  /**
+   * The scheduler manager.
+   *
+   * @var \Drupal\scheduler\SchedulerManager
+   */
+  protected $schedulerManager;
+
+  /**
+   * LightweightCronController constructor.
+   *
+   * @param \Drupal\scheduler\SchedulerManager $scheduler_manager
+   *   The scheduler manager.
+   */
+  public function __construct(SchedulerManager $scheduler_manager) {
+    $this->schedulerManager = $scheduler_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('scheduler.manager')
+    );
+  }
+
   /**
    * Index.
    *
-   * @return \Symfony\Component\HttpFoundation\RedirectResponse
-   *   RedirectResponse.
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The http response.
    */
   public function index() {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::service with dependency injection?
-    \Drupal::service('scheduler.manager')->runLightweightCron();
+    $this->schedulerManager->runLightweightCron();
 
-    return new Response('', 204);
+    return new Response('', Response::HTTP_NO_CONTENT);
   }
 
   /**
@@ -37,9 +63,7 @@ public function index() {
    *   The access result.
    */
   public function access($cron_key) {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::config with dependency injection?
-    $valid_cron_key = \Drupal::config('scheduler.settings')
+    $valid_cron_key = $this->config('scheduler.settings')
       ->get('lightweight_cron_access_key');
     return AccessResult::allowedIf($valid_cron_key == $cron_key);
   }
diff --git a/web/modules/scheduler/src/Form/SchedulerAdminForm.php b/web/modules/scheduler/src/Form/SchedulerAdminForm.php
index 5ae7061bbd..4d00198157 100644
--- a/web/modules/scheduler/src/Form/SchedulerAdminForm.php
+++ b/web/modules/scheduler/src/Form/SchedulerAdminForm.php
@@ -100,7 +100,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#description' => $this->t('This is the time that will be used if the user does not enter a value. Format: HH:MM:SS.'),
       '#states' => [
         'visible' => [
-          ':input[name="scheduler_allow_date_only"]' => ['checked' => TRUE],
+          ':input[name="allow_date_only"]' => ['checked' => TRUE],
         ],
       ],
     ];
diff --git a/web/modules/scheduler/src/Form/SchedulerCronForm.php b/web/modules/scheduler/src/Form/SchedulerCronForm.php
index f9abb55880..41eef3bd0f 100644
--- a/web/modules/scheduler/src/Form/SchedulerCronForm.php
+++ b/web/modules/scheduler/src/Form/SchedulerCronForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\scheduler\Form;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
@@ -24,19 +25,22 @@ class SchedulerCronForm extends ConfigFormBase {
   /**
    * The scheduler manager service.
    *
-   * @var Drupal\scheduler\SchedulerManager
+   * @var \Drupal\scheduler\SchedulerManager
    */
   protected $schedulerManager;
 
   /**
    * Creates a SchedulerCronForm instance.
    *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The factory for configuration objects.
    * @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler service.
-   * @var Drupal\scheduler\SchedulerManager $scheduler_manager
+   * @var \Drupal\scheduler\SchedulerManager $scheduler_manager
    *   The scheduler manager service.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, SchedulerManager $scheduler_manager) {
+  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, SchedulerManager $scheduler_manager) {
+    parent::__construct($config_factory);
     $this->moduleHandler = $module_handler;
     $this->schedulerManager = $scheduler_manager;
   }
@@ -45,7 +49,11 @@ public function __construct(ModuleHandlerInterface $module_handler, SchedulerMan
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('module_handler'), $container->get('scheduler.manager'));
+    return new static(
+      $container->get('config.factory'),
+      $container->get('module_handler'),
+      $container->get('scheduler.manager')
+    );
   }
 
   /**
@@ -147,7 +155,7 @@ public function generateRandomKey(array &$form, FormStateInterface $form_state)
    *   The current state of the form.
    */
   public function runLightweightCron(array &$form, FormStateInterface $form_state) {
-    $this->schedulerManager->runLightweightCron();
+    $this->schedulerManager->runLightweightCron(['admin_form' => TRUE]);
 
     if ($this->moduleHandler->moduleExists('dblog')) {
       $url = Url::fromRoute('dblog.overview')->toString();
@@ -158,9 +166,7 @@ public function runLightweightCron(array &$form, FormStateInterface $form_state)
       // overview does not exist. Show a simple status message.
       $message = $this->t('Lightweight cron run completed.');
     }
-    // @todo Replace drupal_set_message() with an injectable service in 8.1.x.
-    // @see https://www.drupal.org/node/2278383
-    drupal_set_message($message);
+    $this->messenger()->addMessage($message);
   }
 
 }
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
index 51ccff9c5a..b4963e1dd9 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerPublishOnConstraintValidator.php
@@ -18,7 +18,7 @@ public function validate($entity, Constraint $constraint) {
     $default_publish_past_date = \Drupal::config('scheduler.settings')->get('default_publish_past_date');
     $scheduler_publish_past_date = $entity->getEntity()->type->entity->getThirdPartySetting('scheduler', 'publish_past_date', $default_publish_past_date);
 
-    if ($publish_on && $scheduler_publish_past_date == 'error' && $publish_on < REQUEST_TIME) {
+    if ($publish_on && $scheduler_publish_past_date == 'error' && $publish_on < \Drupal::time()->getRequestTime()) {
       $this->context->buildViolation($constraint->messagePublishOnDateNotInFuture)
         ->atPath('publish_on')
         ->addViolation();
diff --git a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
index 05da8014ff..62464450b3 100644
--- a/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
+++ b/web/modules/scheduler/src/Plugin/Validation/Constraint/SchedulerUnpublishOnConstraintValidator.php
@@ -40,7 +40,7 @@ public function validate($entity, Constraint $constraint) {
     // Check that the unpublish-on date is in the future. Unlike the publish-on
     // field, there is no option to use a past date, as this is not relevant for
     // unpublshing. The date must ALWAYS be in the future if it is entered.
-    if ($unpublish_on && $unpublish_on < REQUEST_TIME) {
+    if ($unpublish_on && $unpublish_on < \Drupal::time()->getRequestTime()) {
       $this->context->buildViolation($constraint->messageUnpublishOnDateNotInFuture)
         ->atPath('unpublish_on')
         ->addViolation();
diff --git a/web/modules/scheduler/src/SchedulerEvent.php b/web/modules/scheduler/src/SchedulerEvent.php
index ca857e4750..b5d8fac9b9 100644
--- a/web/modules/scheduler/src/SchedulerEvent.php
+++ b/web/modules/scheduler/src/SchedulerEvent.php
@@ -13,7 +13,7 @@ class SchedulerEvent extends Event {
   /**
    * Node object.
    *
-   * @var Drupal\Core\Entity\EntityInterface
+   * @var \Drupal\Core\Entity\EntityInterface
    */
   protected $node;
 
diff --git a/web/modules/scheduler/src/SchedulerManager.php b/web/modules/scheduler/src/SchedulerManager.php
index de17b181a3..2cc5dccbda 100644
--- a/web/modules/scheduler/src/SchedulerManager.php
+++ b/web/modules/scheduler/src/SchedulerManager.php
@@ -2,26 +2,31 @@
 
 namespace Drupal\scheduler;
 
-use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Datetime\DateFormatter;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Datetime\DateFormatterInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Link;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
-use Drupal\Core\Extension\ModuleHandler;
-use Drupal\node\Entity\Node;
 use Drupal\node\NodeInterface;
 use Drupal\scheduler\Exception\SchedulerMissingDateException;
 use Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException;
 use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Defines a scheduler manager.
  */
 class SchedulerManager {
 
+  use StringTranslationTrait;
+
   /**
    * Date formatter service object.
    *
-   * @var \Drupal\Core\Datetime\DateFormatter
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
    */
   protected $dateFormatter;
 
@@ -35,33 +40,49 @@ class SchedulerManager {
   /**
    * Module handler service object.
    *
-   * @var \Drupal\Core\Extension\ModuleHandler
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
    */
   protected $moduleHandler;
 
   /**
-   * Entity Manager service object.
+   * Entity Type Manager service object.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
 
   /**
    * Config Factory service object.
    *
-   * @var \Drupal\Core\Config\ConfigFactory
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
    */
   protected $configFactory;
 
+  /**
+   * The event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $eventDispatcher;
+
+  /**
+   * The time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
   /**
    * Constructs a SchedulerManager object.
    */
-  public function __construct(DateFormatter $dateFormatter, LoggerInterface $logger, ModuleHandler $moduleHandler, EntityManager $entityManager, ConfigFactory $configFactory) {
+  public function __construct(DateFormatterInterface $dateFormatter, LoggerInterface $logger, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, ConfigFactoryInterface $configFactory, EventDispatcherInterface $eventDispatcher, TimeInterface $time) {
     $this->dateFormatter = $dateFormatter;
     $this->logger = $logger;
     $this->moduleHandler = $moduleHandler;
-    $this->entityManager = $entityManager;
+    $this->entityTypeManager = $entityTypeManager;
     $this->configFactory = $configFactory;
+    $this->eventDispatcher = $eventDispatcher;
+    $this->time = $time;
   }
 
   /**
@@ -74,11 +95,6 @@ public function __construct(DateFormatter $dateFormatter, LoggerInterface $logge
    * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException
    */
   public function publish() {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::service with dependency injection?
-    /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
-    $dispatcher = \Drupal::service('event_dispatcher');
-
     $result = FALSE;
     $action = 'publish';
 
@@ -87,12 +103,11 @@ public function publish() {
     $nids = [];
     $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action));
     if (!empty($scheduler_enabled_types)) {
-      // @TODO: \Drupal calls should be avoided in classes.
-      // Replace \Drupal::entityQuery with dependency injection?
-      $query = \Drupal::entityQuery('node')
+      $query = $this->entityTypeManager->getStorage('node')->getQuery()
         ->exists('publish_on')
-        ->condition('publish_on', REQUEST_TIME, '<=')
+        ->condition('publish_on', $this->time->getRequestTime(), '<=')
         ->condition('type', $scheduler_enabled_types, 'IN')
+        ->latestRevision()
         ->sort('publish_on')
         ->sort('nid');
       // Disable access checks for this query.
@@ -111,9 +126,8 @@ public function publish() {
     // unlike 7.x where each translation was a separate node. This means that
     // the list of node ids returned above may have some translations that need
     // processing now and others that do not.
-    $nodes = Node::loadMultiple($nids);
-    // @TODO: Node::loadMultiple calls should be avoided in classes.
-    // Replace with dependency injection?
+    /** @var \Drupal\node\NodeInterface[] $nodes */
+    $nodes = $this->loadNodes($nids);
     foreach ($nodes as $node_multilingual) {
 
       // The API calls could return nodes of types which are not enabled for
@@ -131,7 +145,7 @@ public function publish() {
         // If the current translation does not have a publish on value, or it is
         // later than the date we are processing then move on to the next.
         $publish_on = $node->publish_on->value;
-        if (empty($publish_on) || $publish_on > REQUEST_TIME) {
+        if (empty($publish_on) || $publish_on > $this->time->getRequestTime()) {
           continue;
         }
 
@@ -140,13 +154,13 @@ public function publish() {
           continue;
         }
 
-        // $node->set('changed', $publish_on) will fail badly if an API call has
+        // $node->setChangedTime($publish_on) will fail badly if an API call has
         // removed the date. Trap this as an exception here and give a
         // meaningful message.
         // @TODO This will now never be thrown due to the empty(publish_on)
         // check above to cater for translations. Remove this exception?
         if (empty($node->publish_on->value)) {
-          $field_definitions = $this->entityManager->getFieldDefinitions('node', $node->getType());
+          $field_definitions = $this->entityTypeManager->getFieldDefinitions('node', $node->getType());
           $field = (string) $field_definitions['publish_on']->getLabel();
           throw new SchedulerMissingDateException(sprintf("Node %d '%s' will not be published because field '%s' has no value", $node->id(), $node->getTitle(), $field));
         }
@@ -154,46 +168,79 @@ public function publish() {
         // Trigger the PRE_PUBLISH event so that modules can react before the
         // node is published.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::PRE_PUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::PRE_PUBLISH, $event);
         $node = $event->getNode();
 
-        // Update timestamps.
-        $node->set('changed', $publish_on);
+        // Update 'changed' timestamp.
+        $node->setChangedTime($publish_on);
         $old_creation_date = $node->getCreatedTime();
-        if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_touch', $this->setting('default_publish_touch'))) {
+        $msg_extra = '';
+        // If required, set the created date to match published date.
+        if ($node->type->entity->getThirdPartySetting('scheduler', 'publish_touch', $this->setting('default_publish_touch')) ||
+          ($node->getCreatedTime() > $publish_on && $node->type->entity->getThirdPartySetting('scheduler', 'publish_past_date_created', $this->setting('default_publish_past_date_created')))
+        ) {
           $node->setCreatedTime($publish_on);
+          $msg_extra = $this->t('The previous creation date was @old_creation_date, now updated to match the publishing date.', [
+            '@old_creation_date' => $this->dateFormatter->format($old_creation_date, 'short'),
+          ]);
         }
 
         $create_publishing_revision = $node->type->entity->getThirdPartySetting('scheduler', 'publish_revision', $this->setting('default_publish_revision'));
         if ($create_publishing_revision) {
           $node->setNewRevision();
           // Use a core date format to guarantee a time is included.
-          // @TODO: 't' calls should be avoided in classes.
-          // Replace with dependency injection?
-          $node->revision_log = t('Node published by Scheduler on @now. Previous creation date was @date.', [
-            '@now' => $this->dateFormatter->format(REQUEST_TIME, 'short'),
-            '@date' => $this->dateFormatter->format($old_creation_date, 'short'),
-          ]);
+          $revision_log_message = rtrim($this->t('Published by Scheduler. The scheduled publishing date was @publish_on.', [
+            '@publish_on' => $this->dateFormatter->format($publish_on, 'short'),
+          ]) . ' ' . $msg_extra);
+          $node->setRevisionLogMessage($revision_log_message)
+            ->setRevisionCreationTime($this->time->getRequestTime());
         }
         // Unset publish_on so the node will not get rescheduled by subsequent
         // calls to $node->save().
         $node->publish_on->value = NULL;
 
+        // Invoke all implementations of hook_scheduler_publish_action() to
+        // allow other modules to do the "publishing" process instead of
+        // Scheduler.
+        $hook = 'scheduler_publish_action';
+        $processed = FALSE;
+        $failed = FALSE;
+        foreach ($this->moduleHandler->getImplementations($hook) as $module) {
+          $function = $module . '_' . $hook;
+          $return = $function($node);
+          $processed = $processed || ($return === 1);
+          $failed = $failed || ($return === -1);
+        }
+
         // Log the fact that a scheduled publication is about to take place.
-        $view_link = $node->link(t('View node'));
-        $nodetype_url = Url::fromRoute('entity.node_type.edit_form', ['node_type' => $node->getType()]);
-        // @TODO: \Drupal calls should be avoided in classes.
-        // Replace \Drupal::l with dependency injection?
-        $nodetype_link = \Drupal::l(node_get_type_label($node) . ' ' . t('settings'), $nodetype_url);
+        $view_link = $node->toLink($this->t('View node'));
+        $node_type = $this->entityTypeManager->getStorage('node_type')->load($node->bundle());
+        $node_type_link = $node_type->toLink($this->t('@label settings', ['@label' => $node_type->label()]), 'edit-form');
         $logger_variables = [
-          '@type' => node_get_type_label($node),
+          '@type' => $node_type->label(),
           '%title' => $node->getTitle(),
-          'link' => $nodetype_link . ' ' . $view_link,
+          'link' => $node_type_link->toString() . ' ' . $view_link->toString(),
+          '@hook' => 'hook_' . $hook,
         ];
-        $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables);
 
-        // Use the actions system to publish the node.
-        $this->entityManager->getStorage('action')->load('node_publish_action')->getPlugin()->execute($node);
+        if ($failed) {
+          // At least one hook function returned a failure or exception, so stop
+          // processing this node and move on to the next one.
+          $this->logger->warning('Publishing failed for %title. Calls to @hook returned a failure code.', $logger_variables);
+          continue;
+        }
+        elseif ($processed) {
+          // The node had 'publishing' processed by a module implementing the
+          // hook, so no need to do anything more, apart from log this result.
+          $this->logger->notice('@type: scheduled processing of %title completed by calls to @hook.', $logger_variables);
+        }
+        else {
+          // None of the above hook calls processed the node and there were no
+          // errors detected so fall back to the standard actions system to
+          // publish the node.
+          $this->logger->notice('@type: scheduled publishing of %title.', $logger_variables);
+          $this->entityTypeManager->getStorage('action')->load('node_publish_action')->getPlugin()->execute($node);
+        }
 
         // Invoke the event to tell Rules that Scheduler has published the node.
         if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) {
@@ -203,7 +250,7 @@ public function publish() {
         // Trigger the PUBLISH event so that modules can react after the node is
         // published.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::PUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::PUBLISH, $event);
         $event->getNode()->save();
 
         $result = TRUE;
@@ -223,11 +270,6 @@ public function publish() {
    * @throws \Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException
    */
   public function unpublish() {
-    // @TODO: \Drupal calls should be avoided in classes.
-    // Replace \Drupal::service with dependency injection?
-    /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */
-    $dispatcher = \Drupal::service('event_dispatcher');
-
     $result = FALSE;
     $action = 'unpublish';
 
@@ -236,12 +278,11 @@ public function unpublish() {
     $nids = [];
     $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action));
     if (!empty($scheduler_enabled_types)) {
-      // @TODO: \Drupal calls should be avoided in classes.
-      // Replace \Drupal::entityQuery with dependency injection?
-      $query = \Drupal::entityQuery('node')
+      $query = $this->entityTypeManager->getStorage('node')->getQuery()
         ->exists('unpublish_on')
-        ->condition('unpublish_on', REQUEST_TIME, '<=')
+        ->condition('unpublish_on', $this->time->getRequestTime(), '<=')
         ->condition('type', $scheduler_enabled_types, 'IN')
+        ->latestRevision()
         ->sort('unpublish_on')
         ->sort('nid');
       // Disable access checks for this query.
@@ -256,9 +297,8 @@ public function unpublish() {
     // Allow other modules to alter the list of nodes to be unpublished.
     $this->moduleHandler->alter('scheduler_nid_list', $nids, $action);
 
-    // @TODO: Node::loadMultiple calls should be avoided in classes.
-    // Replace with dependency injection?
-    $nodes = Node::loadMultiple($nids);
+    /** @var \Drupal\node\NodeInterface[] $nodes */
+    $nodes = $this->loadNodes($nids);
     foreach ($nodes as $node_multilingual) {
       // The API calls could return nodes of types which are not enabled for
       // scheduled unpublishing. Do not process these.
@@ -274,7 +314,7 @@ public function unpublish() {
         // If the current translation does not have an unpublish on value, or it
         // is later than the date we are processing then move on to the next.
         $unpublish_on = $node->unpublish_on->value;
-        if (empty($unpublish_on) || $unpublish_on > REQUEST_TIME) {
+        if (empty($unpublish_on) || $unpublish_on > $this->time->getRequestTime()) {
           continue;
         }
 
@@ -283,7 +323,7 @@ public function unpublish() {
         // by one of the hook functions we provide, and is still being blocked
         // now that the unpublishing time has been reached.
         $publish_on = $node->publish_on->value;
-        if (!empty($publish_on) && $publish_on <= REQUEST_TIME) {
+        if (!empty($publish_on) && $publish_on <= $this->time->getRequestTime()) {
           continue;
         }
 
@@ -292,13 +332,13 @@ public function unpublish() {
           continue;
         }
 
-        // $node->set('changed', $unpublish_on) will fail badly if an API call
+        // $node->setChangedTime($unpublish_on) will fail badly if an API call
         // has removed the date. Trap this as an exception here and give a
         // meaningful message.
         // @TODO This will now never be thrown due to the empty(unpublish_on)
         // check above to cater for translations. Remove this exception?
         if (empty($unpublish_on)) {
-          $field_definitions = $this->entityManager->getFieldDefinitions('node', $node->getType());
+          $field_definitions = $this->entityTypeManager->getFieldDefinitions('node', $node->getType());
           $field = (string) $field_definitions['unpublish_on']->getLabel();
           throw new SchedulerMissingDateException(sprintf("Node %d '%s' will not be unpublished because field '%s' has no value", $node->id(), $node->getTitle(), $field));
         }
@@ -306,53 +346,79 @@ public function unpublish() {
         // Trigger the PRE_UNPUBLISH event so that modules can react before the
         // node is unpublished.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::PRE_UNPUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::PRE_UNPUBLISH, $event);
         $node = $event->getNode();
 
-        // Update timestamps.
-        $old_change_date = $node->getChangedTime();
-        $node->set('changed', $unpublish_on);
+        // Update 'changed' timestamp.
+        $node->setChangedTime($unpublish_on);
 
         $create_unpublishing_revision = $node->type->entity->getThirdPartySetting('scheduler', 'unpublish_revision', $this->setting('default_unpublish_revision'));
         if ($create_unpublishing_revision) {
           $node->setNewRevision();
           // Use a core date format to guarantee a time is included.
-          // @TODO: 't' calls should be avoided in classes.
-          // Replace with dependency injection?
-          $node->revision_log = t('Node unpublished by Scheduler on @now. Previous change date was @date.', [
-            '@now' => $this->dateFormatter->format(REQUEST_TIME, 'short'),
-            '@date' => $this->dateFormatter->format($old_change_date, 'short'),
+          $revision_log_message = $this->t('Unpublished by Scheduler. The scheduled unpublishing date was @unpublish_on.', [
+            '@unpublish_on' => $this->dateFormatter->format($unpublish_on, 'short'),
           ]);
+          // Create the new revision, setting message and revision timestamp.
+          $node->setRevisionLogMessage($revision_log_message)
+            ->setRevisionCreationTime($this->time->getRequestTime());
         }
         // Unset unpublish_on so the node will not get rescheduled by subsequent
-        // calls to $node->save(). Save the value for use when calling Rules.
+        // calls to $node->save().
         $node->unpublish_on->value = NULL;
 
-        // Log the fact that a scheduled unpublication is about to take place.
-        $view_link = $node->link(t('View node'));
-        $nodetype_url = Url::fromRoute('entity.node_type.edit_form', ['node_type' => $node->getType()]);
-        // @TODO: \Drupal calls should be avoided in classes.
-        // Replace \Drupal::l with dependency injection?
-        $nodetype_link = \Drupal::l(node_get_type_label($node) . ' ' . t('settings'), $nodetype_url);
+        // Invoke all implementations of hook_scheduler_unpublish_action() to
+        // allow other modules to do the "unpublishing" process instead of
+        // Scheduler.
+        $hook = 'scheduler_unpublish_action';
+        $processed = FALSE;
+        $failed = FALSE;
+        foreach ($this->moduleHandler->getImplementations($hook) as $module) {
+          $function = $module . '_' . $hook;
+          $return = $function($node);
+          $processed = $processed || ($return === 1);
+          $failed = $failed || ($return === -1);
+        }
+
+        // Set up the log variables.
+        $view_link = $node->toLink($this->t('View node'));
+        $node_type = $this->entityTypeManager->getStorage('node_type')->load($node->bundle());
+        $node_type_link = $node_type->toLink($this->t('@label settings', ['@label' => $node_type->label()]), 'edit-form');
         $logger_variables = [
-          '@type' => node_get_type_label($node),
+          '@type' => $node_type->label(),
           '%title' => $node->getTitle(),
-          'link' => $nodetype_link . ' ' . $view_link,
+          'link' => $node_type_link->toString() . ' ' . $view_link->toString(),
+          '@hook' => 'hook_' . $hook,
         ];
-        $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables);
 
-        // Use the actions system to publish the node.
-        $this->entityManager->getStorage('action')->load('node_unpublish_action')->getPlugin()->execute($node);
+        if ($failed) {
+          // At least one hook function returned a failure or exception, so stop
+          // processing this node and move on to the next one.
+          $this->logger->warning('Unpublishing failed for %title. Calls to @hook returned a failure code.', $logger_variables);
+          continue;
+        }
+        elseif ($processed) {
+          // The node has 'unpublishing' processed by a module implementing the
+          // hook, so no need to do anything more, apart from log this result.
+          $this->logger->notice('@type: scheduled processing of %title completed by calls to @hook.', $logger_variables);
+        }
+        else {
+          // None of the above hook calls processed the node and there were no
+          // errors detected so fall back to the standard actions system to
+          // unpublish the node.
+          $this->logger->notice('@type: scheduled unpublishing of %title.', $logger_variables);
+          $this->entityTypeManager->getStorage('action')->load('node_unpublish_action')->getPlugin()->execute($node);
+        }
 
         // Invoke event to tell Rules that Scheduler has unpublished this node.
         if ($this->moduleHandler->moduleExists('scheduler_rules_integration')) {
           _scheduler_rules_integration_dispatch_cron_event($node, 'unpublish');
         }
 
-        // Trigger the UNPUBLISH event so that modules can react before the node
+        // Trigger the UNPUBLISH event so that modules can react after the node
         // is unpublished.
         $event = new SchedulerEvent($node);
-        $dispatcher->dispatch(SchedulerEvents::UNPUBLISH, $event);
+        $this->eventDispatcher->dispatch(SchedulerEvents::UNPUBLISH, $event);
         $event->getNode()->save();
 
         $result = TRUE;
@@ -426,11 +492,25 @@ public function nidList($action) {
    * This function is called from the external crontab job via url
    * /scheduler/cron/{access key} or it can be run interactively from the
    * Scheduler configuration page at /admin/config/content/scheduler/cron.
+   * It is also executed when running Scheduler Cron via drush.
+   *
+   * @param array $options
+   *   Options passed from drush command or admin form.
    */
-  public function runLightweightCron() {
-    $log = $this->setting('log');
+  public function runLightweightCron(array $options = []) {
+    // When calling via drush the log messages can be avoided by using --nolog.
+    $log = $this->setting('log') && empty($options['nolog']);
     if ($log) {
-      $this->logger->notice('Lightweight cron run activated.');
+      if (array_key_exists('nolog', $options)) {
+        $trigger = 'drush command';
+      }
+      elseif (array_key_exists('admin_form', $options)) {
+        $trigger = 'admin user form';
+      }
+      else {
+        $trigger = 'url';
+      }
+      $this->logger->notice('Lightweight cron run activated by @trigger.', ['@trigger' => $trigger]);
     }
     scheduler_cron();
     if (ob_get_level() > 0) {
@@ -440,9 +520,8 @@ public function runLightweightCron() {
       }
     }
     if ($log) {
-      // @TODO: \Drupal calls should be avoided in classes.
-      // Replace \Drupal::l with dependency injection?
-      $this->logger->notice('Lightweight cron run completed.', ['link' => \Drupal::l(t('settings'), Url::fromRoute('scheduler.cron_form'))]);
+      $link = Link::fromTextAndUrl($this->t('settings'), Url::fromRoute('scheduler.cron_form'));
+      $this->logger->notice('Lightweight cron run completed.', ['link' => $link->toString()]);
     }
   }
 
@@ -459,4 +538,31 @@ protected function setting($key) {
     return $this->configFactory->get('scheduler.settings')->get($key);
   }
 
+  /**
+   * Helper method to load latest revision of each node.
+   *
+   * @param array $nids
+   *   Array of node ids.
+   *
+   * @return array
+   *   Array of loaded nodes.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  protected function loadNodes(array $nids) {
+    $node_storage = $this->entityTypeManager->getStorage('node');
+    $nodes = [];
+
+    // Load the latest revision for each node.
+    foreach ($nids as $nid) {
+      $node = $node_storage->load($nid);
+      $revision_ids = $node_storage->revisionIds($node);
+      $vid = end($revision_ids);
+      $nodes[] = $node_storage->loadRevision($vid);
+    }
+
+    return $nodes;
+  }
+
 }
diff --git a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
index e23f609c9c..2a67f10fe1 100644
--- a/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_access_test/scheduler_access_test.info.yml
@@ -2,12 +2,11 @@ name: 'Scheduler Node Access Test'
 type: module
 description: 'Support module for Scheduler restricted node access testing.'
 package: Testing
-# core: 8.x
+core: 8.x
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
index 1427f62a70..28cf6d1a03 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.info.yml
@@ -2,12 +2,11 @@ name: 'Scheduler API Test'
 type: module
 description: 'Support module for Scheduler API-related testing.'
 package: Testing
-# core: 8.x
+core: 8.x
 dependencies:
   - scheduler:scheduler
 
-# Information added by Drupal.org packaging script on 2017-11-14
-version: '8.x-1.0'
-core: '8.x'
+# Information added by Drupal.org packaging script on 2019-08-23
+version: '8.x-1.1'
 project: 'scheduler'
-datestamp: 1510690392
+datestamp: 1566566892
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install
index d1509db922..b3861648cf 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.install
@@ -22,7 +22,7 @@ function scheduler_api_test_uninstall() {
     ->execute();
   if ($nids = $nids_query->fetchCol()) {
     entity_delete_multiple('node', $nids);
-    drupal_set_message(t('@number %type node(s) have been deleted.', [
+    \Drupal::messenger()->addMessage(t('@number %type node(s) have been deleted.', [
       '@number' => count($nids),
       '%type' => 'scheduler_api_test',
     ]));
diff --git a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module
index 70f0286c2a..30e035ab10 100644
--- a/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module
+++ b/web/modules/scheduler/tests/modules/scheduler_api_test/scheduler_api_test.module
@@ -6,6 +6,7 @@
  */
 
 use Drupal\node\Entity\Node;
+use Drupal\node\NodeInterface;
 
 /**
  * Implements hook_scheduler_nid_list().
@@ -16,18 +17,18 @@ function scheduler_api_test_scheduler_nid_list($action) {
   // Check to see what test nodes exist.
   $query = \Drupal::entityQuery('node');
   $nodes = Node::loadMultiple($query->execute());
-
+  $request_time = \Drupal::time()->getRequestTime();
   foreach ($nodes as $nid => $node) {
     // If publishing and this is the publish test node, set a date and add
     // the node id to the list.
     if ($action == 'publish' && $node->title->value == 'API TEST nid_list publish me') {
-      $node->set('publish_on', REQUEST_TIME)->save();
+      $node->set('publish_on', $request_time)->save();
       $nids[] = $nid;
     }
     // If unpublishing and this is the unpublish test node, set a date and add
     // the node id to the list.
     if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list unpublish me') {
-      $node->set('unpublish_on', REQUEST_TIME)->save();
+      $node->set('unpublish_on', $request_time)->save();
       $nids[] = $nid;
     }
   }
@@ -40,7 +41,7 @@ function scheduler_api_test_scheduler_nid_list($action) {
 function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
   $query = \Drupal::entityQuery('node');
   $nodes = Node::loadMultiple($query->execute());
-
+  $request_time = \Drupal::time()->getRequestTime();
   foreach ($nodes as $nid => $node) {
     if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter do not publish me') {
       // Remove the node id.
@@ -48,7 +49,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
     }
     if ($action == 'publish' && $node->title->value == 'API TEST nid_list_alter publish me') {
       // Set a publish_on date and add the node id.
-      $node->set('publish_on', REQUEST_TIME)->save();
+      $node->set('publish_on', $request_time)->save();
       $nids[] = $nid;
     }
     if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter do not unpublish me') {
@@ -57,7 +58,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
     }
     if ($action == 'unpublish' && $node->title->value == 'API TEST nid_list_alter unpublish me') {
       // Set an unpublish_on date and add the node id.
-      $node->set('unpublish_on', REQUEST_TIME)->save();
+      $node->set('unpublish_on', $request_time)->save();
       $nids[] = $nid;
     }
   }
@@ -67,7 +68,7 @@ function scheduler_api_test_scheduler_nid_list_alter(&$nids, $action) {
 /**
  * Implements hook_scheduler_allow_publishing().
  */
-function scheduler_api_test_scheduler_allow_publishing($node) {
+function scheduler_api_test_scheduler_allow_publishing(NodeInterface $node) {
   // If there is no 'Approved for Publishing' field then allow publishing.
   if (!isset($node->field_approved_publishing)) {
     $allowed = TRUE;
@@ -77,14 +78,14 @@ function scheduler_api_test_scheduler_allow_publishing($node) {
     $allowed = $node->field_approved_publishing->value;
     // If publication is denied then inform the user why.
     if (!$allowed) {
-      drupal_set_message(t('%title is scheduled for publishing, but will not be published until approved.', ['%title' => $node->title->value]), 'status', FALSE);
+      \Drupal::messenger()->addMessage(t('%title is scheduled for publishing, but will not be published until approved.', ['%title' => $node->title->value]), 'status', FALSE);
       // If the time is in the past it means that the action has been prevented.
       // Write a dblog message to show this. Give a link to view the node but
       // cater for no nid as the node may be new and not yet saved.
-      if ($node->publish_on->value <= REQUEST_TIME) {
+      if ($node->publish_on->value <= \Drupal::time()->getRequestTime()) {
         \Drupal::logger('scheduler_api_test')->warning('Publishing of "%title" is prevented until approved.', [
           '%title' => $node->title->value,
-          'link' => $node->id() ? $node->link(t('View node')) : '',
+          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
         ]);
       }
     }
@@ -95,7 +96,7 @@ function scheduler_api_test_scheduler_allow_publishing($node) {
 /**
  * Implements hook_scheduler_allow_unpublishing().
  */
-function scheduler_api_test_scheduler_allow_unpublishing($node) {
+function scheduler_api_test_scheduler_allow_unpublishing(NodeInterface $node) {
   // If there is no 'Approved for Unpublishing' field then allow unpublishing.
   if (!isset($node->field_approved_unpublishing)) {
     $allowed = TRUE;
@@ -105,17 +106,89 @@ function scheduler_api_test_scheduler_allow_unpublishing($node) {
     $allowed = $node->field_approved_unpublishing->value;
     // If unpublication is denied then inform the user why.
     if (!$allowed) {
-      drupal_set_message(t('%title is scheduled for unpublishing, but will not be unpublished until approved.', ['%title' => $node->title->value]), 'status', FALSE);
+      \Drupal::messenger()->addMessage(t('%title is scheduled for unpublishing, but will not be unpublished until approved.', ['%title' => $node->title->value]), 'status', FALSE);
       // If the time is in the past it means that the action has been prevented.
       // Write a dblog message to show this. Give a link to view the node but
       // cater for no nid as the node may be new and not yet saved.
-      if ($node->unpublish_on->value <= REQUEST_TIME) {
+      if ($node->unpublish_on->value <= \Drupal::time()->getRequestTime()) {
         \Drupal::logger('scheduler_api_test')->warning('Unpublishing of "%title" is prevented until approved.', [
           '%title' => $node->title->value,
-          'link' => $node->id() ? $node->link(t('View node')) : '',
+          'link' => $node->id() ? $node->toLink(t('View node'))->toString() : '',
         ]);
       }
     }
   }
   return $allowed;
 }
+
+/**
+ * Implements hook_scheduler_hide_publish_on_field().
+ */
+function scheduler_api_test_scheduler_hide_publish_on_field($form, $form_state, $node) {
+  // Hide the publish_on field if the node title contains orange or green.
+  if (stristr($node->title->value, 'orange') || stristr($node->title->value, 'green')) {
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The publish_on field is hidden for orange or green node titles.'), 'status', FALSE);
+    return TRUE;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Implements hook_scheduler_hide_unpublish_on_field().
+ */
+function scheduler_api_test_scheduler_hide_unpublish_on_field($form, $form_state, $node) {
+  // Hide the publish_on field if the node title contains yellow or green.
+  if (stristr($node->title->value, 'yellow') || stristr($node->title->value, 'green')) {
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: The unpublish_on field is hidden for yellow or green node titles.'), 'status', FALSE);
+    return TRUE;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Implements hook_scheduler_publish_action().
+ */
+function scheduler_api_test_scheduler_publish_action($node) {
+  if (stristr($node->title->value, 'red')) {
+    // Nodes with red in the title are simulated to cause a failure and should
+    // then be skipped by Scheduler.
+    $node->set('title', $node->title->value . ' - publishing failed in API test module');
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Red nodes should cause Scheduler to abandon publishing.'), 'status', FALSE);
+    return -1;
+  }
+  elseif (stristr($node->title->value, 'yellow')) {
+    // Nodes with yellow in the title are simulated to be processed by this
+    // hook, and will not be published by Scheduler.
+    $node->set('title', $node->title->value . ' - publishing processed by API test module');
+    $node->setPublished();
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Yellow nodes should not have publishing processed by Scheduler.'), 'status', FALSE);
+    return 1;
+  }
+  return 0;
+}
+
+/**
+ * Implements hook_scheduler_unpublish_action().
+ */
+function scheduler_api_test_scheduler_unpublish_action($node) {
+  if (stristr($node->title->value, 'blue')) {
+    // Nodes with blue in the title are simulated to cause a failure and should
+    // then be skipped by Scheduler.
+    $node->set('title', $node->title->value . ' - unpublishing failed in API test module');
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Blue nodes should cause Scheduler to abandon unpublishing.'), 'status', FALSE);
+    return -1;
+  }
+  if (stristr($node->title->value, 'orange')) {
+    // Nodes with orange in the title are simulated to be processed by this
+    // hook, and will not be published by Scheduler.
+    $node->set('title', $node->title->value . ' - unpublishing processed by API test module');
+    $node->setUnpublished();
+    \Drupal::messenger()->addMessage(t('Scheduler_Api_Test: Orange nodes should not have unpublishing processed by Scheduler.'), 'status', FALSE);
+    return 1;
+  }
+  return 0;
+}
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
index 7c8a999793..4c90cc3466 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerAdminSettingsTest.php
@@ -29,20 +29,20 @@ public function testAdminSettings() {
       'allow_date_only' => TRUE,
       'default_time' => '6:30',
     ];
-    $this->drupalPostForm('admin/config/content/scheduler', $settings, t('Save configuration'));
+    $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration');
 
     // Verify that the values have been saved correctly.
     $this->assertTrue($this->config('scheduler.settings')->get('allow_date_only'), 'The config setting for allow_date_only is stored correctly.');
-    $this->assertEqual($this->config('scheduler.settings')->get('default_time'), $this->seconds_formatted, 'The config setting for default_time is stored correctly.');
+    $this->assertEquals($this->seconds_formatted, $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time is stored correctly.');
 
     // Try to save an invalid time value.
     $settings = [
       'allow_date_only' => TRUE,
       'default_time' => '123',
     ];
-    $this->drupalPostForm('admin/config/content/scheduler', $settings, t('Save configuration'));
+    $this->drupalPostForm('admin/config/content/scheduler', $settings, 'Save configuration');
     // Verify that an error is displayed and the value has not been saved.
-    $this->assertEqual($this->config('scheduler.settings')->get('default_time'), $this->seconds_formatted, 'The config setting for default_time has not changed.');
+    $this->assertEquals($this->seconds_formatted, $this->config('scheduler.settings')->get('default_time'), 'The config setting for default_time has not changed.');
     $this->assertText('The default time should be in the format HH:MM:SS', 'When an invalid default time is entered the correct error message is displayed.');
 
     // Show the status report, which includes the Scheduler timecheck.
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
index 278beaadce..53fc962796 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerApiTest.php
@@ -68,7 +68,7 @@ public function testAllowedPublishing() {
       'publish_on[0][value][date]' => date('Y-m-d', time() + 3),
       'publish_on[0][value][time]' => date('H:i:s', time() + 3),
     ];
-    $this->drupalPostForm('node/add/' . $this->customName, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save');
     $this->assertText('is scheduled for publishing, but will not be published until approved.', 'The message is shown when scheduling a node which is not yet allowed to be published.');
 
     // Create a node that is scheduled but not approved for publication. Then
@@ -105,7 +105,7 @@ public function testAllowedPublishing() {
 
     // Check that a node can be approved and published via edit form.
     $node = $this->createUnapprovedNode('publish_on');
-    $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved_publishing[value]' => '1'], t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', ['field_approved_publishing[value]' => '1'], 'Save');
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     $this->assertTrue($node->isPublished(), 'An approved node with a date in the past is published immediately after saving via edit form.');
@@ -135,7 +135,7 @@ public function testAllowedUnpublishing() {
       'unpublish_on[0][value][date]' => date('Y-m-d', time() + 3),
       'unpublish_on[0][value][time]' => date('H:i:s', time() + 3),
     ];
-    $this->drupalPostForm('node/add/' . $this->customName, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->customName, $edit, 'Save');
     $this->assertText('is scheduled for unpublishing, but will not be unpublished until approved.', 'The message is shown when scheduling a node which is not yet allowed to be unpublished.');
 
     // Create a node that is scheduled but not approved for unpublication. Then
@@ -254,16 +254,16 @@ public function testApiNodeAction() {
 
     // Edit the node and set a publish-on date in the past.
     $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', REQUEST_TIME)),
+      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', $this->requestTime)),
+      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', $this->requestTime)),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     // Verify that the values have been altered as expected.
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     $this->assertTrue($node->isSticky(), 'API action "PRE_PUBLISH_IMMEDIATELY" has changed the node to sticky.');
     $this->assertTrue($node->isPromoted(), 'API action "PUBLISH_IMMEDIATELY" has changed the node to promoted.');
-    $this->assertEqual($node->title->value, 'Published immediately', 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.');
+    $this->assertEquals('Published immediately', $node->title->value, 'API action "PUBLISH_IMMEDIATELY" has changed the node title correctly.');
   }
 
   /**
@@ -377,4 +377,120 @@ public function testNidListAlter() {
     $this->assertFalse($node4->isPublished(), 'After cron, node 4 "' . $node4->title->value . '" is unpublished.');
   }
 
+  /**
+   * Test the hooks which allow hiding of scheduler input fields.
+   *
+   * This covers hook_scheduler_hide_publish_on_field and
+   * hook_scheduler_hide_unpublish_on_field.
+   */
+  public function testHideField() {
+    $this->drupalLogin($this->schedulerUser);
+
+    // Create test nodes.
+    $node1 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Red will not have either field hidden',
+    ]);
+    $node2 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Orange will have the publish-on field hidden',
+    ]);
+    $node3 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Yellow will have the unpublish-on field hidden',
+    ]);
+    $node4 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'title' => 'Green will have both Scheduler fields hidden',
+    ]);
+
+    // Node 1 'red' should have both fields displayed.
+    $this->drupalGet('node/' . $node1->id() . '/edit');
+    $this->assertTrue($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The red publish-on field is shown.');
+    $this->assertTrue($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The red unpublish-on field is shown.');
+
+    // Node 2 'orange' should have only the publish-on field hidden.
+    $this->drupalGet('node/' . $node2->id() . '/edit');
+    $this->assertFalse($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The orange publish-on field is hidden.');
+    $this->assertTrue($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The orange unpublish-on field is shown.');
+
+    // Node 3 'yellow' should have only the unpublish-on field hidden.
+    $this->drupalGet('node/' . $node3->id() . '/edit');
+    $this->assertTrue($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The yellow publish-on field is shown.');
+    $this->assertFalse($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The yellow unpublish-on field is hidden.');
+
+    // Node 4 'green' should have both publish-on and unpublish-on hidden.
+    $this->drupalGet('node/' . $node4->id() . '/edit');
+    $this->assertFalse($this->xpath('//input[@id = "edit-publish-on-0-value-date"]'), 'The green publish-on field is hidden.');
+    $this->assertFalse($this->xpath('//input[@id = "edit-unpublish-on-0-value-date"]'), 'The green unpublish-on field is hidden.');
+  }
+
+  /**
+   * Test when other modules process the publish and unpublish actions.
+   *
+   * This covers hook_scheduler_publish_action and
+   * hook_scheduler_unpublish_action.
+   */
+  public function testHookPublishUnpublishAction() {
+    $this->drupalLogin($this->schedulerUser);
+
+    // Create test nodes.
+    $node1 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'title' => 'Red will cause a failure on publishing',
+      'publish_on' => strtotime('-1 day'),
+    ]);
+    $node2 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'title' => 'Orange will be unpublished by the API test module not Scheduler',
+      'unpublish_on' => strtotime('-1 day'),
+    ]);
+    $node3 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'title' => 'Yellow will be published by the API test module not Scheduler',
+      'publish_on' => strtotime('-1 day'),
+    ]);
+    // 'green' nodes will have both fields hidden so is harder to test manually.
+    // Therefore introduce a different colour.
+    $node4 = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'title' => 'Blue will cause a failure on unpublishing',
+      'unpublish_on' => strtotime('-1 day'),
+    ]);
+
+    // Simulate a cron run.
+    scheduler_cron();
+
+    // Check the red node.
+    $this->nodeStorage->resetCache([$node1->id()]);
+    $node1 = $this->nodeStorage->load($node1->id());
+    $this->assertFalse($node1->isPublished(), 'The red node is still unpublished.');
+    $this->assertTrue($node1->publish_on->value, 'The red node still has a publish-on date.');
+
+    // Check the orange node.
+    $this->nodeStorage->resetCache([$node2->id()]);
+    $node2 = $this->nodeStorage->load($node2->id());
+    $this->assertFalse($node2->isPublished(), 'The orange node was unpublished by the API test module.');
+    $this->assertTrue(stristr($node2->title->value, 'unpublishing processed by API test module'), 'The orange node was processed by the API test module.');
+    $this->assertFalse($node2->unpublish_on->value, 'The orange node no longer has an unpublish-on date.');
+
+    // Check the yellow node.
+    $this->nodeStorage->resetCache([$node3->id()]);
+    $node3 = $this->nodeStorage->load($node3->id());
+    $this->assertTrue($node3->isPublished(), 'The yellow node was published by the API test module.');
+    $this->assertTrue(stristr($node3->title->value, 'publishing processed by API test module'), 'The yellow node was processed by the API test module.');
+    $this->assertFalse($node3->publish_on->value, 'The yellow node no longer has a publish-on date.');
+
+    // Check the blue node.
+    $this->nodeStorage->resetCache([$node4->id()]);
+    $node4 = $this->nodeStorage->load($node4->id());
+    $this->assertTrue($node4->isPublished(), 'The green node is still published.');
+    $this->assertTrue($node4->unpublish_on->value, 'The green node still has an unpublish-on date.');
+
+  }
+
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
index 3e7920f696..3c569a7be3 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerBasicTest.php
@@ -14,16 +14,16 @@ class SchedulerBasicTest extends SchedulerBrowserTestBase {
    */
   public function testPublishingAndUnpublishing() {
     // Login is required here before creating the publish_on date and time
-    // values so that date.formatter can utilise the current users timezone. The
-    // constraints receive values which have been converted using the users
-    // timezone so they need to be consistent.
+    // values so that $this->dateFormatter can utilise the current users
+    // timezone. The constraints receive values which have been converted using
+    // the users timezone so they need to be consistent.
     $this->drupalLogin($this->schedulerUser);
 
     // Create node values. Set time to one hour in the future.
     $edit = [
       'title[0][value]' => 'Publish This Node',
-      'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(time() + 3600, 'custom', 'Y-m-d'),
-      'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(time() + 3600, 'custom', 'H:i:s'),
+      'publish_on[0][value][date]' => $this->dateFormatter->format(time() + 3600, 'custom', 'Y-m-d'),
+      'publish_on[0][value][time]' => $this->dateFormatter->format(time() + 3600, 'custom', 'H:i:s'),
     ];
     $this->helpTestScheduler($edit);
 
@@ -43,7 +43,7 @@ public function testPublishingAndUnpublishing() {
    * Schedules content, runs cron and asserts status.
    */
   protected function helpTestScheduler($edit) {
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     // Verify that the node was created.
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertTrue($node, sprintf('"%s" was created sucessfully.', $edit['title[0][value]']));
@@ -63,7 +63,7 @@ protected function helpTestScheduler($edit) {
     }
 
     // Modify the scheduler field data to a time in the past, then run cron.
-    $node->$key = REQUEST_TIME - 1;
+    $node->$key = $this->requestTime - 1;
     $node->save();
     $this->cronRun();
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
index 828554a893..2ee9876e57 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerBrowserTestBase.php
@@ -54,14 +54,14 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase {
   protected $typeName;
 
   /**
-   * The node type.
+   * The node type object.
    *
    * @var \Drupal\node\Entity\NodeType
    */
   protected $nodetype;
 
   /**
-   * The node storage.
+   * The node storage object.
    *
    * @var \Drupal\Core\Entity\EntityStorageInterface
    */
@@ -74,18 +74,32 @@ abstract class SchedulerBrowserTestBase extends BrowserTestBase {
    */
   protected $database;
 
+  /**
+   * The request time stored as interger for direct re-use in many tests.
+   *
+   * @var int
+   */
+  protected $requestTime;
+
+  /**
+   * The date formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
+   */
+  protected $dateFormatter;
+
   /**
    * {@inheritdoc}
    */
   public function setUp() {
     parent::setUp();
 
-    // Create a 'Basic Page' content type, with 'page' as the identifier.
-    // The test files should use $this->type and $this->typeName and not use
+    // Create a test content type with id 'testpage' and name 'Test Page'.
+    // The tests should use $this->type and $this->typeName and not use
     // $this->nodetype->get('type') or $this->nodetype->get('name'), nor have
-    // the hard-coded strings 'page' and 'Basic page'.
-    $this->type = 'page';
-    $this->typeName = 'Basic page';
+    // the hard-coded strings 'testpage' or 'Test Page'.
+    $this->type = 'testpage';
+    $this->typeName = 'Test Page';
     /** @var NodeTypeInterface $nodetype */
     $this->nodetype = $this->drupalCreateContentType([
       'type' => $this->type,
@@ -99,17 +113,17 @@ public function setUp() {
 
     // Define nodeStorage for use in many tests.
     /** @var EntityStorageInterface $nodeStorage */
-    $this->nodeStorage = $this->container->get('entity.manager')->getStorage('node');
+    $this->nodeStorage = $this->container->get('entity_type.manager')->getStorage('node');
 
     // Create an administrator user having the main admin permissions, full
-    // rights on the 'page' content type and all of the Scheduler permissions.
+    // rights on the test content type and all of the Scheduler permissions.
     // 'access site reports' is required for admin/reports/dblog.
     // 'administer site configuration' is required for admin/reports/status.
     $this->adminUser = $this->drupalCreateUser([
-      'administer nodes',
       'access content',
       'access content overview',
       'access site reports',
+      'administer nodes',
       'administer site configuration',
       'create ' . $this->type . ' content',
       'edit own ' . $this->type . ' content',
@@ -134,6 +148,12 @@ public function setUp() {
     // Store the database connection for re-use in the actual tests.
     $this->database = $this->container->get('database');
 
+    // Determine the request time and save for re-use in the actual tests.
+    $this->requestTime = $this->container->get('datetime.time')->getRequestTime();
+
+    // Store the core dateFormatter service for re-use in the actual tests.
+    $this->dateFormatter = $this->container->get('date.formatter');
+
   }
 
   /**
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
index a0d0a22de9..a90807e8bb 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDefaultTimeTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
+use DateTime;
+use DateInterval;
+
 /**
  * Tests the default time functionality.
  *
@@ -15,23 +18,25 @@ class SchedulerDefaultTimeTest extends SchedulerBrowserTestBase {
   public function testDefaultTime() {
     $this->drupalLogin($this->schedulerUser);
     $config = $this->config('scheduler.settings');
-    $date_formatter = \Drupal::service('date.formatter');
 
-    // For this test we use an offset of 6 hours 30 minutes (23400 seconds).
-    $seconds = 23400;
+    // For this test we use a default time of 6:30am.
+    $default_time = '06:30:00';
+    $config->set('default_time', $default_time)->save();
+
+    // Create DateTime objects to hold the two scheduling dates. This is better
+    // than using raw unix timestamps because it caters for daylight-saving
+    // shifts properly.
+    // @see https://www.drupal.org/project/scheduler/issues/2957490
+    $publish_time = new DateTime();
+    $publish_time->add(new DateInterval('P1D'))->setTime(6, 30);
 
-    // If the test happens to be run at a time when '+1 day' puts the calculated
-    // publishing date into a different daylight-saving period then formatted
-    // time can be an hour different. To avoid these failures we use a fixed
-    // string when asserting the message and looking for field values.
-    // @see https://www.drupal.org/node/2809627
-    $seconds_formatted = '06:30:00';
-    $config->set('default_time', $seconds_formatted)->save();
+    $unpublish_time = new DateTime();
+    $unpublish_time->add(new DateInterval('P2D'))->setTime(6, 30);
 
-    // We cannot easily test the exact validation messages as they contain the
-    // REQUEST_TIME, which can be one or more seconds in the past. Best we can
+    // We cannot easily test the full validation message as they contain the
+    // current time which can be one or two seconds in the past. The best we can
     // do is check the fixed part of the message as it is when passed to t() in
-    // Datetime::validateDatetime. This will only work in English.
+    // Datetime::validateDatetime. Tests only needs to work in English anyway.
     $publish_validation_message = 'The Publish on date is invalid.';
     $unpublish_validation_message = 'The Unpublish on date is invalid.';
 
@@ -41,11 +46,11 @@ public function testDefaultTime() {
     // Test that entering a time is required.
     $edit = [
       'title[0][value]' => 'No time ' . $this->randomString(15),
-      'publish_on[0][value][date]' => $date_formatter->format(strtotime('+1 day', REQUEST_TIME), 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][date]' => $date_formatter->format(strtotime('+2 day', REQUEST_TIME), 'custom', 'Y-m-d'),
+      'publish_on[0][value][date]' => $publish_time->format('Y-m-d'),
+      'unpublish_on[0][value][date]' => $unpublish_time->format('Y-m-d'),
     ];
     // Create a node and check that the expected error messages are shown.
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $this->assertSession()->pageTextContains($publish_validation_message, 'By default it is required to enter a time when scheduling content for publication.');
     $this->assertSession()->pageTextContains($unpublish_validation_message, 'By default it is required to enter a time when scheduling content for unpublication.');
 
@@ -53,26 +58,27 @@ public function testDefaultTime() {
     $config->set('allow_date_only', TRUE)->save();
 
     // Create a node and check that the expected error messages are not shown.
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $this->assertSession()->pageTextNotContains($publish_validation_message, 'If the default time option is enabled the user can skip the time when scheduling content for publication.');
     $this->assertSession()->pageTextNotContains($unpublish_validation_message, 'If the default time option is enabled the user can skip the time when scheduling content for unpublication.');
 
+    // Get the pattern of the 'long' default date format.
+    $date_format_storage = $this->container->get('entity_type.manager')->getStorage('date_format');
+    $long_pattern = $date_format_storage->load('long')->getPattern();
+
     // Check that the scheduled information is shown after saving.
-    $publish_time = strtotime('+1 day midnight', REQUEST_TIME) + $seconds;
-    $unpublish_time = strtotime('+2 day midnight', REQUEST_TIME) + $seconds;
-    $args = ['@publish_time' => $date_formatter->format($publish_time, 'long')];
-    $this->assertRaw(t('This post is unpublished and will be published @publish_time.', $args), 'The user is informed that the content will be published on the requested date, on the default time.');
+    $this->assertText(sprintf('This post is unpublished and will be published %s', $publish_time->format($long_pattern)), 'The user is informed that the content will be published on the requested date, on the default time.');
 
-    // Protect in case the node was not created.
+    // Protect this section in case the node was not created.
     if ($node = $this->drupalGetNodeByTitle($edit['title[0][value]'])) {
       // Check that the correct scheduled dates are stored in the node.
-      $this->assertEqual($node->publish_on->value, $publish_time, 'The node publish_on value is stored correctly.');
-      $this->assertEqual($node->unpublish_on->value, $unpublish_time, 'The node unpublish_on value is stored correctly.');
+      $this->assertEquals($publish_time->getTimestamp(), (int) $node->publish_on->value, 'The node publish_on value is stored correctly.');
+      $this->assertEquals($unpublish_time->getTimestamp(), (int) $node->unpublish_on->value, 'The node unpublish_on value is stored correctly.');
 
       // Check that the default time has been added to the form on edit.
       $this->drupalGet('node/' . $node->id() . '/edit');
-      $this->assertFieldByName('publish_on[0][value][time]', $seconds_formatted, 'The default time offset has been added to the date field when scheduling content for publication.');
-      $this->assertFieldByName('unpublish_on[0][value][time]', $seconds_formatted, 'The default time offset has been added to the date field when scheduling content for unpublication.');
+      $this->assertFieldByName('publish_on[0][value][time]', $default_time, 'The default time offset has been added to the date field when scheduling content for publication.');
+      $this->assertFieldByName('unpublish_on[0][value][time]', $default_time, 'The default time offset has been added to the date field when scheduling content for unpublication.');
 
     }
     else {
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
index 1ca309ac0e..46356e9415 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDeleteNodeTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\node\Entity\NodeType;
-
 /**
  * Tests deletion of nodes enabled for Scheduler.
  *
@@ -52,14 +50,14 @@ public function testDeleteNodeWhenSchedulingIsRequired() {
     // normally made hidden from browsers but will be in the page source.
     // It is also good when testing for the absense of something to also test
     // for the presence of text, hence the second assertion for each check.
-    $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a published node with no scheduling information.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a published node with no scheduling information.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete a published node with no scheduling information.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a published node with no scheduling information.');
 
     // Do the same test for the unpublished node.
     $this->drupalGet('node/' . $unpublished_node->id() . '/edit');
     $this->clickLink('Delete');
-    $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete an unpublished node with no scheduling information.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete an unpublished node with no scheduling information.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete an unpublished node with no scheduling information.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete an unpublished node with no scheduling information.');
   }
 
   /**
@@ -89,14 +87,14 @@ public function testDeleteNodeWithPastDates() {
     // Attempt to delete the published node and check for no validation error.
     $this->drupalGet('node/' . $published_node->id() . '/edit');
     $this->clickLink('Delete');
-    $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a node with an unpublish date in the past.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a node with an unpublish date in the past.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete a node with an unpublish date in the past.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a node with an unpublish date in the past.');
 
     // Attempt to delete the unpublished node and check for no validation error.
     $this->drupalGet('node/' . $unpublished_node->id() . '/edit');
     $this->clickLink('Delete');
-    $this->assertNoRaw(t('Error message'), 'No error messages are shown when trying to delete a node with a publish date in the past.');
-    $this->assertRaw(t('Are you sure you want to delete the content'), 'The deletion warning message is shown immediately when trying to delete a node with a publish date in the past.');
+    $this->assertNoText('Error message', 'No error messages are shown when trying to delete a node with a publish date in the past.');
+    $this->assertText('Are you sure you want to delete the content', 'The deletion warning message is shown immediately when trying to delete a node with a publish date in the past.');
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php
index f1f5fc1a1c..3b0f7228ae 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerDevelGenerateTest.php
@@ -6,6 +6,10 @@
  * Tests the Scheduler interaction with Devel Generate module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Devel no longer uses the deprecated
+ * $published parameter for setPublished(), and does not use functions
+ * drupal_set_message(), format_date() and db_query_range().
  */
 class SchedulerDevelGenerateTest extends SchedulerBrowserTestBase {
 
@@ -57,19 +61,19 @@ public function setUp() {
    */
   protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled, $time_range = NULL) {
     // Check that the expected number of nodes have been created.
-    $count = \Drupal::entityQuery('node')
+    $count = $this->nodeStorage->getQuery()
       ->condition('type', $type)
       ->count()
       ->execute();
-    $this->assertEqual($count, $num_nodes, sprintf('The expected number of %s is %s, found %s', $type, $num_nodes, $count));
+    $this->assertEquals($num_nodes, $count, sprintf('The expected number of %s is %s, found %s', $type, $num_nodes, $count));
 
     // Check that the expected number of nodes have been scheduled.
-    $count = \Drupal::entityQuery('node')
+    $count = $this->nodeStorage->getQuery()
       ->condition('type', $type)
       ->exists($field)
       ->count()
       ->execute();
-    $this->assertEqual($count, $num_scheduled, sprintf('The expected number of scheduled %s is %s, found %s', $field, $num_scheduled, $count));
+    $this->assertEquals($num_scheduled, $count, sprintf('The expected number of scheduled %s is %s, found %s', $field, $num_scheduled, $count));
 
     if (isset($time_range)) {
       // Define the minimum and maximum times that we expect the scheduled dates
@@ -78,10 +82,10 @@ protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled
       // slowly creep forward during sucessive calls. Tests can fail incorrectly
       // for this reason, hence the best approximation is to use time() when
       // calculating the upper end of the range.
-      $min = REQUEST_TIME - $time_range;
+      $min = $this->requestTime - $time_range;
       $max = time() + $time_range;
 
-      $query = \Drupal::entityQueryAggregate('node');
+      $query = $this->nodeStorage->getAggregateQuery();
       $result = $query
         ->condition('type', $type)
         ->aggregate($field, 'min')
@@ -91,8 +95,8 @@ protected function countScheduledNodes($type, $field, $num_nodes, $num_scheduled
       $max_found = $result[0]["{$field}_max"];
 
       // Assert that the found values are within the expcted range.
-      $this->assertGreaterThanOrEqual($min, $min_found, sprintf('The minimum value for %s is %s, smaller than the expected %s', $field, format_date($min_found, 'custom', 'j M, H:i:s'), format_date($min, 'custom', 'j M, H:i:s')));
-      $this->assertLessThanOrEqual($max, $max_found, sprintf('The maximum value for %s is %s which is larger than expected %s', $field, format_date($max_found, 'custom', 'j M, H:i:s'), format_date($max, 'custom', 'j M, H:i:s')));
+      $this->assertGreaterThanOrEqual($min, $min_found, sprintf('The minimum value for %s is %s, smaller than the expected %s', $field, $this->dateFormatter->format($min_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($min, 'custom', 'j M, H:i:s')));
+      $this->assertLessThanOrEqual($max, $max_found, sprintf('The maximum value for %s is %s which is larger than expected %s', $field, $this->dateFormatter->format($max_found, 'custom', 'j M, H:i:s'), $this->dateFormatter->format($max, 'custom', 'j M, H:i:s')));
     }
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
index ceba0168f1..3781fdad6f 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerFieldsDisplayTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\node\Entity\NodeType;
-
 /**
  * Tests the display of the date entry fields (vertical tab, fieldset).
  *
@@ -18,19 +16,38 @@ class SchedulerFieldsDisplayTest extends SchedulerBrowserTestBase {
    */
   public static $modules = ['field_ui'];
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Create a custom user with admin permissions but also permission to use
+    // the field_ui module 'node form display' tab.
+    $this->adminUser2 = $this->drupalCreateUser([
+      'access content',
+      'administer content types',
+      'administer node form display',
+      'create ' . $this->type . ' content',
+      'schedule publishing of nodes',
+    ]);
+  }
+
   /**
    * Tests date input is displayed as vertical tab or an expandable fieldset.
+   *
+   * This test covers scheduler_form_node_form_alter().
    */
-  public function testFieldsDisplay() {
+  public function testVerticalTabOrFieldset() {
     $this->drupalLogin($this->adminUser);
 
     // Check that the dates are shown in a vertical tab by default.
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'By default the scheduler dates are shown in a vertical tab.');
 
     // Check that the dates are shown as a fieldset when configured to do so.
     $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'fieldset')->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertFalse($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are not shown in a vertical tab when they are configured to show as a fieldset.');
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are shown in a fieldset when they are configured to show as a fieldset.');
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and not(@open = "open")]'), 'The scheduler dates fieldset is collapsed by default.');
@@ -38,19 +55,19 @@ public function testFieldsDisplay() {
     // Check that the fieldset is expanded if either of the scheduling dates
     // are required.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', TRUE)->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the publish-on date is required.');
 
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'unpublish_required', TRUE)->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the unpublish-on date is required.');
 
     // Check that the fieldset is expanded if the 'always' option is set.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE)
       ->setThirdPartySetting('scheduler', 'expand_fieldset', 'always')->save();
-    $this->drupalGet('node/add/page');
+    $this->drupalGet('node/add/' . $this->type);
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when the option to always expand is turned on.');
 
     // Check that the fieldset is expanded if the node already has a publish-on
@@ -74,6 +91,12 @@ public function testFieldsDisplay() {
     $node = $this->drupalCreateNode($options);
     $this->drupalGet('node/' . $node->id() . '/edit');
     $this->assertTrue($this->xpath('//details[@id = "edit-scheduler-settings" and @open = "open"]'), 'The scheduler dates are shown in an expanded fieldset when an unpublish-on date already exists.');
+
+    // Check that the display reverts to a vertical tab again when specifically
+    // configured to do so.
+    $this->nodetype->setThirdPartySetting('scheduler', 'fields_display_mode', 'vertical_tab')->save();
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler dates are shown in a vertical tab when that option is set.');
   }
 
   /**
@@ -82,14 +105,7 @@ public function testFieldsDisplay() {
    * This test covers scheduler_entity_extra_field_info().
    */
   public function testManageFormDisplay() {
-
-    // Create a custom administrator user with permissions to use the field_ui
-    // module 'node form display' tab.
-    $this->adminUser = $this->drupalCreateUser([
-      'administer content types',
-      'administer node form display',
-    ]);
-    $this->drupalLogin($this->adminUser);
+    $this->drupalLogin($this->adminUser2);
 
     // Check that the weight input field is displayed when the content type is
     // enabled for scheduling. This field still exists even with tabledrag on.
@@ -104,4 +120,54 @@ public function testManageFormDisplay() {
     $this->assertNoFieldById('edit-fields-scheduler-settings-weight', NULL, 'The scheduler settings row is not shown when the content type is not enabled for scheduling.');
   }
 
+  /**
+   * Tests the edit form when scheduler fields have been disabled.
+   *
+   * This test covers scheduler_form_node_form_alter().
+   */
+  public function testDisabledFields() {
+    $this->drupalLogin($this->adminUser2);
+
+    // 1. Set the publish_on field to 'hidden' in the node edit form.
+    $edit = [
+      'fields[publish_on][region]' => 'hidden',
+    ];
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save');
+
+    // Check that a scheduler vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler input is a vertical tab.');
+    // Check the publish_on field is not shown, but the unpublish_on field is.
+    $this->assertNoFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is not shown - 1');
+    $this->assertFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is shown - 1');
+
+    // 2. Set publish_on to be displayed but hide the unpublish_on field.
+    $edit = [
+      'fields[publish_on][region]' => 'content',
+      'fields[unpublish_on][region]' => 'hidden',
+    ];
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save');
+
+    // Check that a scheduler vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertTrue($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler input is a vertical tab.');
+    // Check the publish_on field is not shown, but the unpublish_on field is.
+    $this->assertFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is shown - 2');
+    $this->assertNoFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is not shown - 2');
+
+    // 3. Set both fields to be hidden.
+    $edit = [
+      'fields[publish_on][region]' => 'hidden',
+      'fields[unpublish_on][region]' => 'hidden',
+    ];
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/form-display', $edit, 'Save');
+
+    // Check that no vertical tab is displayed.
+    $this->drupalGet('node/add/' . $this->type);
+    $this->assertFalse($this->xpath('//div[contains(@class, "form-type-vertical-tabs")]//details[@id = "edit-scheduler-settings"]'), 'The scheduler vertical tab is not shown.');
+    // Check the neither field is displayed.
+    $this->assertNoFieldByName('publish_on[0][value][date]', NULL, 'The Publish-on field is not shown - 3');
+    $this->assertNoFieldByName('unpublish_on[0][value][date]', NULL, 'The Unpublish-on field is not shown - 3');
+  }
+
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
index 1bad0e0e43..e2878457f1 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerLightweightCronTest.php
@@ -5,7 +5,7 @@
 use Drupal\Core\Url;
 
 /**
- * Tests the Scheduler lighweight cron urls and admin form.
+ * Tests the Scheduler lightweight cron urls and admin form.
  *
  * @group scheduler
  */
@@ -56,7 +56,7 @@ public function testLightweightCronSettingsForm() {
     $this->assertTrue(strlen($key) == 20, 'Default lightweight cron key string length is 20');
 
     // Check that a new random key can be generated.
-    $this->drupalPostForm($this->routeCronForm, [], t('Generate new random key'));
+    $this->drupalPostForm($this->routeCronForm, [], 'Generate new random key');
     $new_key_xpath = $this->xpath('//input[@id="edit-lightweight-access-key"]/@value');
     $new_key = $new_key_xpath[0]->getText();
     $this->assertTrue(!empty($new_key), 'Lightweight cron key field is not empty after generating new key');
@@ -64,11 +64,11 @@ public function testLightweightCronSettingsForm() {
     $this->assertNotEqual($key, $new_key, 'Lightweight cron key has changed.');
 
     // Check that the 'run lightweight cron' button works.
-    $this->drupalPostForm($this->routeCronForm, [], t("Run Scheduler's lightweight cron now"));
+    $this->drupalPostForm($this->routeCronForm, [], "Run Scheduler's lightweight cron now");
     $this->assertText('Lightweight cron run completed.', 'Lightweight cron runs OK manually');
 
     // Check that the form cannot be saved if the cron key is blank.
-    $this->drupalPostForm($this->routeCronForm, ['lightweight_access_key' => ''], t('Save configuration'));
+    $this->drupalPostForm($this->routeCronForm, ['lightweight_access_key' => ''], 'Save configuration');
     $this->assertText('Lightweight cron access key field is required.', 'Saving configuration with a blank cron key throws the expected validation message');
     $this->assertNoText('The configuration options have been saved.', 'Saving configuration with a blank cron key is not possible');
   }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
index 859f0c558b..f918d2a487 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerMetaInformationTest.php
@@ -34,10 +34,10 @@ public function testMetaInformation() {
     // Set a scheduler unpublish date on the node.
     $unpublish_date = strtotime('+1 day');
     $edit = [
-      'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format($unpublish_date, 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format($unpublish_date, 'custom', 'H:i:s'),
+      'unpublish_on[0][value][date]' => $this->dateFormatter->format($unpublish_date, 'custom', 'Y-m-d'),
+      'unpublish_on[0][value][time]' => $this->dateFormatter->format($unpublish_date, 'custom', 'H:i:s'),
     ];
-    $this->drupalPostForm('node/' . $published_node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $published_node->id() . '/edit', $edit, 'Save');
 
     // The node page should now have an X-Robots-Tag header with an
     // unavailable_after-directive and RFC850 date- and time-value.
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
index 7823bc79f5..e434e9f613 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerMultilingualTest.php
@@ -89,19 +89,19 @@ private function checkStatus($nid, $description, array $st) {
     $this->nodeStorage->resetCache([$nid]);
     $node = $this->nodeStorage->load($nid);
 
-    foreach ($st as $key => $status) {
+    foreach ($st as $key => $expected_status) {
       if ($key == 0) {
         // Key 0 is the original, so we just check $node.
-        $this->assertEqual($node->isPublished(), $status,
-          sprintf('%s: The original content (%s) is %s', $description, $this->languages[$key]['name'], ($status ? 'published' : 'unpublished')));
+        $this->assertEquals($expected_status, $node->isPublished(),
+          sprintf('%s: The original content (%s) is %s', $description, $this->languages[$key]['name'], ($expected_status ? 'published' : 'unpublished')));
       }
       else {
         // Key > 0 are the translations, which we get using the Content
         // Translation Manager getTranslationMetadata() function.
         $trans = $this->ctm->getTranslationMetadata($node->getTranslation($this->languages[$key]['code']));
         $trans = $node->getTranslation($this->languages[$key]['code']);
-        $this->assertEqual($trans->isPublished(), $status,
-          sprintf('%s: Translation %d (%s) is %s', $description, $key, $this->languages[$key]['name'], ($status ? 'published' : 'unpublished')));
+        $this->assertEquals($expected_status, $trans->isPublished(),
+          sprintf('%s: Translation %d (%s) is %s', $description, $key, $this->languages[$key]['name'], ($expected_status ? 'published' : 'unpublished')));
       }
     }
   }
@@ -117,9 +117,9 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     // parameters passed in.
     $this->drupalGet('admin/config/regional/content-language');
     $settings = [
-      'edit-settings-node-page-settings-language-language-alterable' => TRUE,
-      'edit-settings-node-page-fields-publish-on' => $publish_on_translatable,
-      'edit-settings-node-page-fields-unpublish-on' => $unpublish_on_translatable,
+      'edit-settings-node-' . $this->type . '-settings-language-language-alterable' => TRUE,
+      'edit-settings-node-' . $this->type . '-fields-publish-on' => $publish_on_translatable,
+      'edit-settings-node-' . $this->type . '-fields-unpublish-on' => $unpublish_on_translatable,
     ];
     // The submit shows the updated values, so no need for second get.
     $this->submitForm($settings, 'Save configuration');
@@ -160,8 +160,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     $this->drupalGet('node/' . $node->id() . '/translations/add/' . $this->languages[0]['code'] . '/' . $this->languages[2]['code']);
     $edit = [
       'title[0][value]' => $this->languages[2]['name'] . '(2) - Publish in the future',
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', REQUEST_TIME)),
+      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', $this->requestTime)),
+      'publish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', $this->requestTime)),
     ];
     $this->submitForm($edit, $save_button_text);
 
@@ -169,8 +169,8 @@ public function testPublishingTranslations($publish_on_translatable, $unpublish_
     $this->drupalGet('node/' . $node->id() . '/translations/add/' . $this->languages[0]['code'] . '/' . $this->languages[3]['code']);
     $edit = [
       'title[0][value]' => $this->languages[3]['name'] . '(3) - Publish in the past',
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', REQUEST_TIME)),
+      'publish_on[0][value][date]' => date('Y-m-d', strtotime('-2 day', $this->requestTime)),
+      'publish_on[0][value][time]' => date('H:i:s', strtotime('-2 day', $this->requestTime)),
     ];
     $this->submitForm($edit, $save_button_text);
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
index 7f5f25f3da..f9a56f58b0 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerNodeAccessTest.php
@@ -57,7 +57,7 @@ public function testNodeAccess() {
         'type' => $this->type,
         'status' => $data['status'],
         'title' => 'Test node to be ' . $data['after'],
-        $field => REQUEST_TIME + 1,
+        $field => $this->requestTime + 1,
       ];
       $node = $this->drupalCreateNode($settings);
       $this->drupalGet('node/' . $node->id());
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
index 79b162a38d..d4988fa45e 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerNonEnabledTypeTest.php
@@ -19,15 +19,13 @@ public function setUp() {
     $this->contentName = 'not_for_scheduler';
     $this->contentType = $this->drupalCreateContentType(['type' => $this->contentName, 'name' => 'Not for Scheduler']);
 
-    // Create an administrator user.
-    $this->adminUser = $this->drupalCreateUser([
+    // Create an admin user with permission to create the new content type.
+    $this->adminUser2 = $this->drupalCreateUser([
       'access content',
+      'administer nodes',
       'administer scheduler',
       'create ' . $this->contentName . ' content',
-      'edit own ' . $this->contentName . ' content',
-      'delete own ' . $this->contentName . ' content',
       'view own unpublished content',
-      'administer nodes',
       'schedule publishing of nodes',
       'access site reports',
     ]);
@@ -80,7 +78,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl
       'title' => $title,
       'status' => 0,
       'type' => $this->contentName,
-      'publish_on' => REQUEST_TIME - 2,
+      'publish_on' => $this->requestTime - 2,
     ];
     $node = $this->drupalCreateNode($edit);
 
@@ -107,7 +105,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl
       'title' => $title,
       'status' => 1,
       'type' => $this->contentName,
-      'unpublish_on' => REQUEST_TIME - 1,
+      'unpublish_on' => $this->requestTime - 1,
     ];
     $node = $this->drupalCreateNode($edit);
 
@@ -138,7 +136,7 @@ protected function checkNonEnabledTypes($publishing_enabled, $unpublishing_enabl
    */
   public function testNonEnabledNodeType() {
     // Log in.
-    $this->drupalLogin($this->adminUser);
+    $this->drupalLogin($this->adminUser2);
 
     // 1. By default check that the scheduler date fields are not displayed.
     $this->checkNonEnabledTypes(FALSE, FALSE, 1);
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
index e4c83058fd..b5c8c5e9b7 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerPastDatesTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Html;
 
 /**
  * Tests the options and processing when dates are entered in the past.
@@ -18,70 +18,105 @@ public function testSchedulerPastDates() {
     // Log in.
     $this->drupalLogin($this->schedulerUser);
 
-    // Ensure that neither of the scheduling dates are set to be required.
-    $this->nodetype->setThirdPartySetting('scheduler', 'publish_required', FALSE)
-      ->setThirdPartySetting('scheduler', 'unpublish_required', FALSE)->save();
-
     // Create an unpublished page node.
+    /** @var NodeInterface $node */
     $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]);
+    $created_time = $node->getCreatedTime();
 
     // Test the default behavior: an error message should be shown when the user
     // enters a publication date that is in the past.
     $edit = [
       'title[0][value]' => 'Past ' . $this->randomString(10),
-      'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(strtotime('-1 day', REQUEST_TIME), 'custom', 'Y-m-d'),
-      'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(strtotime('-1 day', REQUEST_TIME), 'custom', 'H:i:s'),
+      'publish_on[0][value][date]' => $this->dateFormatter->format(strtotime('-1 day', $this->requestTime), 'custom', 'Y-m-d'),
+      'publish_on[0][value][time]' => $this->dateFormatter->format(strtotime('-1 day', $this->requestTime), 'custom', 'H:i:s'),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $this->assertRaw(t("The 'publish on' date must be in the future"), 'An error message is shown by default when the publication date is in the past.');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertText("The 'publish on' date must be in the future", 'An error message is shown by default when the publication date is in the past.');
 
     // Test the 'error' behavior explicitly.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'error')->save();
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $this->assertRaw(t("The 'publish on' date must be in the future"), 'An error message is shown when the publication date is in the past and the "error" behavior is chosen.');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertText("The 'publish on' date must be in the future", 'An error message is shown when the publication date is in the past and the "error" behavior is chosen.');
 
     // Test the 'publish' behavior: the node should be published immediately.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'publish')->save();
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $this->assertNoText(t("The 'publish on' date must be in the future"), 'No error message is shown when the publication date is in the past and the "publish" behavior is chosen.');
-    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, SafeMarkup::checkPlain($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "publish" behavior is chosen.');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertNoText("The 'publish on' date must be in the future", 'No error message is shown when the publication date is in the past and the "publish" behavior is chosen.');
+    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, Html::escape($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "publish" behavior is chosen.');
 
-    // Reload the changed node and check that it is published.
+    // Reload the node.
     $this->nodeStorage->resetCache([$node->id()]);
-
-    /** @var NodeInterface $node */
     $node = $this->nodeStorage->load($node->id());
+
+    // Check that the node is published and has the expected timestamps.
     $this->assertTrue($node->isPublished(), 'The node has been published immediately when the publication date is in the past and the "publish" behavior is chosen.');
     $this->assertNull($node->publish_on->value, 'The node publish_on date has been removed after publishing when the "publish" behavior is chosen.');
+    $this->assertEquals($node->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the node has been updated to the publish_on time when published immediately.');
+    $this->assertEquals($node->getCreatedTime(), $created_time, 'The created time of the node has not been changed when the "publish" behavior is chosen.');
 
     // Test the 'schedule' behavior: the node should be unpublished and become
-    // published on the next cron run.
+    // published on the next cron run. Use a new unpublished node.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $this->assertNoText(t("The 'publish on' date must be in the future"), 'No error message is shown when the publication date is in the past and the "schedule" behavior is chosen.');
-    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, SafeMarkup::checkPlain($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "schedule" behavior is chosen.');
+    $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]);
+    $created_time = $node->getCreatedTime();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->assertNoText("The 'publish on' date must be in the future", 'No error message is shown when the publication date is in the past and the "schedule" behavior is chosen.');
+    $this->assertText(sprintf('%s %s has been updated.', $this->typeName, Html::escape($edit['title[0][value]'])), 'The node is saved successfully when the publication date is in the past and the "schedule" behavior is chosen.');
     $this->assertText('will be published', 'The node is scheduled to be published when the publication date is in the past and the "schedule" behavior is chosen.');
 
-    // Reload the node and check that it is unpublished but scheduled correctly.
+    // Reload the node.
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
+
+    // Check that the node is unpublished but scheduled correctly.
     $this->assertFalse($node->isPublished(), 'The node has been unpublished when the publication date is in the past and the "schedule" behavior is chosen.');
-    $this->assertEqual($node->publish_on->value, strtotime('-1 day', REQUEST_TIME), 'The node has the correct publish_on date stored.');
+    $this->assertEquals(strtotime('-1 day', $this->requestTime), (int) $node->publish_on->value, 'The node has the correct publish_on date stored.');
 
     // Simulate a cron run and check that the node is published.
     scheduler_cron();
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     $this->assertTrue($node->isPublished(), 'The node with publication date in the past and the "schedule" behavior has now been published by cron.');
+    $this->assertEquals($node->getChangedTime(), strtotime('-1 day', $this->requestTime), 'The changed time of the node has been updated to the publish_on time when published via cron.');
+    $this->assertEquals($node->getCreatedTime(), $created_time, 'The created time of the node has not been changed when the "schedule" behavior is chosen.');
+
+    // Test the option to alter the creation time if the publishing time is
+    // earlier than the node created time.
+    $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date_created', TRUE)->save();
+
+    $past_date_options = [
+      'publish' => 'publish',
+      'schedule' => 'schedule',
+    ];
+
+    foreach ($past_date_options as $key => $option) {
+      $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', $key)->save();
+
+      // Create a new node, edit and save.
+      $node = $this->drupalCreateNode(['type' => $this->type, 'status' => FALSE]);
+      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+
+      if ($option == 'schedule') {
+        scheduler_cron();
+      }
+
+      // Reload the node.
+      $this->nodeStorage->resetCache([$node->id()]);
+      $node = $this->nodeStorage->load($node->id());
+
+      // Check that the created time has been altered to match publishing time.
+      $this->assertEquals($node->getCreatedTime(), strtotime('-1 day', $this->requestTime), sprintf('The created time of the node has not been changed when the %s option is chosen.', $option));
+
+    }
 
     // Check that an Unpublish date in the past fails validation.
     $edit = [
       'title[0][value]' => 'Unpublish in the past ' . $this->randomString(10),
-      'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME - 3600, 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME - 3600, 'custom', 'H:i:s'),
+      'unpublish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime - 3600, 'custom', 'Y-m-d'),
+      'unpublish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime - 3600, 'custom', 'H:i:s'),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
-    $this->assertRaw(t("The 'unpublish on' date must be in the future"), 'An error message is shown when the unpublish date is in the past.');
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
+    $this->assertText("The 'unpublish on' date must be in the future", 'An error message is shown when the unpublish date is in the past.');
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
index c0632580ba..2dbb46bf39 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerPermissionsTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Html;
 
 /**
  * Tests the permissions of the Scheduler module.
@@ -14,7 +14,7 @@ class SchedulerPermissionsTest extends SchedulerBrowserTestBase {
   /**
    * Tests that users without permission do not see the scheduler date fields.
    */
-  public function testUserPermissions() {
+  public function testUserPermissionsAdd() {
     // Create a user who can add the content type but who does not have the
     // permission to use the scheduler functionality.
     $this->webUser = $this->drupalCreateUser([
@@ -51,7 +51,7 @@ public function testUserPermissions() {
       $edit['status[value]'] = TRUE;
     }
     $this->drupalPostForm('node/add/' . $this->type, $edit, $checkbox ? 'Save' : 'Save and publish');
-    $this->assertText(sprintf('%s %s has been created.', $this->typeName, SafeMarkup::checkPlain($title)), 'A node can be created and published when the user does not have scheduler permissions.');
+    $this->assertText(sprintf('%s %s has been created.', $this->typeName, Html::escape($title)), 'A node can be created and published when the user does not have scheduler permissions.');
     $this->assertTrue($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is published');
 
     // Check that a new node can be saved as unpublished.
@@ -61,7 +61,7 @@ public function testUserPermissions() {
       $edit['status[value]'] = FALSE;
     }
     $this->drupalPostForm('node/add/' . $this->type, $edit, $checkbox ? 'Save' : 'Save as unpublished');
-    $this->assertText(sprintf('%s %s has been created.', $this->typeName, SafeMarkup::checkPlain($title)), 'A node can be created and saved as unpublished when the user does not have scheduler permissions.');
+    $this->assertText(sprintf('%s %s has been created.', $this->typeName, Html::escape($title)), 'A node can be created and saved as unpublished when the user does not have scheduler permissions.');
     $this->assertFalse($this->drupalGetNodeByTitle($title)->isPublished(), 'The new node is unpublished');
 
     // Set publishing and unpublishing to required, to make it a stronger test.
@@ -76,4 +76,66 @@ public function testUserPermissions() {
     // permission"
   }
 
+  /**
+   * Tests that users without permission can edit existing scheduled content.
+   */
+  public function testUserPermissionsEdit() {
+    // Create a user who can add the content type but who does not have the
+    // permission to use the scheduler functionality.
+    $this->webUser = $this->drupalCreateUser([
+      'access content',
+      'administer nodes',
+      'create ' . $this->type . ' content',
+      'edit own ' . $this->type . ' content',
+      'delete own ' . $this->type . ' content',
+      'view own unpublished content',
+    ]);
+    $this->drupalLogin($this->webUser);
+
+    $publish_time = strtotime('+ 6 hours', $this->requestTime);
+    $unpublish_time = strtotime('+ 10 hours', $this->requestTime);
+
+    // Create nodes with publish_on and unpublish_on dates.
+    $unpublished_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => FALSE,
+      'publish_on' => $publish_time,
+    ]);
+    $published_node = $this->drupalCreateNode([
+      'type' => $this->type,
+      'status' => TRUE,
+      'unpublish_on' => $unpublish_time,
+    ]);
+
+    // Verify that the publish_on date is stored as expected before editing.
+    $this->assertEqual($unpublished_node->publish_on->value, $publish_time, 'The publish_on value is stored correctly before edit.');
+
+    // Edit the unpublished node and save.
+    $title = 'For Publishing ' . $this->randomString(10);
+    $this->drupalPostForm('node/' . $unpublished_node->id() . '/edit', ['title[0][value]' => $title], 'Save');
+
+    // Check the updated title, to verify that edit and save was sucessful.
+    $unpublished_node = $this->nodeStorage->load($unpublished_node->id());
+    $this->assertEqual($unpublished_node->title->value, $title, 'The unpublished node title has been updated correctly after edit.');
+
+    // Test that the publish_on date is still stored and is unchanged.
+    $this->assertEqual($unpublished_node->publish_on->value, $publish_time, 'The node publish_on value is still stored correctly after edit.');
+
+    // Do the same for unpublishing.
+    // Verify that the unpublish_on date is stored as expected before editing.
+    $this->assertEqual($published_node->unpublish_on->value, $unpublish_time, 'The unpublish_on value is stored correctly before edit.');
+
+    // Edit the published node and save.
+    $title = 'For Unpublishing ' . $this->randomString(10);
+    $this->drupalPostForm('node/' . $published_node->id() . '/edit', ['title[0][value]' => $title], 'Save');
+
+    // Check the updated title, to verify that edit and save was sucessful.
+    $published_node = $this->nodeStorage->load($published_node->id());
+    $this->assertEqual($published_node->title->value, $title, 'The published node title has been updated correctly after edit.');
+
+    // Test that the unpublish_on date is still stored and is unchanged.
+    $this->assertEqual($published_node->unpublish_on->value, $unpublish_time, 'The node unpublish_on value is still stored correctly after edit.');
+
+  }
+
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
index 00ec4c478d..ed7a5d1d25 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRequiredTest.php
@@ -2,9 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\node\Entity\NodeType;
-use Drupal\Component\Utility\SafeMarkup;
-
 /**
  * Tests the options for scheduling dates to be required during add/edit.
  *
@@ -154,7 +151,8 @@ public function testRequiredScheduling() {
       ],
     ];
 
-    $fields = \Drupal::entityManager()->getFieldDefinitions('node', $this->type);
+    $fields = $this->container->get('entity_field.manager')
+      ->getFieldDefinitions('node', $this->type);
 
     foreach ($test_cases as $test_case) {
       // Set required (un)publishing as stipulated by the test case.
@@ -207,7 +205,7 @@ public function testRequiredScheduling() {
         'unpublish_on[0][value][time]' => '',
       ];
       // Add or edit the node.
-      $this->drupalPostForm($path, $values, t('Save'));
+      $this->drupalPostForm($path, $values, 'Save');
 
       // Check for the expected result.
       switch ($test_case['expected']) {
@@ -217,7 +215,7 @@ public function testRequiredScheduling() {
           break;
 
         case 'not required':
-          $string = sprintf('%s %s has been %s.', $this->typeName, SafeMarkup::checkPlain($title), ($test_case['operation'] == 'add' ? 'created' : 'updated'));
+          $string = sprintf('%s %s has been %s.', $this->typeName, $title, ($test_case['operation'] == 'add' ? 'created' : 'updated'));
           $this->assertText($string, $test_case['id'] . '. ' . $test_case['message']);
           break;
       }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
index 8810e8df96..a4d7711fd6 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRevisioningTest.php
@@ -26,7 +26,7 @@ class SchedulerRevisioningTest extends SchedulerBrowserTestBase {
   protected function schedule(NodeInterface $node, $action = 'publish') {
     // Simulate scheduling by setting the (un)publication date in the past and
     // running cron.
-    $node->{$action . '_on'} = strtotime('-1 day', REQUEST_TIME);
+    $node->{$action . '_on'} = strtotime('-5 hour', $this->requestTime);
     $node->save();
     scheduler_cron();
     $this->nodeStorage->resetCache([$node->id()]);
@@ -34,56 +34,51 @@ protected function schedule(NodeInterface $node, $action = 'publish') {
   }
 
   /**
-   * Check if the latest revision log message of a node matches a given string.
+   * Check if the number of revisions for a node matches a given value.
    *
    * @param int $nid
    *   The node id of the node to check.
    * @param string $value
-   *   The value with which the log message will be compared.
+   *   The value with which the number of revisions will be compared.
    * @param string $message
    *   The message to display along with the assertion.
-   * @param string $group
-   *   The type of assertion - examples are "Browser", "PHP".
    *
    * @return bool
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
-  protected function assertRevisionLogMessage($nid, $value, $message = '', $group = 'Other') {
-    // Retrieve the latest revision log message for this node.
-    $log_message = $this->database->select('node_revision', 'r')
-      ->fields('r', ['revision_log'])
+  protected function assertRevisionCount($nid, $value, $message = '') {
+    $count = \Drupal::database()->select('node_revision', 'r')
       ->condition('nid', $nid)
-      ->orderBy('vid', 'DESC')
-      ->range(0, 1)
+      ->countQuery()
       ->execute()
       ->fetchColumn();
-
-    return $this->assertEqual($log_message, $value, $message, $group);
+    return $this->assertEquals($value, (int) $count, $message);
   }
 
   /**
-   * Check if the number of revisions for a node matches a given value.
+   * Check if the latest revision log message of a node matches a given string.
    *
    * @param int $nid
    *   The node id of the node to check.
    * @param string $value
-   *   The value with which the number of revisions will be compared.
+   *   The value with which the log message will be compared.
    * @param string $message
    *   The message to display along with the assertion.
-   * @param string $group
-   *   The type of assertion - examples are "Browser", "PHP".
    *
    * @return bool
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
-  protected function assertRevisionCount($nid, $value, $message = '', $group = 'Other') {
-    $count = \Drupal::database()->select('node_revision', 'r')
+  protected function assertRevisionLogMessage($nid, $value, $message = '') {
+    // Retrieve the latest revision log message for this node.
+    $log_message = $this->database->select('node_revision', 'r')
+      ->fields('r', ['revision_log'])
       ->condition('nid', $nid)
-      ->countQuery()
+      ->orderBy('vid', 'DESC')
+      ->range(0, 1)
       ->execute()
       ->fetchColumn();
 
-    return $this->assertEqual($count, $value, $message, $group);
+    return $this->assertEquals($value, $log_message, $message);
   }
 
   /**
@@ -91,7 +86,7 @@ protected function assertRevisionCount($nid, $value, $message = '', $group = 'Ot
    */
   public function testRevisioning() {
     // Create a scheduled node that is not automatically revisioned.
-    $created = strtotime('-2 day', REQUEST_TIME);
+    $created = strtotime('-2 day', $this->requestTime);
     $settings = [
       'type' => $this->type,
       'revision' => 0,
@@ -118,17 +113,15 @@ public function testRevisioning() {
     // Test scheduled publication with revisioning enabled.
     $node = $this->schedule($node);
     $this->assertRevisionCount($node->id(), 2, 'A new revision was created when revisioning is enabled.');
-    $expected_message = sprintf('Node published by Scheduler on %s. Previous creation date was %s.',
-      \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'),
-      \Drupal::service('date.formatter')->format($created, 'short'));
+    $expected_message = sprintf('Published by Scheduler. The scheduled publishing date was %s.',
+    $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short'));
     $this->assertRevisionLogMessage($node->id(), $expected_message, 'The correct message was found in the node revision log after scheduled publishing.');
 
     // Test scheduled unpublication with revisioning enabled.
     $node = $this->schedule($node, 'unpublish');
     $this->assertRevisionCount($node->id(), 3, 'A new revision was created when a node was unpublished with revisioning enabled.');
-    $expected_message = sprintf('Node unpublished by Scheduler on %s. Previous change date was %s.',
-      \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'),
-      \Drupal::service('date.formatter')->format(REQUEST_TIME, 'short'));
+    $expected_message = sprintf('Unpublished by Scheduler. The scheduled unpublishing date was %s.',
+    $this->dateFormatter->format(strtotime('-5 hour', $this->requestTime), 'short'));
     $this->assertRevisionLogMessage($node->id(), $expected_message, 'The correct message was found in the node revision log after scheduled unpublishing.');
   }
 
@@ -140,7 +133,7 @@ public function testAlterCreationDate() {
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_past_date', 'schedule')->save();
 
     // Create a node with a 'created' date two days in the past.
-    $created = strtotime('-2 day', REQUEST_TIME);
+    $created = strtotime('-2 day', $this->requestTime);
     $settings = [
       'type' => $this->type,
       'created' => $created,
@@ -155,7 +148,7 @@ public function testAlterCreationDate() {
     // Get the created date from the node and check that it has not changed.
     $created_after_cron = $node->created->value;
     $this->assertTrue($node->isPublished(), 'The node has been published.');
-    $this->assertEqual($created, $created_after_cron, 'The node creation date is not changed by default.');
+    $this->assertEquals($created_after_cron, $created, 'The node creation date is not changed by default.');
 
     // Set option to change the created date to match the publish_on date.
     $this->nodetype->setThirdPartySetting('scheduler', 'publish_touch', TRUE)->save();
@@ -164,7 +157,8 @@ public function testAlterCreationDate() {
     $node = $this->schedule($node, 'publish');
     // Check that the created date has changed to match the publish_on date.
     $created_after_cron = $node->created->value;
-    $this->assertEqual(strtotime('-1 day', REQUEST_TIME), $created_after_cron, "With 'touch' option set, the node creation date is changed to match the publishing date.");
+    $this->assertEqual(strtotime('-5 hour', $this->requestTime), $created_after_cron, "With 'touch' option set, the node creation date is changed to match the publishing date.");
+
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
index 9bd335f9f3..63ed424e67 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesActionsTest.php
@@ -8,6 +8,9 @@
  * Tests the six actions that Scheduler provides for use in Rules module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code.
+ * @see https://www.drupal.org/project/scheduler/issues/2924353
  */
 class SchedulerRulesActionsTest extends SchedulerBrowserTestBase {
 
@@ -57,7 +60,7 @@ public function testPublishOnActions() {
     $rule1->addAction('scheduler_set_publishing_date_action',
       ContextConfig::create()
         ->map('node', 'node')
-        ->setValue('date', REQUEST_TIME + 1800)
+        ->setValue('date', $this->requestTime + 1800)
       )
       ->addAction('rules_system_message',
         ContextConfig::create()
@@ -113,7 +116,7 @@ public function testPublishOnActions() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     // Check that neither of the rules are triggered, no publish and unpublish
@@ -129,7 +132,7 @@ public function testPublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 1',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 1 is triggered and rule 2 is not. Check that a publishing
@@ -145,7 +148,7 @@ public function testPublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 2',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 2 is triggered and rule 1 is not. Check that the
@@ -179,7 +182,7 @@ public function testUnpublishOnActions() {
     $rule3->addAction('scheduler_set_unpublishing_date_action',
       ContextConfig::create()
         ->map('node', 'node')
-        ->setValue('date', REQUEST_TIME + 1800)
+        ->setValue('date', $this->requestTime + 1800)
       )
       ->addAction('rules_system_message',
         ContextConfig::create()
@@ -235,7 +238,7 @@ public function testUnpublishOnActions() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     // Check that neither of the rules are triggered, no publish and unpublish
@@ -251,7 +254,7 @@ public function testUnpublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 3',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 3 is triggered and rule 4 is not. Check that an
@@ -267,7 +270,7 @@ public function testUnpublishOnActions() {
       'title[0][value]' => 'Trigger Action Rule 4',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
     $this->nodeStorage->resetCache([$this->node->id()]);
     $node = $this->nodeStorage->load($this->node->id());
     // Check that rule 4 is triggered and rule 3 is not. Check that the
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
index 62a07efc36..7a246a99ce 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesConditionsTest.php
@@ -8,6 +8,9 @@
  * Tests the four conditions that Scheduler provides for use in Rules module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code.
+ * @see https://www.drupal.org/project/scheduler/issues/2924353
  */
 class SchedulerRulesConditionsTest extends SchedulerBrowserTestBase {
 
@@ -225,7 +228,7 @@ public function testNodeIsScheduledConditions() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
 
     $this->assertText($message5, '"' . $message5 . '" is shown');
     $this->assertText($message6, '"' . $message6 . '" is shown');
@@ -234,10 +237,10 @@ public function testNodeIsScheduledConditions() {
 
     // Edit the node and set a publish_on date.
     $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', REQUEST_TIME)),
+      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)),
+      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', $this->requestTime)),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
 
     $this->assertNoText($message5, '"' . $message5 . '" is not shown');
     $this->assertText($message6, '"' . $message6 . '" is shown');
@@ -246,10 +249,10 @@ public function testNodeIsScheduledConditions() {
 
     // Edit the node and set an unpublish_on date.
     $edit = [
-      'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', REQUEST_TIME)),
-      'unpublish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', REQUEST_TIME)),
+      'unpublish_on[0][value][date]' => date('Y-m-d', strtotime('+2 day', $this->requestTime)),
+      'unpublish_on[0][value][time]' => date('H:i:s', strtotime('+2 day', $this->requestTime)),
     ];
-    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $this->node->id() . '/edit', $edit, 'Save');
 
     $this->assertNoText($message5, '"' . $message5 . '" is not shown');
     $this->assertNoText($message6, '"' . $message6 . '" is not shown');
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
index f7276ad61d..f600246442 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerRulesEventsTest.php
@@ -8,6 +8,9 @@
  * Tests the six events that Scheduler provides for use in Rules module.
  *
  * @group scheduler
+ * @group legacy
+ * @todo Remove the 'legacy' tag when Rules no longer uses deprecated code.
+ * @see https://www.drupal.org/project/scheduler/issues/2924353
  */
 class SchedulerRulesEventsTest extends SchedulerBrowserTestBase {
 
@@ -49,6 +52,11 @@ public function testRulesEvents() {
       5 => ['scheduler_existing_node_is_scheduled_for_unpublishing_event', 'An existing node is saved and is scheduled for unpublishing.'],
       6 => ['scheduler_has_unpublished_this_node_event', 'Scheduler has unpublished this node during cron.'],
     ];
+    // PHPCS throws a false-positive 'variable $var is undefined' message when
+    // the variable is defined by list( ) syntax. To avoid the unwanted warnings
+    // we can wrap the section with @codingStandardsIgnoreStart and IgnoreEnd.
+    // @see https://www.drupal.org/project/coder/issues/2876245
+    // @codingStandardsIgnoreStart
     foreach ($rule_data as $i => list($event_name, $description)) {
       $rule[$i] = $this->expressionManager->createRule();
       $message[$i] = 'RULES message ' . $i . '. ' . $description;
@@ -63,6 +71,7 @@ public function testRulesEvents() {
       ]);
       $config_entity->save();
     }
+    // @codingStandardsIgnoreEnd
 
     $this->drupalLogin($this->schedulerUser);
 
@@ -72,7 +81,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Test for no events',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -85,7 +94,7 @@ public function testRulesEvents() {
     $edit = [
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
     $this->assertNoText($message[3], '"' . $message[3] . '" is not shown');
@@ -101,7 +110,7 @@ public function testRulesEvents() {
       'publish_on[0][value][time]' => date('H:i:s', time() + 3),
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertText($message[1], '"' . $message[1] . '" IS shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -115,7 +124,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Edit node with publish-on date',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertText($message[2], '"' . $message[2] . '" IS shown');
     $this->assertNoText($message[3], '"' . $message[3] . '" is not shown');
@@ -143,7 +152,7 @@ public function testRulesEvents() {
       'unpublish_on[0][value][time]' => date('H:i:s', time() + 3),
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -157,7 +166,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Edit node with unpublish-on date',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
     $this->assertNoText($message[3], '"' . $message[3] . '" is not shown');
@@ -187,7 +196,7 @@ public function testRulesEvents() {
       'unpublish_on[0][value][time]' => date('H:i:s', time() + 4),
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
+    $this->drupalPostForm('node/add/' . $this->type, $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertText($message[1], '"' . $message[1] . '" IS shown');
     $this->assertNoText($message[2], '"' . $message[2] . '" is not shown');
@@ -201,7 +210,7 @@ public function testRulesEvents() {
       'title[0][value]' => 'Edit node with both dates',
       'body[0][value]' => $this->randomString(30),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertNoText($message[1], '"' . $message[1] . '" is not shown');
     $this->assertText($message[2], '"' . $message[2] . '" IS shown');
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php
index b58c1f169b..ef4196825d 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerStatusReportTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
+use Drupal\Core\Url;
+
 /**
  * Tests the Scheduler section of the status report.
  *
@@ -19,13 +21,13 @@ public function testStatusReport() {
     $this->assertText('Scheduler Time Check');
     $this->assertText('In most cases the server time should match Coordinated Universal Time (UTC) / Greenwich Mean Time (GMT)');
 
-    $admin_regional_settings = \Drupal::url('system.regional_settings');
+    $admin_regional_settings = Url::fromRoute('system.regional_settings');
     $this->assertLink('changed by admin users');
-    $this->assertLinkByHref($admin_regional_settings);
+    $this->assertLinkByHref($admin_regional_settings->toString());
 
-    $account_edit = \Drupal::url('entity.user.edit_form', ['user' => $this->adminUser->id()]);
+    $account_edit = Url::fromRoute('entity.user.edit_form', ['user' => $this->adminUser->id()]);
     $this->assertLink('user account');
-    $this->assertLinkByHref($account_edit);
+    $this->assertLinkByHref($account_edit->toString());
   }
 
 }
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
index ceb4487466..784f960034 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerTokenReplaceTest.php
@@ -14,11 +14,9 @@ class SchedulerTokenReplaceTest extends SchedulerBrowserTestBase {
    */
   public function testSchedulerTokenReplacement() {
     $this->drupalLogin($this->schedulerUser);
-    $date_formatter = \Drupal::service('date.formatter');
-
     // Define timestamps for consistent use when repeated throughout this test.
-    $publish_on_timestamp = REQUEST_TIME + 3600;
-    $unpublish_on_timestamp = REQUEST_TIME + 7200;
+    $publish_on_timestamp = $this->requestTime + 3600;
+    $unpublish_on_timestamp = $this->requestTime + 7200;
 
     // Create an unpublished page with scheduled dates.
     $node = $this->drupalCreateNode([
@@ -48,7 +46,7 @@ public function testSchedulerTokenReplacement() {
       $edit = [
         'body[0][value]' => 'Publish on: [node:scheduler-publish' . $test_data['token_format'] . ']. Unpublish on: [node:scheduler-unpublish' . $test_data['token_format'] . '].',
       ];
-      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
       $this->drupalGet('node/' . $node->id());
 
       // Refresh the node and get the body output value.
@@ -57,11 +55,11 @@ public function testSchedulerTokenReplacement() {
       $body_output = \Drupal::token()->replace($node->body->value, ['node' => $node]);
 
       // Create the expected text for the body.
-      $publish_on_date = $date_formatter->format($publish_on_timestamp, $test_data['date_format'], $test_data['custom']);
-      $unpublish_on_date = $date_formatter->format($unpublish_on_timestamp, $test_data['date_format'], $test_data['custom']);
+      $publish_on_date = $this->dateFormatter->format($publish_on_timestamp, $test_data['date_format'], $test_data['custom']);
+      $unpublish_on_date = $this->dateFormatter->format($unpublish_on_timestamp, $test_data['date_format'], $test_data['custom']);
       $expected_output = 'Publish on: ' . $publish_on_date . '. Unpublish on: ' . $unpublish_on_date . '.';
       // Check that the actual text matches the expected value.
-      $this->assertEqual($body_output, $expected_output, 'Scheduler tokens replaced correctly for ' . $test_data['token_format'] . ' format.');
+      $this->assertEquals($expected_output, $body_output, 'Scheduler tokens replaced correctly for ' . $test_data['token_format'] . ' format.');
     }
   }
 
diff --git a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
index 346ac01536..fa5dc21b45 100644
--- a/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
+++ b/web/modules/scheduler/tests/src/Functional/SchedulerValidationTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\scheduler\Functional;
 
-use Drupal\Component\Utility\SafeMarkup;
-
 /**
  * Tests the validation when editing a node.
  *
@@ -30,8 +28,8 @@ public function testValidationDuringEdit() {
       'status' => FALSE,
     ]);
     $edit = [
-      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', REQUEST_TIME)),
-      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', REQUEST_TIME)),
+      'publish_on[0][value][date]' => date('Y-m-d', strtotime('+1 day', $this->requestTime)),
+      'publish_on[0][value][time]' => date('H:i:s', strtotime('+1 day', $this->requestTime)),
     ];
     $this->drupalGet('node/' . $node->id() . '/edit');
 
@@ -43,8 +41,8 @@ public function testValidationDuringEdit() {
     $checkbox = $this->xpath('//input[@type="checkbox" and @id="edit-status-value"]');
 
     $this->submitForm($edit, $checkbox ? 'Save' : 'Save and keep unpublished');
-    $this->assertRaw(t("If you set a 'publish on' date then you must also set an 'unpublish on' date."), 'Validation prevents entering a publish-on date with no unpublish-on date if unpublishing is required.');
-    $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.');
+    $this->assertText("If you set a 'publish on' date then you must also set an 'unpublish on' date.", 'Validation prevents entering a publish-on date with no unpublish-on date if unpublishing is required.');
+    $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.');
 
     // Create an unpublished page node, then edit the node and check that if the
     // status is changed to published, then an unpublish-on date is also needed.
@@ -59,8 +57,8 @@ public function testValidationDuringEdit() {
       $edit = [];
     }
     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, $checkbox ? 'Save' : 'Save and publish');
-    $this->assertRaw(t("Either you must set an 'unpublish on' date or save this node as unpublished."), 'Validation prevents publishing the node directly without an unpublish-on date if unpublishing is required.');
-    $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.');
+    $this->assertText("Either you must set an 'unpublish on' date or save this node as unpublished.", 'Validation prevents publishing the node directly without an unpublish-on date if unpublishing is required.');
+    $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.');
 
     // Create an unpublished node, edit the node and check that if both dates
     // are entered then the unpublish date is later than the publish date.
@@ -69,14 +67,14 @@ public function testValidationDuringEdit() {
       'status' => FALSE,
     ]);
     $edit = [
-      'publish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 8100, 'custom', 'Y-m-d'),
-      'publish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 8100, 'custom', 'H:i:s'),
-      'unpublish_on[0][value][date]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 1800, 'custom', 'Y-m-d'),
-      'unpublish_on[0][value][time]' => \Drupal::service('date.formatter')->format(REQUEST_TIME + 1800, 'custom', 'H:i:s'),
+      'publish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime + 8100, 'custom', 'Y-m-d'),
+      'publish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime + 8100, 'custom', 'H:i:s'),
+      'unpublish_on[0][value][date]' => $this->dateFormatter->format($this->requestTime + 1800, 'custom', 'Y-m-d'),
+      'unpublish_on[0][value][time]' => $this->dateFormatter->format($this->requestTime + 1800, 'custom', 'H:i:s'),
     ];
     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, $checkbox ? 'Save' : 'Save and keep unpublished');
-    $this->assertRaw(t("The 'unpublish on' date must be later than the 'publish on' date."), 'Validation prevents entering an unpublish-on date which is earlier than the publish-on date.');
-    $this->assertNoRaw(t('@type %title has been updated.', ['@type' => $this->typeName, '%title' => SafeMarkup::checkPlain($node->title->value)]), 'The node has not been saved.');
+    $this->assertText("The 'unpublish on' date must be later than the 'publish on' date.", 'Validation prevents entering an unpublish-on date which is earlier than the publish-on date.');
+    $this->assertNoText(sprintf('%s %s has been updated.', $this->typeName, $node->title->value), 'The node has not been saved.');
   }
 
 }
-- 
GitLab