From e47700f70ab9237a360082768de59440330458bb Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Mon, 12 Aug 2019 14:24:04 +1000
Subject: [PATCH] Issue #3009377 by kim.pepper, andypost, Berdir, alexpott, Wim
 Leers, mpdonadio, acbramley, larowlan, catch, xjm: Replace
 drupal_get_user_timezone with a date_default_timezone_get() and an event
 listener

---
 core/core.services.yml                        |   1 +
 core/includes/bootstrap.inc                   |  19 ++--
 .../Drupal/Core/Datetime/DrupalDateTime.php   |   2 +-
 .../Drupal/Core/Datetime/Element/Datelist.php |   4 +-
 .../Drupal/Core/Datetime/Element/Datetime.php |   4 +-
 .../AuthenticationSubscriber.php              |   4 +-
 .../lib/Drupal/Core/Session/AccountEvents.php |  28 +++++
 core/lib/Drupal/Core/Session/AccountProxy.php |  28 ++++-
 .../Drupal/Core/Session/AccountSetEvent.php   |  39 +++++++
 .../DateTimeCustomFormatter.php               |   2 +-
 .../FieldFormatter/DateTimeFormatterBase.php  |   4 +-
 .../FieldFormatter/DateTimePlainFormatter.php |   2 +-
 .../Field/FieldType/DateTimeFieldItemList.php |   2 +-
 .../Field/FieldWidget/DateTimeWidgetBase.php  |   2 +-
 .../datetime/src/Plugin/views/filter/Date.php |   4 +-
 .../src/Functional/DateTimeFieldTest.php      |   6 +-
 .../Kernel/Views/DateTimeHandlerTestBase.php  |   2 +-
 .../Field/FieldWidget/DateRangeWidgetBase.php |   2 +-
 .../src/Functional/DateRangeFieldTest.php     |  14 +--
 core/modules/system/src/TimeZoneResolver.php  | 100 ++++++++++++++++++
 core/modules/system/system.services.yml       |   5 +
 .../src/Kernel/TimeZoneDeprecationTest.php    |  30 ++++++
 .../tests/src/Kernel/TimezoneResolverTest.php |  62 +++++++++++
 .../Plugin/views/query/QueryPluginBase.php    |   6 +-
 .../Datetime/TimestampTest.php                |   2 +-
 .../Core/Datetime/Element/TimezoneTest.php    |   9 +-
 .../Tests/Core/Session/AccountProxyTest.php   |  19 ++--
 27 files changed, 338 insertions(+), 64 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Session/AccountEvents.php
 create mode 100644 core/lib/Drupal/Core/Session/AccountSetEvent.php
 create mode 100644 core/modules/system/src/TimeZoneResolver.php
 create mode 100644 core/modules/system/tests/src/Kernel/TimeZoneDeprecationTest.php
 create mode 100644 core/modules/system/tests/src/Kernel/TimezoneResolverTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 04f75f64bc6b..f2b22a878048 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1530,6 +1530,7 @@ services:
     arguments: ['@private_key', '@cache.bootstrap', '@cache.static']
   current_user:
     class: Drupal\Core\Session\AccountProxy
+    arguments: ['@event_dispatcher']
   session_configuration:
     class: Drupal\Core\Session\SessionConfiguration
     arguments: ['%session.storage.options%']
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index ce86b74875ee..f65c3d1f0676 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -564,20 +564,15 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
  *
  * @return string
  *   The name of the current user's timezone or the name of the default timezone.
+ *
+ * @deprecated in drupal:8.8.0 and will be removed from drupal:9.0.0. Use
+ *   date_default_timezone_get() instead.
+ *
+ * @see https://www.drupal.org/node/3009387
  */
 function drupal_get_user_timezone() {
-  $user = \Drupal::currentUser();
-  $config = \Drupal::config('system.date');
-
-  if ($user && $config->get('timezone.user.configurable') && $user->isAuthenticated() && $user->getTimezone()) {
-    return $user->getTimezone();
-  }
-  else {
-    // Ignore PHP strict notice if time zone has not yet been set in the php.ini
-    // configuration.
-    $config_data_default_timezone = $config->get('timezone.default');
-    return !empty($config_data_default_timezone) ? $config_data_default_timezone : @date_default_timezone_get();
-  }
+  @trigger_error('drupal_get_user_timezone() is deprecated in drupal:8.8.0. It will be removed from drupal:9.0.0. Use date_default_timezone_get() instead. See https://www.drupal.org/node/3009387', E_USER_DEPRECATED);
+  return date_default_timezone_get();
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Datetime/DrupalDateTime.php b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
index 193e58f7fc94..9f9edc47398a 100644
--- a/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
+++ b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
@@ -73,7 +73,7 @@ public function __construct($time = 'now', $timezone = NULL, $settings = []) {
   protected function prepareTimezone($timezone) {
     if (empty($timezone)) {
       // Fallback to user or system default timezone.
-      $timezone = drupal_get_user_timezone();
+      $timezone = date_default_timezone_get();
     }
     return parent::prepareTimezone($timezone);
   }
diff --git a/core/lib/Drupal/Core/Datetime/Element/Datelist.php b/core/lib/Drupal/Core/Datetime/Element/Datelist.php
index 68dc02288441..97ee3bcbe648 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datelist.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datelist.php
@@ -33,7 +33,7 @@ public function getInfo() {
       '#date_year_range' => '1900:2050',
       '#date_increment' => 1,
       '#date_date_callbacks' => [],
-      '#date_timezone' => drupal_get_user_timezone(),
+      '#date_timezone' => date_default_timezone_get(),
     ];
   }
 
@@ -149,7 +149,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
    *     minute.
    *   - #date_timezone: The Time Zone Identifier (TZID) to use when displaying
    *     or interpreting dates, i.e: 'Asia/Kolkata'. Defaults to the value
-   *     returned by drupal_get_user_timezone().
+   *     returned by date_default_timezone_get().
    *
    * Example usage:
    * @code
diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
index 118e8263fc34..52b90485b039 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datetime.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
@@ -61,7 +61,7 @@ public function getInfo() {
       '#date_time_callbacks' => [],
       '#date_year_range' => '1900:2050',
       '#date_increment' => 1,
-      '#date_timezone' => drupal_get_user_timezone(),
+      '#date_timezone' => date_default_timezone_get(),
     ];
   }
 
@@ -190,7 +190,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
    *     second.
    *   - #date_timezone: The Time Zone Identifier (TZID) to use when displaying
    *     or interpreting dates, i.e: 'Asia/Kolkata'. Defaults to the value
-   *     returned by drupal_get_user_timezone().
+   *     returned by date_default_timezone_get().
    *
    * Example usage:
    * @code
diff --git a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
index 5377fbf2405b..d8d0da74e517 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\Core\EventSubscriber;
 
-use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
 use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
+use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
 use Drupal\Core\Session\AccountProxyInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -80,8 +80,6 @@ public function onKernelRequestAuthenticate(GetResponseEvent $event) {
           return;
         }
       }
-      // No account has been set explicitly, initialize the timezone here.
-      date_default_timezone_set(drupal_get_user_timezone());
     }
   }
 
diff --git a/core/lib/Drupal/Core/Session/AccountEvents.php b/core/lib/Drupal/Core/Session/AccountEvents.php
new file mode 100644
index 000000000000..707bb120ab25
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/AccountEvents.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\Core\Session;
+
+/**
+ * Defines events for the account system.
+ *
+ * @see \Drupal\Core\Session\AccountSetEvent
+ */
+final class AccountEvents {
+
+  /**
+   * Name of the event fired when the current user is set.
+   *
+   * This event allows modules to perform an action whenever the current user is
+   * set. The event listener receives an \Drupal\Core\Session\AccountSetEvent
+   * instance.
+   *
+   * @Event
+   *
+   * @see \Drupal\Core\Session\AccountSetEvent
+   * @see \Drupal\Core\Session\AccountProxyInterface::setAccount()
+   *
+   * @var string
+   */
+  const SET_USER = 'account.set';
+
+}
diff --git a/core/lib/Drupal/Core/Session/AccountProxy.php b/core/lib/Drupal/Core/Session/AccountProxy.php
index bc7f554c33d3..35c5cd8fbf51 100644
--- a/core/lib/Drupal/Core/Session/AccountProxy.php
+++ b/core/lib/Drupal/Core/Session/AccountProxy.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Core\Session;
 
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
 /**
  * A proxied implementation of AccountInterface.
  *
@@ -15,6 +18,8 @@
  */
 class AccountProxy implements AccountProxyInterface {
 
+  use DependencySerializationTrait;
+
   /**
    * The instantiated account.
    *
@@ -39,6 +44,27 @@ class AccountProxy implements AccountProxyInterface {
    */
   protected $initialAccountId;
 
+  /**
+   * Event dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $eventDispatcher;
+
+  /**
+   * AccountProxy constructor.
+   *
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
+   *   Event dispatcher.
+   */
+  public function __construct(EventDispatcherInterface $eventDispatcher = NULL) {
+    if (!$eventDispatcher) {
+      @trigger_error('Calling AccountProxy::__construct() without the $eventDispatcher argument is deprecated in drupal:8.8.0. The $eventDispatcher argument will be required in drupal:9.0.0. See https://www.drupal.org/node/3009387', E_USER_DEPRECATED);
+      $eventDispatcher = \Drupal::service('event_dispatcher');
+    }
+    $this->eventDispatcher = $eventDispatcher;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -50,7 +76,7 @@ public function setAccount(AccountInterface $account) {
     }
     $this->account = $account;
     $this->id = $account->id();
-    date_default_timezone_set(drupal_get_user_timezone());
+    $this->eventDispatcher->dispatch(AccountEvents::SET_USER, new AccountSetEvent($account));
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Session/AccountSetEvent.php b/core/lib/Drupal/Core/Session/AccountSetEvent.php
new file mode 100644
index 000000000000..1ef91fe67b54
--- /dev/null
+++ b/core/lib/Drupal/Core/Session/AccountSetEvent.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\Core\Session;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event fired when an account is set for the current session.
+ */
+final class AccountSetEvent extends Event {
+
+  /**
+   * The set account.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * AccountSetEvent constructor.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The set account.
+   */
+  public function __construct(AccountInterface $account) {
+    $this->account = $account;
+  }
+
+  /**
+   * Gets the account.
+   *
+   * @return \Drupal\Core\Session\AccountInterface
+   *   The account.
+   */
+  public function getAccount() {
+    return $this->account;
+  }
+
+}
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
index e3b9daf2ee2a..4ade45b7a55b 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
@@ -55,7 +55,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    */
   protected function formatDate($date) {
     $format = $this->getSetting('date_format');
-    $timezone = $this->getSetting('timezone_override');
+    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
     return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL);
   }
 
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php
index a4fe711b8a39..272cb9ea1ec1 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php
@@ -160,8 +160,6 @@ abstract protected function formatDate($date);
    * zone applied to it.  This method will apply the time zone for the current
    * user, based on system and user settings.
    *
-   * @see drupal_get_user_timezone()
-   *
    * @param \Drupal\Core\Datetime\DrupalDateTime $date
    *   A DrupalDateTime object.
    */
@@ -171,7 +169,7 @@ protected function setTimeZone(DrupalDateTime $date) {
       $timezone = DateTimeItemInterface::STORAGE_TIMEZONE;
     }
     else {
-      $timezone = drupal_get_user_timezone();
+      $timezone = date_default_timezone_get();
     }
     $date->setTimeZone(timezone_open($timezone));
   }
diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
index 8ddfdda2aeb6..b8b25bc61eb6 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
@@ -42,7 +42,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    */
   protected function formatDate($date) {
     $format = $this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
-    $timezone = $this->getSetting('timezone_override');
+    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
     return $this->dateFormatter->format($date->getTimestamp(), 'custom', $format, $timezone != '' ? $timezone : NULL);
   }
 
diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php
index 2af64d7bb9ee..203f15d0bd0a 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeFieldItemList.php
@@ -95,7 +95,7 @@ public static function processDefaultValue($default_value, FieldableEntityInterf
       if ($definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
         // A default date only value should be in the format used for date
         // storage but in the user's local timezone.
-        $date = new DrupalDateTime($default_value[0]['default_date'], drupal_get_user_timezone());
+        $date = new DrupalDateTime($default_value[0]['default_date'], date_default_timezone_get());
         $format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
       }
       else {
diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
index d723482824c0..41e9853a55e1 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
@@ -22,7 +22,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#type' => 'datetime',
       '#default_value' => NULL,
       '#date_increment' => 1,
-      '#date_timezone' => drupal_get_user_timezone(),
+      '#date_timezone' => date_default_timezone_get(),
       '#required' => $element['#required'],
     ];
 
diff --git a/core/modules/datetime/src/Plugin/views/filter/Date.php b/core/modules/datetime/src/Plugin/views/filter/Date.php
index 41c4726f0eb7..f73a675fef0c 100644
--- a/core/modules/datetime/src/Plugin/views/filter/Date.php
+++ b/core/modules/datetime/src/Plugin/views/filter/Date.php
@@ -151,7 +151,7 @@ protected function opSimple($field) {
   protected function getTimezone() {
     return $this->dateFormat === DateTimeItemInterface::DATE_STORAGE_FORMAT
       ? DateTimeItemInterface::STORAGE_TIMEZONE
-      : drupal_get_user_timezone();
+      : date_default_timezone_get();
   }
 
   /**
@@ -173,7 +173,7 @@ protected function getOffset($time, $timezone) {
     // the user's offset from UTC for use in the query.
     $origin_offset = 0;
     if ($this->dateFormat === DateTimeItemInterface::DATE_STORAGE_FORMAT && $this->value['type'] === 'offset') {
-      $origin_offset = $origin_offset + timezone_offset_get(new \DateTimeZone(drupal_get_user_timezone()), new \DateTime($time, new \DateTimeZone($timezone)));
+      $origin_offset = $origin_offset + timezone_offset_get(new \DateTimeZone(date_default_timezone_get()), new \DateTime($time, new \DateTimeZone($timezone)));
     }
 
     return $origin_offset;
diff --git a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
index 179bc51cc8dd..1ffa0e865b80 100644
--- a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
+++ b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
@@ -253,7 +253,7 @@ public function testDatetimeField() {
     $date = new DrupalDateTime($value, 'UTC');
 
     // Update the timezone to the system default.
-    $date->setTimezone(timezone_open(drupal_get_user_timezone()));
+    $date->setTimezone(timezone_open(date_default_timezone_get()));
 
     // Submit a valid date and ensure it is accepted.
     $date_format = DateFormat::load('html_date')->getPattern();
@@ -708,7 +708,7 @@ public function testDefaultValue() {
 
       // Create a new node to check that datetime field default value is today.
       $new_node = Node::create(['type' => 'date_content']);
-      $expected_date = new DrupalDateTime('now', drupal_get_user_timezone());
+      $expected_date = new DrupalDateTime('now', date_default_timezone_get());
       $this->assertEqual($new_node->get($field_name)
         ->offsetGet(0)->value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
 
@@ -747,7 +747,7 @@ public function testDefaultValue() {
       // Create a new node to check that datetime field default value is +90
       // days.
       $new_node = Node::create(['type' => 'date_content']);
-      $expected_date = new DrupalDateTime('+90 days', drupal_get_user_timezone());
+      $expected_date = new DrupalDateTime('+90 days', date_default_timezone_get());
       $this->assertEqual($new_node->get($field_name)
         ->offsetGet(0)->value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
 
diff --git a/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php b/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php
index 590355164178..82c54def0405 100644
--- a/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php
+++ b/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php
@@ -111,7 +111,7 @@ protected function setSiteTimezone($timezone) {
    *   Unix timestamp.
    */
   protected function getUTCEquivalentOfUserNowAsTimestamp() {
-    $user_now = new DateTimePlus('now', new \DateTimeZone(drupal_get_user_timezone()));
+    $user_now = new DateTimePlus('now', new \DateTimeZone(date_default_timezone_get()));
     $utc_equivalent = new DateTimePlus($user_now->format('Y-m-d H:i:s'), new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
 
     return $utc_equivalent->getTimestamp();
diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
index d98804e011de..b0f3ea7be7da 100644
--- a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
+++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
@@ -62,7 +62,7 @@ public function massageFormValues(array $values, array $form, FormStateInterface
     }
 
     $storage_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE);
-    $user_timezone = new \DateTimeZone(drupal_get_user_timezone());
+    $user_timezone = new \DateTimeZone(date_default_timezone_get());
 
     foreach ($values as &$item) {
       if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) {
diff --git a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
index 4343a050ae54..65faabf97fb6 100644
--- a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
+++ b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php
@@ -304,8 +304,8 @@ public function testDatetimeRangeField() {
     $end_date = new DrupalDateTime($end_value, 'UTC');
 
     // Update the timezone to the system default.
-    $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
-    $end_date->setTimezone(timezone_open(drupal_get_user_timezone()));
+    $start_date->setTimezone(timezone_open(date_default_timezone_get()));
+    $end_date->setTimezone(timezone_open(date_default_timezone_get()));
 
     // Submit a valid date and ensure it is accepted.
     $date_format = DateFormat::load('html_date')->getPattern();
@@ -390,7 +390,7 @@ public function testDatetimeRangeField() {
     $this->drupalGet('entity_test/add');
     $value = '2012-12-31 00:00:00';
     $start_date = new DrupalDateTime($value, 'UTC');
-    $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
+    $start_date->setTimezone(timezone_open(date_default_timezone_get()));
 
     $date_format = DateFormat::load('html_date')->getPattern();
     $time_format = DateFormat::load('html_time')->getPattern();
@@ -478,9 +478,9 @@ public function testAlldayRangeField() {
 
     // Build up dates in the proper timezone.
     $value = '2012-12-31 00:00:00';
-    $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
+    $start_date = new DrupalDateTime($value, timezone_open(date_default_timezone_get()));
     $end_value = '2013-06-06 23:59:59';
-    $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
+    $end_date = new DrupalDateTime($end_value, timezone_open(date_default_timezone_get()));
 
     // Submit a valid date and ensure it is accepted.
     $date_format = DateFormat::load('html_date')->getPattern();
@@ -563,9 +563,9 @@ public function testAlldayRangeField() {
     $this->drupalGet('entity_test/add');
 
     $value = '2012-12-31 00:00:00';
-    $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
+    $start_date = new DrupalDateTime($value, timezone_open(date_default_timezone_get()));
     $end_value = '2012-12-31 23:59:59';
-    $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
+    $end_date = new DrupalDateTime($end_value, timezone_open(date_default_timezone_get()));
 
     $date_format = DateFormat::load('html_date')->getPattern();
     $time_format = DateFormat::load('html_time')->getPattern();
diff --git a/core/modules/system/src/TimeZoneResolver.php b/core/modules/system/src/TimeZoneResolver.php
new file mode 100644
index 000000000000..551a8d5cda70
--- /dev/null
+++ b/core/modules/system/src/TimeZoneResolver.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Drupal\system;
+
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Session\AccountEvents;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Event handler that resolves time zone based on site and user configuration.
+ *
+ * Sets the time zone using date_default_timezone_set().
+ *
+ * @see date_default_timezone_set()
+ */
+class TimeZoneResolver implements EventSubscriberInterface {
+
+  /**
+   * The config.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  private $currentUser;
+
+  /**
+   * TimeZoneResolver constructor.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   */
+  public function __construct(AccountInterface $current_user, ConfigFactoryInterface $config_factory) {
+    $this->configFactory = $config_factory;
+    $this->currentUser = $current_user;
+  }
+
+  /**
+   * Sets the default time zone.
+   */
+  public function setDefaultTimeZone() {
+    if ($time_zone = $this->getTimeZone()) {
+      date_default_timezone_set($time_zone);
+    }
+  }
+
+  /**
+   * Updates the default time zone when time zone config changes.
+   *
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   *   The config crud event.
+   */
+  public function onConfigSave(ConfigCrudEvent $event) {
+    $saved_config = $event->getConfig();
+    if ($saved_config->getName() === 'system.date' && ($event->isChanged('timezone.default') || $event->isChanged('timezone.user.configurable'))) {
+      $this->setDefaultTimeZone();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[ConfigEvents::SAVE][] = ['onConfigSave', 0];
+    // The priority for this must run directly after the authentication
+    // subscriber.
+    $events[KernelEvents::REQUEST][] = ['setDefaultTimeZone', 299];
+    $events[AccountEvents::SET_USER][] = ['setDefaultTimeZone'];
+    return $events;
+  }
+
+  /**
+   * Gets the time zone based on site and user configuration.
+   *
+   * @return string|null
+   *   The time zone, or NULL if nothing is set.
+   */
+  protected function getTimeZone() {
+    $config = $this->configFactory->get('system.date');
+    if ($config->get('timezone.user.configurable') && $this->currentUser->isAuthenticated() && $this->currentUser->getTimezone()) {
+      return $this->currentUser->getTimeZone();
+    }
+    elseif ($default_timezone = $config->get('timezone.default')) {
+      return $default_timezone;
+    }
+    return NULL;
+  }
+
+}
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index 0728e0da8668..b52573fc0cad 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -43,3 +43,8 @@ services:
     arguments: ['@theme_handler', '@cache_tags.invalidator']
     tags:
       - { name: event_subscriber }
+  system.timezone_resolver:
+    class: Drupal\system\TimeZoneResolver
+    arguments: ['@current_user', '@config.factory']
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/system/tests/src/Kernel/TimeZoneDeprecationTest.php b/core/modules/system/tests/src/Kernel/TimeZoneDeprecationTest.php
new file mode 100644
index 000000000000..4473d4c19bf0
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/TimeZoneDeprecationTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\Tests\system\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests deprecated methods in bootstrap throw deprecation warnings.
+ *
+ * @group legacy
+ * @group system
+ */
+class TimeZoneDeprecationTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'system',
+    'user',
+  ];
+
+  /**
+   * @expectedDeprecation drupal_get_user_timezone() is deprecated in drupal:8.8.0. It will be removed from drupal:9.0.0. Use date_default_timezone_get() instead. See https://www.drupal.org/node/3009387
+   */
+  public function testDeprecation() {
+    $this->assertEquals('Australia/Sydney', drupal_get_user_timezone());
+  }
+
+}
diff --git a/core/modules/system/tests/src/Kernel/TimezoneResolverTest.php b/core/modules/system/tests/src/Kernel/TimezoneResolverTest.php
new file mode 100644
index 000000000000..7c7f889ed4ab
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/TimezoneResolverTest.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\Tests\system\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\Tests\user\Traits\UserCreationTrait;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * @coversDefaultClass \Drupal\system\TimeZoneResolver
+ * @group system
+ */
+class TimezoneResolverTest extends KernelTestBase {
+
+  use UserCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'system',
+    'user',
+  ];
+
+  /**
+   * Tests time zone resolution.
+   */
+  public function testGetTimeZone() {
+    $this->installEntitySchema('user');
+    $this->installSchema('system', ['sequences']);
+    $this->installConfig(['system']);
+
+    // Check the default test timezone.
+    $this->assertEquals('Australia/Sydney', date_default_timezone_get());
+
+    // Test the configured system timezone.
+    $configFactory = $this->container->get('config.factory');
+    $timeZoneConfig = $configFactory->getEditable('system.date');
+    $timeZoneConfig->set('timezone.default', 'Australia/Adelaide');
+    $timeZoneConfig->save();
+
+    $eventDispatcher = $this->container->get('event_dispatcher');
+    $kernel = $this->container->get('kernel');
+
+    $eventDispatcher->dispatch(KernelEvents::REQUEST, new GetResponseEvent($kernel, Request::create('http://www.example.com'), HttpKernelInterface::MASTER_REQUEST));
+
+    $this->assertEquals('Australia/Adelaide', date_default_timezone_get());
+
+    $user = $this->createUser([]);
+    $user->set('timezone', 'Australia/Lord_Howe');
+    $user->save();
+
+    $this->setCurrentUser($user);
+
+    $this->assertEquals('Australia/Lord_Howe', date_default_timezone_get());
+
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
index 5d39b9cb1c67..566435a3266e 100644
--- a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
+++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
@@ -221,13 +221,13 @@ public function getDateField($field, $string_date = FALSE, $calculate_offset = T
   }
 
   /**
-   * Set the database to the current user timezone,
+   * Set the database to the current user timezone.
    *
    * @return string
-   *   The current timezone as returned by drupal_get_user_timezone().
+   *   The current timezone as returned by date_default_timezone_get().
    */
   public function setupTimezone() {
-    return drupal_get_user_timezone();
+    return date_default_timezone_get();
   }
 
   /**
diff --git a/core/tests/Drupal/FunctionalTests/Datetime/TimestampTest.php b/core/tests/Drupal/FunctionalTests/Datetime/TimestampTest.php
index 58d0a508840d..064be558b0c6 100644
--- a/core/tests/Drupal/FunctionalTests/Datetime/TimestampTest.php
+++ b/core/tests/Drupal/FunctionalTests/Datetime/TimestampTest.php
@@ -104,7 +104,7 @@ public function testWidget() {
     $date = new DrupalDateTime($value, 'UTC');
 
     // Update the timezone to the system default.
-    $date->setTimezone(timezone_open(drupal_get_user_timezone()));
+    $date->setTimezone(timezone_open(date_default_timezone_get()));
 
     // Display creation form.
     $this->drupalGet('entity_test/add');
diff --git a/core/tests/Drupal/KernelTests/Core/Datetime/Element/TimezoneTest.php b/core/tests/Drupal/KernelTests/Core/Datetime/Element/TimezoneTest.php
index 074d1b1a9875..8a030b3fd1d9 100644
--- a/core/tests/Drupal/KernelTests/Core/Datetime/Element/TimezoneTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Datetime/Element/TimezoneTest.php
@@ -191,9 +191,8 @@ protected function setUp() {
     }
 
     // Validate the timezone setup.
-    $this->assertEquals($this->timezones['user'], drupal_get_user_timezone(), 'Subsequent tests assume specific value for drupal_get_user_timezone().');
-    $this->assertEquals(drupal_get_user_timezone(), date_default_timezone_get(), "Subsequent tests may assume PHP's time is set to Drupal user's time zone.");
-    $this->assertEquals(drupal_get_user_timezone(), $this->date->getTimezone()->getName(), 'Subsequent tests assume DrupalDateTime objects default to Drupal user time zone if none specified');
+    $this->assertEquals($this->timezones['user'], date_default_timezone_get(), 'Subsequent tests assume specific value for date_default_timezone_get().');
+    $this->assertEquals(date_default_timezone_get(), $this->date->getTimezone()->getName(), 'Subsequent tests assume DrupalDateTime objects default to Drupal user time zone if none specified');
   }
 
   /**
@@ -295,7 +294,7 @@ protected function assertTimesUnderstoodCorrectly($elementType, array $inputs) {
 
         // Check that $this->date has not anywhere been accidentally changed
         // from its default timezone, invalidating the test logic.
-        $this->assertEquals(drupal_get_user_timezone(), $this->date->getTimezone()->getName(), "Test date still set to user timezone.");
+        $this->assertEquals(date_default_timezone_get(), $this->date->getTimezone()->getName(), "Test date still set to user timezone.");
 
         // Build a list of cases where the result is not as expected.
         // Check the time has been understood correctly.
@@ -348,7 +347,7 @@ public function assertDateTimezonePropertyProcessed($elementType) {
           ];
         }
       }
-      $this->assertEquals($this->timezones['user'], drupal_get_user_timezone(), 'Subsequent tests assume specific value for drupal_get_user_timezone().');
+      $this->assertEquals($this->timezones['user'], date_default_timezone_get(), 'Subsequent tests assume specific value for date_default_timezone_get().');
       $message = "The correct timezone should be set on the processed {$this->elementType}  elements: (expected, actual) \n" . print_r($wrongTimezones, TRUE);
       $this->assertCount(0, $wrongTimezones, $message);
     }
diff --git a/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php b/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php
index d793524c333a..ae233a5cb918 100644
--- a/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php
+++ b/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php
@@ -3,8 +3,9 @@
 namespace Drupal\Tests\Core\Session;
 
 use Drupal\Core\Session\AccountInterface;
-use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Session\AccountProxy;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * @coversDefaultClass \Drupal\Core\Session\AccountProxy
@@ -17,7 +18,8 @@ class AccountProxyTest extends UnitTestCase {
    * @covers ::setInitialAccountId
    */
   public function testId() {
-    $account_proxy = new AccountProxy();
+    $dispatcher = $this->prophesize(EventDispatcherInterface::class);
+    $account_proxy = new AccountProxy($dispatcher->reveal());
     $this->assertSame(0, $account_proxy->id());
     $account_proxy->setInitialAccountId(1);
     $this->assertFalse(\Drupal::hasContainer());
@@ -36,20 +38,11 @@ public function testId() {
    */
   public function testSetInitialAccountIdException() {
     $this->expectException(\LogicException::class);
-    $account_proxy = new AccountProxy();
+    $dispatcher = $this->prophesize(EventDispatcherInterface::class);
+    $account_proxy = new AccountProxy($dispatcher->reveal());
     $current_user = $this->prophesize(AccountInterface::class);
     $account_proxy->setAccount($current_user->reveal());
     $account_proxy->setInitialAccountId(1);
   }
 
 }
-
-namespace Drupal\Core\Session;
-
-if (!function_exists('drupal_get_user_timezone')) {
-
-  function drupal_get_user_timezone() {
-    return date_default_timezone_get();
-  }
-
-}
-- 
GitLab