Problem/Motivation

As part of removing procedural code in includes, we should move drupal_get_user_timezone() from bootstrap.inc to a service in system module.

This process has uncovered the following:

  1. There is a dependency of core on system module in Drupal\Core\EventSubscriber\AuthenticationSubscriber and Drupal\Core\Session\AccountProxy
  2. There is a circular dependency in 'current_user' service: AccountProxy::setAccount() -> drupal_get_user_timezone() -> \Drupal::currentUser();
  3. In AccountProxy::setAccount() we are calling date_default_timezone_set(drupal_get_user_timezone()) which relies on a kind of global state that other code relies on.

Proposed resolution

  • Create an event listener in system module which sets the timezone based on site and user configuation using the standard date_default_timezone_set().
  • Replace usages of drupal_get_user_timezone() with date_default_timezone_get().
  • This also includes a fix for a timezone bug that was exposed by having a more reliable active-timezone switch that did not work in some cases before and was therefor almost impossible to test and now the tests simply failed. See #68 and #2861986: Plus one day on Date only fields with custom display widget for pacific/auckland timezone

Remaining tasks

Do it

User interface changes

none

API changes

drupal_get_user_timezone() is deprecated.

Constructor and implementation changes for AccountProxy that are very hard to have 100% BC because there was previously no constructor and we know of at least one contributed module that completely overrides several methods and that will either break or cause broken timezone behavior. The maintainers were notified and plan to rewrite their code: https://github.com/acquia/commerce-manager/issues/117.

Data model changes

none

Release notes snippet

Drupal no longer uses its own function for getting the default time zone, and now relies on the PHP function date_default_timezone_get().

The logic for setting the default timezone has been moved to events that is handled by the TimeZoneResolver which uses date_default_timezone_set().

The events are:

  1. A request via KernelEvents::REQUEST
  2. When time zone config changes, via ConfigCrudEvent
  3. When a user is set via a a new AccountSetEvent

The TimeZoneResolver handles these events and uses the same fall back logic prior to 8.7.0:

  1. Use the user-specified timezone, or if not set
  2. Use the site default configured timezone, or if not set
  3. Use the system default timezone
CommentFileSizeAuthor
#127 3009377-127-interdiff.txt1.17 KBkim.pepper
#127 3009377-127.patch33.4 KBkim.pepper
#124 3009377-124.patch33.39 KBacbramley
#115 3009377-115.patch33.31 KBandypost
#115 interdiff.txt1.04 KBandypost
#112 3009377-112.patch33.31 KBkim.pepper
#112 3009377-112-interdiff.txt2.07 KBkim.pepper
#109 3009377-109.patch33.31 KBkim.pepper
#109 3009377-109-interdiff.txt2.26 KBkim.pepper
#105 3009377-105.patch31.5 KBkim.pepper
#105 3009377-105-interdiff.txt3.22 KBkim.pepper
#100 3009377-80.patch32.95 KBmpdonadio
#93 3009377-93.patch32.95 KBandypost
#93 interdiff.txt2.91 KBandypost
#80 3009377-80.patch32.95 KBalexpott
#80 79-80-interdiff.txt1.33 KBalexpott
#79 3009377-timezone-79.patch34.18 KBWim Leers
#79 interdiff.txt3.85 KBWim Leers
#77 3009377-timezone-77.patch35.05 KBkim.pepper
#77 3009377-timezone-77-interdiff.txt1.22 KBkim.pepper
#75 3009377-timezone-75.patch35.55 KBkim.pepper
#75 3009377-timezone-75-interdiff.txt4 KBkim.pepper
#68 3009377-user-timezone-68-interdiff.txt1.8 KBBerdir
#68 3009377-user-timezone-68.patch37.07 KBBerdir
#66 3009377-user-timezone-66.patch35.27 KBkim.pepper
#66 3009377-user-timezone-66-interdiff.txt1.66 KBkim.pepper
#62 3009377-user-timezone-62.patch35.23 KBkim.pepper
#62 3009377-user-timezone-62-interdiff.txt3.29 KBkim.pepper
#58 3009377-user-timezone-58.patch32.87 KBkim.pepper
#58 3009377-user-timezone-58-interdiff.txt3.76 KBkim.pepper
#55 3009377-user-timezone-54.patch32.86 KBkim.pepper
#55 3009377-user-timezone-54-interdiff.txt3.81 KBkim.pepper
#53 3009377-user-timezone-53.patch32.55 KBkim.pepper
#53 3009377-user-timezone-53-interdiff.txt4.47 KBkim.pepper
#50 3009377-user-timezone-50.patch29.4 KBkim.pepper
#50 3009377-user-timezone-50-interdiff.txt9.99 KBkim.pepper
#45 3009377-user-timezone-45.patch34.1 KBkim.pepper
#45 3009377-user-timezone-45-interdiff.txt798 byteskim.pepper
#37 3009377-user-timezone-37.patch34.13 KBkim.pepper
#37 3009377-user-timezone-37-interdiff.txt1.32 KBkim.pepper
#35 3009377-user-timezone-35.patch53.77 KBkim.pepper
#35 3009377-user-timezone-35-interdiff.txt3.3 KBkim.pepper
#31 3009377-user-timezone-31.patch52.45 KBkim.pepper
#31 3009377-user-timezone-31-interdiff.txt1.98 KBkim.pepper
#29 3009377-user-timezone-29.patch52.07 KBkim.pepper
#29 3009377-user-timezone-29-interdiff.txt1.53 KBkim.pepper
#27 3009377-user-timezone-27.patch32.29 KBkim.pepper
#27 3009377-user-timezone-27-interdiff.txt62.85 KBkim.pepper
#16 3009377-user-timezone-16.patch51.94 KBkim.pepper
#16 3009377-user-timezone-16-interdiff.txt8.38 KBkim.pepper
#14 3009377-user-timezone-14.patch47.04 KBkim.pepper
#14 3009377-user-timezone-14-interdiff.txt18.86 KBkim.pepper
#11 3009377-user-timezone-11.patch47.22 KBkim.pepper
#11 3009377-user-timezone-11-interdiff.txt45.46 KBkim.pepper
#5 3009377-user-timezone-5.patch43.52 KBkim.pepper
#5 3009377-user-timezone-5-interdiff.txt25.25 KBkim.pepper
#2 3009377-user-timezone-2.patch26.07 KBkim.pepper
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

kim.pepper created an issue. See original summary.

kim.pepper’s picture

Status: Active » Needs review
FileSize
26.07 KB

I've put this in system module. Not sure if it should be in a sub-dir or not?

Status: Needs review » Needs work

The last submitted patch, 2: 3009377-user-timezone-2.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

larowlan’s picture

Great work!

  1. +++ b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
    @@ -71,7 +71,7 @@ public function __construct($time = 'now', $timezone = NULL, $settings = []) {
    +      $timezone = \Drupal::service('system.user_timezone_handler')->getUserTimeZone();
    

    sob, singleton access in a value object constructor. this is why we can't have nice things

    We almost need a factory service for date time objects (follow up) or a follow up to deprecate this codepath so that in d9 we can make the timezone required.

    In core the only place calling it without a timezone is in a test.

  2. +++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
    @@ -81,7 +81,7 @@ public function onKernelRequestAuthenticate(GetResponseEvent $event) {
    +      date_default_timezone_set(\Drupal::service('system.user_timezone_handler')->getUserTimeZone());
    
    +++ b/core/lib/Drupal/Core/Session/AccountProxy.php
    @@ -49,7 +49,7 @@ public function setAccount(AccountInterface $account) {
    +    date_default_timezone_set(\Drupal::service('system.user_timezone_handler')->getUserTimeZone());
    
    +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php
    @@ -171,7 +171,7 @@ protected function setTimeZone(DrupalDateTime $date) {
    +      $timezone = \Drupal::service('system.user_timezone_handler')->getUserTimeZone();
    
    +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeWidgetBase.php
    @@ -22,7 +22,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
    +      '#date_timezone' => \Drupal::service('system.user_timezone_handler')->getUserTimeZone()(),
    
    +++ b/core/modules/datetime/src/Plugin/views/filter/Date.php
    @@ -151,7 +151,7 @@ protected function opSimple($field) {
    +      : \Drupal::service('system.user_timezone_handler')->getUserTimeZone();
    
    @@ -173,7 +173,7 @@ protected function getOffset($time, $timezone) {
    +      $origin_offset = $origin_offset + timezone_offset_get(new \DateTimeZone(\Drupal::service('system.user_timezone_handler')->getUserTimeZone()), new \DateTime($time, new \DateTimeZone($timezone)));
    
    +++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeWidgetBase.php
    @@ -62,7 +62,7 @@ public function massageFormValues(array $values, array $form, FormStateInterface
    +    $user_timezone = new \DateTimeZone(\Drupal::service('system.user_timezone_handler')->getUserTimeZone());
    

    we can inject these

  3. +++ b/core/modules/system/tests/src/Kernel/UserTimezoneHandlerTest.php
    @@ -0,0 +1,40 @@
    +    // Uninstalling modules requires the users_data table to exist.
    

    c/p?

  4. +++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
    @@ -224,10 +224,11 @@ public function getDateField($field, $string_date = FALSE, $calculate_offset = T
    +    return \Drupal::service('system.user_timezone_handler')->getUserTimeZone();
    

    and this one can be injected too

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
25.25 KB
43.52 KB

Thanks @larowlan. I expect there to be a few tests breaks after making these changes, but here goes...

  1. Created a follow up #3009401: Create a factory for datetime objects and added a todo
  2. fixed
  3. fixed
  4. fixed

Status: Needs review » Needs work

The last submitted patch, 5: 3009377-user-timezone-5.patch, failed testing. View results

kim.pepper’s picture

Hmm. Seems almost every test fail is due to needing to depend on system module now.

kim.pepper’s picture

Ah, it's actually because of...

Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The service "authentication_subscriber" has a dependency on a non-existent service "system.user_timezone_handler".

'authentication_subscriber' is in core.services.yml and is trying to inject 'system.user_timezone_handler'

larowlan’s picture

So its a bit of rats' nest you're unravelling here, and basically you've discovered a hidden dependency from core to the system module.

I'm thinking we'll need a default timezone handler in core namespace, that doesn't support configuration options and then a service provider in system module that swaps out the default implementation for the configurable one - thoughts?

Great work!

kim.pepper’s picture

Yep that makes sense. I think there are a few usages which actually just want the default timezone but are calling drupal_get_user_timezone() for convenience (see \Drupal\Tests\Core\Session\AccountProxyTest)

kim.pepper’s picture

I have moved the interface and a default system implementation to \Drupal\Core\Datetime\SystemTimeZoneHandler then injected that into \Drupal\system\UserTimeZoneHandler

For anything under core/lib, I've switched to using a new 'date.timezone_handler' service. I've put @todo's next to the core/lib usages which I think were incorrectly depending on system module.

Let's see what breaks.

Status: Needs review » Needs work

The last submitted patch, 11: 3009377-user-timezone-11.patch, failed testing. View results

kim.pepper’s picture

Hmm.

Circular reference detected for service "current_user", path: "current_user -> date.timezone_handler -> current_user".
kim.pepper’s picture

I've sidestepped the circular dependency by calling \Drupal::service('date.timezone_handler')->getTimeZone() in AccountProxy instead of injecting the service.

I also created SystemServiceProvider to replace the simple core 'date.timezone_handler' with the configurable one in the system module.

Status: Needs review » Needs work

The last submitted patch, 14: 3009377-user-timezone-14.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kim.pepper’s picture

Fixes SqlTest fails.

I'm still perplexed by why we are setting this here? This is like setting a global variable that other code depends on. It would be better for that code to use the timezone service?

+++ b/core/lib/Drupal/Core/Session/AccountProxy.php
@@ -49,7 +51,8 @@ public function setAccount(AccountInterface $account) {
-    date_default_timezone_set(drupal_get_user_timezone());

I've removed it to see what breaks. :-)

Status: Needs review » Needs work

The last submitted patch, 16: 3009377-user-timezone-16.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

kim.pepper’s picture

Issue summary: View changes

Updating issue summary with research done so far.

larowlan’s picture

I'm +1 for the resolver approach, have asked @alexpott and @catch their thoughts too.

alexpott’s picture

A few thoughts...

  1. +++ b/core/lib/Drupal/Core/Session/AccountProxy.php
    @@ -49,7 +49,6 @@ public function setAccount(AccountInterface $account) {
    -    date_default_timezone_set(drupal_get_user_timezone());
    

    Removing this feels tricky. Is this responsible for setting the system timezone correctly?

  2. +++ b/core/modules/system/tests/src/Kernel/UserTimeZoneDeprecationTest.php
    @@ -0,0 +1,31 @@
    +    // Avoid risky warnings.
    +    $this->assertNotNull(drupal_get_user_timezone());
    

    I think we set the timezone in tests so this can probably assert against an expected value rather than NotNull().

  3. +++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
    @@ -47,6 +50,42 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
    +    $this->userTimeZoneHandler = $userTimeZoneHandler;
    

    Whilst views is probably dependent on user - should this be called timeZoneHandler?

  4. +1 for the resolver approach too - much better than the service modification going on in the current patch.
  5. I'm not 100% sure about the need for core and system resolvers. I would have thought a core and a user resolver are all that is necessary. The system / core config dependency thing is not really a problem. Nothing breaks - config returns nulls and code is often written to expect that. This is by far not the only case of this. I think the core / system divide is a fiction of our own making.
catch’s picture

I don't think the core/system divide is a fiction of our own making, we're still in the process of removing direct, hard, circular dependencies between things like the extension system, installer, and system module itself.

https://api.drupal.org/api/drupal/core%21includes%21install.inc/function... (still a hard dependency)

https://api.drupal.org/api/drupal/core%21modules%21system%21system.modul... (only just deprecated in 8.6.x).

Having said that, configuration objects which might or might not exist and are handled gracefully if they aren't are a bit different to having to install a module in order to have a functional module installation system, so don't have a massively strong opinion on 2 vs. 3 services.

I also wonder though, why not move the configuration to core like config.extension.yml? i.e. core.timezone.yml or similar?

dawehner’s picture

One thing which confuses me is that we still have the actual configuration in system module. Given the timezone seems essentially for booting up Drupal, couldn't we place the configuration file inside core itself, and system module just provides a UI for it? This way we would have to have one less service involved.

System module for me its just a excuse for things which could be part of core anyway.

kim.pepper’s picture

I'm still not sure how we resolve the circular dependency in the 'current_user' service:
AccountProxy::setAccount() calls drupal_get_user_timezone() which calls \Drupal::currentUser();

kim.pepper’s picture

kim.pepper’s picture

Worked with @larowlan on this, and came up with an alternative approach:

  • Instead of a service to return the time zone, use date_default_timezone_get() and date_default_timezone_set()
  • AccountProxy::setAccount() is used to trigger a new event to the default timezone in TimeZoneResolver. This removes the circular dependency.
  • A default TimeZoneProvider in core and a configurable TimeZoneProvider in system module added as tagged services

Status: Needs review » Needs work

The last submitted patch, 27: 3009377-user-timezone-27.patch, failed testing. View results

kim.pepper’s picture

  1. Fixed ConfigurableTimezoneProviderTest
  2. Listen for ConfigEvents::SAVE events so we resolve if 'timezone.default' gets updated.

Status: Needs review » Needs work

The last submitted patch, 29: 3009377-user-timezone-29.patch, failed testing. View results

kim.pepper’s picture

I reverted the ConfigEvents::SAVE events as it seemed to cause other issues.

I've added DependencySerializationTrait to AccountProxy as that was the cause of a few fails.

Status: Needs review » Needs work

The last submitted patch, 31: 3009377-user-timezone-31.patch, failed testing. View results

andypost’s picture

The approach looks nice and reminds me how we did configurable language manager

Berdir’s picture

Issue tags: +needs profiling

Lets make sure we are profiling this, because we are adding quite a bit of stuff/services of something that happens pretty early in bootstrap on all (authenticated?) requests.

kim.pepper’s picture

Fixes the DateTime test.

Re #34 Good idea! Any tips?

Status: Needs review » Needs work

The last submitted patch, 35: 3009377-user-timezone-35.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
1.32 KB
34.13 KB

Trying again...

kim.pepper’s picture

Re: #24 my profiling on Blackfire.io didn't show any significant difference. https://blackfire.io/profiles/compare/683295ae-0fc4-4b44-9c67-d1c85868fe...

kim.pepper’s picture

Issue tags: -needs profiling

I realised I profiled with xdebug enabled. Here's a diff with xdebug disabled. I still don't see any significant difference https://blackfire.io/profiles/compare/4c443cc0-46f5-4e5f-9902-dd9ac93ee5...

Berdir’s picture

Yes, profiling this low-level stuff is tricky, the first link had huge differences (20%) but as you said, there was xdebug and also quite a few other changes that seem to be irrelevant, like different amount of queries. However, the second link doesn't have any difference at all, the amount of calls is identical, so that doesn't seem right either.

kim.pepper’s picture

I seem to have borked my local Blackfire setup. If anyone else feels like posting profiling data, please go ahead.

kim.pepper’s picture

OK. I think I got it sorted. Here's another comparison @Berdir Let me know if this one makes sense https://blackfire.io/profiles/compare/93320c35-fd05-44c6-a0d0-f66fb7b284...

Berdir’s picture

As discussed, that result still didn't show any call differences.

I did now try it on a extremely minimal route, I picked system.timezone for that, specifically this request: http://d8/system/timezone/nzdt/-1/1.

The blackfire extension somehow misbehaved on that page, probably because of the way my firefox shows the json data there. So I did the request on the console, as anon as well as with a cookie for uid 1. I had the page cache disabled on purpose.

In all cases, the patch was between 6-8% faster but we're talking about response times of 21-23ms, so the difference is 1-2ms.

https://blackfire.io/profiles/compare/65cf67d3-7012-4a10-876a-ea343fc6e6...

Things that we can see
* 3 more classes being loaded
* However, we also save a bunch of things, one event subscriber goes away and with it the call to drupal_get_user_timezone and its \Drupal::config() call.

+++ b/core/modules/system/src/ConfigurableTimeZoneProvider.php
@@ -0,0 +1,51 @@
+   */
+  public function getTimeZone() {
+    if ($this->currentUser && $this->dateConfig->get('timezone.user.configurable') && $this->currentUser->isAuthenticated()) {
+      return $this->currentUser->getTimeZone();
+    }
+    return $this->dateConfig->get('timezone.default');

this->currentUser is a required constructor argument, so I don't think it can ever be NULL?

However, I would suggest we move the is authenticated check first and also only inject the config factory and not already load the config.

The advantage is that we need the config load call only if the user is actually authenticated

The thing that I'm not fully convinced about is whether we really need this to be a tagged service. That is a pattern that is useful if we have to expect multiple service providers that have different weights and on different requests, different services that act on it. In this case, it seems very unlikely that contrib will add additional implementations, it's really just a pattern to decouple core and system and in this case, the pattern that the LanguageManager/ConfigurableLanguageManager are using seems like a better match. That would mean we'd just have a single service in core and we replace/override that in the system module, either by just redefining it in system.services.yml (still not sure how reliable that is) or using ServiceProvider.

Personally, I don't really think that we would even need that level of decoupling, I agree that there is a distinction between system.module and core, but 90% of that is for historical reasons, e.g. all the configuration in system.module is mostly just there because the ability to have config in the core namespace (and especially config entities) only came pretty late in the D8 development cycle if I remember correctly. And the long-term goal is IMHO to move things *out* of it and not adding more to it, but that's just me :) And I'm OK with this approach if it means everyone else is OK with it too.

Berdir’s picture

> The advantage is that we need the config load call only if the user is actually authenticated

Correction: That is wrong as the else case also reads from configuration, so we do always need it.

There *would* be a way to avoid the config read at runtime for the non-configurable case (which I suppose is 90% of the sites?), if we would handle this in a similar way as LanguageServiceProvider does with some of the language configuration. Read it in the service provider and then set it there on the container/service, could be two constructor arguments for example, so at runtime, we'd only need to do something like this:

if ($this->isConfigurable && $this->currentUser->isAuthenticated()) {
  return $this->currentUser->getTimezone();
}
return $this->defaultTimezone;

I'm not quite sure about that, the downside is that we need a config listener that forces a container rebuild if either of those two settings change. Similar to the check in \Drupal\language\Entity\ConfigurableLanguage::postSave(). On the other side, doing that could actually even result in a performance *improvement* compared to HEAD (small, but every config we *don't* have to read during bootstrap is a good thing) which would be an additional argument for getting this committed compared to just being able deprecate an old include file function. And I guess that 99% of the sites out there never change that setting anymore after the initial configuration.

kim.pepper’s picture

Thanks for the review.

> this->currentUser is a required constructor argument, so I don't think it can ever be NULL?

Fixed.

I think the logic in \Drupal\Core\Datetime\TimeZoneResolver::resolveTimeZone() is the important design change. We're iterating through providers, calling setting date_default_timezone_set($timeZone). All other code is then using date_default_timezone_get() instead of drupal_get_user_timezone().

TimeZoneResolver is also an event listener, so can be triggered to reset it if something changes.

I'm not sure if the ServiceProvider pattern handles that without needing additional logic for handling events?

Status: Needs review » Needs work

The last submitted patch, 45: 3009377-user-timezone-45.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Needs review

DrupalCI hiccup. Back to needs review.

Berdir’s picture

> I think the logic in \Drupal\Core\Datetime\TimeZoneResolver::resolveTimeZone() is the important design change. We're iterating through providers, calling setting date_default_timezone_set($timeZone). All other code is then using date_default_timezone_get() instead of drupal_get_user_timezone().

Yes, I get this part, my proposal would by to simply call this->getTimezone() or so and then system.module would replace the service with its own class that extends the default implementation and overrides getTimezone(). The advantage is that we don't need (yet another) service tag collection process, weight handling/sorting and calling out to other services.

Nothing would change with the events that class implements.

The tagged service approach has to instantiate 3 services (and load the interface), a simple service that's replaced by system.module would have to load just one (+ the interface).

andypost’s picture

@Berdir your implementation is what I supposed in #33 btw it will require to rebuild container on setting (config) change (like locale doing)
OTOH this change will affect caching - if no configurable time zones allowed we get less cache variations

kim.pepper’s picture

I think I've got the approach @Berdir was suggesting.

This doesn't require a ServiceProvider because there is no core implementation to override.

Let's see what the bot says!

Status: Needs review » Needs work

The last submitted patch, 50: 3009377-user-timezone-50.patch, failed testing. View results

alexpott’s picture

+++ b/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php
@@ -98,6 +98,7 @@ protected function setSiteTimezone($timezone) {
+    $this->container->get('system.timezone_resolver')->resolveTimeZone();

+++ b/core/modules/system/src/ConfigurableTimeZoneResolver.php
@@ -0,0 +1,66 @@
+    if ($this->dateConfig->get('timezone.user.configurable') && $this->currentUser->isAuthenticated()) {
+      date_default_timezone_set($this->currentUser->getTimeZone());
+    }

+++ b/core/modules/system/system.services.yml
@@ -43,3 +43,8 @@ services:
+    class: Drupal\system\ConfigurableTimeZoneResolver

Whilst there is no way to set it via the UI there is code to suggest that a NULL is possible and it is not a explicitly required base field. The original code check if $user->getTimeZone() returns a value. I think we should continue to do that here and open a follow-up to discuss the required-ness. Also see \Drupal\user\UserInterface::TIMEZONE_EMPTY (apparently dead code) - ah no - bad deprecated code :( and system_user_login()

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
4.47 KB
32.55 KB

Thanks @alexpott I've re-added the NULL user timezone check, and replaced a couple of deprecated usages.

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/system/src/ConfigurableTimeZoneResolver.php
    @@ -0,0 +1,66 @@
    +
    +/**
    + * A configurable time zone provider.
    + */
    +class ConfigurableTimeZoneResolver implements EventSubscriberInterface {
    

    Hm, so no we got rid of the interface completely, that did go one step further than I imagined :)

    Not sure if we should keep that or not. Also, now that we no longer have a non-configurable implementation, does it make sense to name this one "Configurable..."?

  2. +++ b/core/modules/system/src/ConfigurableTimeZoneResolver.php
    @@ -0,0 +1,66 @@
    +   */
    +  public function resolveTimeZone() {
    +    if ($this->dateConfig->get('timezone.user.configurable') && $this->currentUser->isAuthenticated() && $this->currentUser->getTimezone()) {
    +      date_default_timezone_set($this->currentUser->getTimeZone());
    +    }
    +    elseif ($defaultTimeZone = $this->dateConfig->get('timezone.default')) {
    +      date_default_timezone_set($defaultTimeZone);
    +    }
    +  }
    

    Now you merged it together completely, I would still keep a separate protected method that returns the value, then it would still be an option to sublclass if someone wants to do something crazy.

    My (crazy) idea about keeping the config value as a pre-calculated service argument would still require a ServiceProvider, but we could still consider that in a second phase, it's probably too much for a single issue.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
3.81 KB
32.86 KB
  1. Discussed this with @Berdir in slack and agreed and interface isn't needed as it's an event listener and there are no methods being called on this service anywhere.
  2. Split out protected function getTimeZone()
  3. Agreed!
kim.pepper’s picture

Title: Replace drupal_get_user_timezone with a service » Replace drupal_get_user_timezone with a date_default_timezone_get() and an event listener
Issue summary: View changes

Updated title, issue summary and change record to reflect current solution.

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/system/src/TimeZoneResolver.php
    @@ -41,14 +45,11 @@ public function __construct(AccountInterface $currentUser, ConfigFactoryInterfac
    +    if ($timeZone = $this->getTimeZone()) {
    +      date_default_timezone_set($timeZone);
         }
    

    I think our current coding standard still requires $time_zone for local variables?

  2. +++ b/core/modules/system/src/TimeZoneResolver.php
    @@ -63,4 +64,20 @@ public static function getSubscribedEvents() {
    +  /**
    +   * Get the time zone based on site and user configuration.
    

    AFAIK the first word should be third-person, so Gets (or Returns).

    Also not sure about the Resolve on the resolveTimeZone()

    And while we're on naming, not sure if it should be TimeZone or Timezone, I think the second because that's what PHP and our own existing methods does as well: http://php.net/manual/de/datetime.gettimezone.php.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
3.76 KB
32.87 KB
  1. Discussed with @Berdir in slack and agreed this was inline with coding standards https://www.drupal.org/docs/develop/standards/coding-standards#naming
  2. Changed Get to Gets.

Also not sure about the Resolve on the resolveTimeZone()

We agreed to change this to setDefaultTimeZone()

And while we're on naming, not sure if it should be TimeZone or Timezone, I think the second because that's what PHP and our own existing methods does as well: http://php.net/manual/de/datetime.gettimezone.php.

Agreed that although both are correct, TimeZone was inline with \Drupal\Core\Session\AccountInterface::getTimeZone()

Berdir’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php
@@ -98,6 +98,7 @@ protected function setSiteTimezone($timezone) {
       ->set('timezone.user.configurable', 0)
       ->set('timezone.default', $timezone)
       ->save();
+    $this->container->get('system.timezone_resolver')->setDefaultTimeZone();
   }
 

I think this looks great now, the only thing I'm not sure about is these necessary test changes.

It will only affect the current request, so it might be OK, but we could also implement/extend a config save event subscriber that would call out to setDefaultTimeZone() if one of those two values change. Since we already have \Drupal\system\SystemConfigSubscriber, I expect it would only require a few additional lines for that.

Setting to RTBC to get feedback from the committers about this.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Doing things in config listeners is great - it solves a whole class of bugs in config import and using drush or console to make changes so +1 to @Berdir's idea in #59

catch’s picture

It will only affect the current request, so it might be OK, but we could also implement/extend a config save event subscriber that would call out to setDefaultTimeZone() if one of those two values change. Since we already have \Drupal\system\SystemConfigSubscriber, I expect it would only require a few additional lines for that.

I think we should do that, CNW for that change.

Personally, I don't really think that we would even need that level of decoupling, I agree that there is a distinction between system.module and core, but 90% of that is for historical reasons, e.g. all the configuration in system.module is mostly just there because the ability to have config in the core namespace (and especially config entities) only came pretty late in the D8 development cycle if I remember correctly. And the long-term goal is IMHO to move things *out* of it and not adding more to it

I agree with this - system.module should have as little code as possible. However in that case, why not move this configuration to somewhere in core* here too?

kim.pepper’s picture

Thanks @alexpott and @catch

Re #61

  1. Added to \Drupal\system\SystemConfigSubscriber::onConfigSave(). I also (re)added the interface, because now we are calling the service. :-)
  2. Is moving config (e.g. 'system.date') to core lib a BC break? I couldn't see anything about config in the BC policy https://www.drupal.org/core/d8-bc-policy

Status: Needs review » Needs work

The last submitted patch, 62: 3009377-user-timezone-62.patch, failed testing. View results

Berdir’s picture

Wondering if those test fails are caused because we immediately read the new value *and* we do so on the inject config object. I suspect that still sees the old value? So if we really do this, we need to only read it directly from configFactory, to make sure we always have the latest information.

2. Not sure, but changing config is something we need to be very careful about. I've done things like that in pathauto and paragraphs recently and we got a lot of support requests/bug reports because people are using incorrect workflows and are immediately reverting that config change by re-importing old configuration after running updates. So I would be -1 on doing that just to make things a bit "nicer".

kim.pepper’s picture

Issue tags: +DrupalSouth 2018
kim.pepper’s picture

Thank you @Berdir. Let's try that.

Status: Needs review » Needs work

The last submitted patch, 66: 3009377-user-timezone-66.patch, failed testing. View results

Berdir’s picture

Status: Needs work » Needs review
FileSize
37.07 KB
1.8 KB

I'm so confused about these tests and how our date formatting works, we're converting so many times between timezone and not-timezone and timestamps and date objects, it's absolutely insane :)

But, as far as I can tell, this identified an actual, real bug.

Interestingly, the test only failed on plain and custom date formatter and not the default. They are almost identical, but not *quite*. They didn't get the change/bugfix in #2739290: UTC+12 is broken on date only fields.

So what's happening is that we are rendering a date as just a date, without time, so \Drupal\datetime\Plugin\Field\FieldFormatter\DateTimeFormatterBase::setTimeZone() skips the timezone and forces UTC. But then \Drupal\datetime\Plugin\Field\FieldFormatter\DateTimePlainFormatter::formatDate() calls format() with NULL as timezone, which makes it then fall back to the default. Which is Pacific/Kwajalein, which then adds 12h and makes it switch over to the next date.

The fun part is why we haven't seen this before. Unlike other places, \Drupal\Core\Datetime\DateFormatter::format() already called date_default_timezone_get() directly, and not through our wrapper that returns the configured timezone. So it did not *actually* see the timezone set by \Drupal\Tests\datetime\Functional\DateTestBase::setSiteTimezone() because before, we only set that again when changing the current user. So we've really always been testing that with the default sydney timezone. Now we automatically update it for the current process. It's also fun how that test loops over all the different timezones, but what we're really doing is just ensuring that we are enforcing UTC over and over again :)

@jhedstrom also pointed out there is an existing issue for this problem and I came up with the same fix. Except there we needed additional changes to the test to make it fail: #2861986: Plus one day on Date only fields with custom display widget for pacific/auckland timezone.

Berdir’s picture

Cross-referencing stuff, see #2946826: Add a method to format a DrupalDateTime object to DateFormatter on ideas to make this whole date/timezone/timestamp converting easier to follow.

kim.pepper’s picture

Assigned: kim.pepper » Unassigned
Status: Needs review » Reviewed & tested by the community

Great find @Berdir!! All feedback addressed so back to RTBC

Wim Leers’s picture

But, as far as I can tell, this identified an actual, real bug. […] The fun part is why we haven't seen this before.

This bug is very similar to the one reported at and fixed in #3002164: DateTimeIso8601::getDateTime() does not specify the storage timezone when constructing DrupalDateTime object. And I think that issue may be solving the same bug as #68's interdiff at a lower level: in a @DataType class rather than a formatter.

I don't have time to verify right now whether that is indeed solving the same problem at a lower level like I suspect, but we definitely should investigate it.

We discovered it from an API-First POV. You say that @jhedstrom discovered it through a widget. And you discovered the same thing here. So it seems like we're all discovering the same problems deep in our date handling…

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

@WimLeers, no, I don't think that will help.

The problem is the mentioned disconnect between our date fields/formatters, which uses DateTime objects, and the date formatting API which has a timestamp + timezone API only. So we have to deal with the timezone explicitly.

What's possible is that it would allow to simplify \Drupal\datetime\Plugin\Field\FieldFormatter\DateTimeFormatterBase::setTimeZone as the explicit call to setting the storage timezone then might not be needed anymore. But I that isn't related to this issue/problem.

mpdonadio’s picture

I think

The problem is the mentioned disconnect between our date fields/formatters, which uses DateTime objects, and the date formatting API which has a timestamp + timezone API only. So we have to deal with the timezone explicitly.

is the correct assessment here. There is way, way too much shuffling amongst \DateTime, DrupalDateTime, DateTimePlus, ISO strings, and timestamps everywhere in core, but a lot of that is because of what is advertised on public APIs.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php
    @@ -98,6 +98,7 @@ protected function setSiteTimezone($timezone) {
    +    $this->container->get('system.timezone_resolver')->setDefaultTimeZone();
    

    Should this happen on a config event? Ah it is already done... I guess this is not needed.

  2. +++ b/core/modules/system/src/SystemConfigSubscriber.php
    @@ -42,6 +52,9 @@ public function onConfigSave(ConfigCrudEvent $event) {
    +    if ($saved_config->getName() == 'system.date' && ($event->isChanged('timezone.default') || $event->isChanged('timezone.user.configurable'))) {
    +      $this->timeZoneResolver->setDefaultTimeZone();
    +    }
    

    <3 ... however thinking about this some more it might be even neater to subscribe to the config event in TimeZoneResolver since that is an event subscriber too. It keeps everything nicely together.

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
4 KB
35.55 KB

Thanks @alexpott

  1. Fixed: Removed
  2. Fixed: Moved to TimeZoneResolver
Berdir’s picture

+++ b/core/modules/system/src/TimeZoneResolver.php
@@ -0,0 +1,100 @@
+/**
+ * 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, TimeZoneResolverInterface {
+

Should we remove the interface again? With the config event change, we again have no calls to this except the evens.

kim.pepper’s picture

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Nice, I really like having this all contained in one place.

Wim Leers’s picture

  1. +++ b/core/includes/bootstrap.inc
    @@ -530,20 +530,15 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
    + * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0.
    + *   Use date_default_timezone_get() instead.
    
    +++ b/core/lib/Drupal/Core/Datetime/DrupalDateTime.php
    @@ -71,7 +71,7 @@ public function __construct($time = 'now', $timezone = NULL, $settings = []) {
    -      $timezone = drupal_get_user_timezone();
    +      $timezone = date_default_timezone_get();
    

    🎉 Having worked on some datetime issues lately, anything that simplifies it is a good step forward!

  2. +++ b/core/lib/Drupal/Core/Session/AccountSetEvent.php
    @@ -0,0 +1,39 @@
    + * Wraps a configuration event for event listeners.
    

    "Wraps a configuration event" is a copy/paste leftover.

  3. +++ b/core/lib/Drupal/Core/Session/AccountSetEvent.php
    @@ -0,0 +1,39 @@
    +   * Gets the Account.
    ...
    +   *   The Account.
    

    s/A/a/

  4. +++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php
    @@ -3,8 +3,8 @@
    -use Drupal\Core\Field\FieldItemListInterface;
     use Drupal\Core\Field\FieldDefinitionInterface;
    +use Drupal\Core\Field\FieldItemListInterface;
    

    Out of scope change.

  5. +++ b/core/modules/system/src/TimeZoneResolver.php
    @@ -0,0 +1,100 @@
    +   * @var \Drupal\user\Plugin\views\argument_default\CurrentUser
    

    Wrong FQCN.

  6. +++ b/core/modules/system/src/TimeZoneResolver.php
    @@ -0,0 +1,100 @@
    +      return $defaultTimeZone;
    

    We only use camelCase for class members.

  7. +++ b/core/tests/Drupal/Tests/Core/Session/AccountProxyTest.php
    @@ -36,20 +38,11 @@ public function testId() {
    -if (!function_exists('drupal_get_user_timezone')) {
    -
    -  function drupal_get_user_timezone() {
    -    return date_default_timezone_get();
    -  }
    -
    -}
    

    Nice!

alexpott’s picture

+++ b/core/modules/system/tests/src/Functional/Datetime/DrupalDateTimeTest.php
@@ -54,6 +54,7 @@ public function testDateTimezone() {
+    $this->container->get('system.timezone_resolver')->setDefaultTimeZone();

@@ -71,6 +72,7 @@ public function testDateTimezone() {
+    $this->container->get('system.timezone_resolver')->setDefaultTimeZone();

@@ -80,6 +82,7 @@ public function testDateTimezone() {
+    $this->container->get('system.timezone_resolver')->setDefaultTimeZone();

Not needed - the listener does this.

Wim Leers’s picture

Thanks to #80, we can go one step further: we can mark system.timezone_resolver private 😀

However … when I rolled a patch to do exactly that, I noticed that nothing in core is doing this…

Berdir’s picture

No, our event subscriber implementation does not alllow private services.

Also, our coding standard documentation allows apparently camel case as long as it is consistently done in a file or so

Wim Leers’s picture

No, our event subscriber implementation does not alllow private services.

But … making it private still allows tests to pass 🤔

Also, our coding standard documentation allows apparently camel case as long as it is consistently done in a file or so

TIL

kim.pepper’s picture

Looks like @alexpott fixed all of @Wim Leers feedback in #80. 👍

alexpott’s picture

@Wim Leers to make a service in our container private doing private: TRUE doesn't work. The only way to make a service private is to do public: false and if you do that tests fail.

alexpott’s picture

  1. +++ b/core/core.services.yml
    @@ -1530,6 +1530,7 @@ services:
       current_user:
         class: Drupal\Core\Session\AccountProxy
    +    arguments: ['@event_dispatcher']
    
    +++ b/core/lib/Drupal/Core/Session/AccountProxy.php
    @@ -38,6 +43,23 @@ class AccountProxy implements AccountProxyInterface {
    +  /**
    +   * AccountProxy constructor.
    +   *
    +   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
    +   *   Event dispatcher.
    +   */
    +  public function __construct(EventDispatcherInterface $eventDispatcher) {
    +    $this->eventDispatcher = $eventDispatcher;
    +  }
    

    So I pondered what this might break and I found http://grep.xnddx.ru/search?text=extends+AccountProxy&filename= which leads us to https://cgit.drupalcode.org/acquia_commercemanager/tree/modules/acm/src/... / https://www.drupal.org/project/acquia_commercemanager

    I'm not sure anything will really break because they override the constructor and use the new class in other services and don't override the real current user as far as I can tell. But I've not looked that hard. I've looked for test coverage of the user features it offers which appear to be the ability to log in from an external ecommerce system but I can't find any :(

    Not sure what to do here. \Drupal\Core\Session\AccountProxy does not itself feel like an extension point. Sure inject the service places and consume the current user but there are many more supported ways of adding new ways to log in to Drupal. Plus I have no idea of the security implications of doing things the way this module does.

  2. +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
    @@ -55,7 +55,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -    $timezone = $this->getSetting('timezone_override');
    +    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
    
    +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
    @@ -42,7 +42,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -    $timezone = $this->getSetting('timezone_override');
    +    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
    

    Why is this change necessary? Ah this is explained in #68 - nice. Fixing bugs ftw.

Berdir’s picture

1. Yeah, OK, that point goes to you on the final-discussion, overriding that service/class for alternative authentication mechanisms seems like a bad idea when we have a pluggable authentication system.

Since it didn't have a constructor before this, they couldn't call it, but I guess they could have still used a setter-based injection.

I guess the only thing we can do to avoid breaking that is to get the event dispatcher through a getEventDispatcher() method that does a @trigger_error() when it's not already injected?

alexpott’s picture

@Berdir well they've also overridden setAccount() so that's not going to fire. And yeah stuff like this is why I think we need to use final more. We need to wrangle our extension points. It'll help everyone.

Berdir’s picture

Yeah, but the question is, what exactly would have happened if AccountProxy would have been final? The *only* thing that does is prevent subclasses. What would have likely happened is that the module author copied the AccountProxy class as so much was changed compared to the original anyway. And the result would have been the same, we would not have the new extension point/event that we are introducing here and the timezone handling would be silently broken for sites using that module.

Which is exactly my argument against using final too much, that it leads to copying whole classes and resulting in maybe less frequent but much more subtle and harder-to-identify bugs than a missing constructor argument, which will either result in a deprecated message or even a hard error. I get how that is a deal-breaker for automated updates, but for anyone with a managed process and testing environments, the second is IMHO preferable.

But we're getting off track with that discussion here. So maybe we should on purpose leave it like it is, reach out to the module authors (which both seem to be fairly active, and the module only has 2 reported/active installs) and hope that not too many custom sites did something similar.

kim.pepper’s picture

I created an issue in the commerce-manager issue queue https://github.com/acquia/commerce-manager/issues/117 and invited the maintainers to participate in this discussion.

mpdonadio’s picture

Re #68, "Interestingly, the test only failed on plain and custom date formatter and not the default.", I dug into this and it looks like the root cause goes all the way back to #2399211: Support all options from views fields in DateTime formatters.

andypost’s picture

This issue solves bug #2861986: Plus one day on Date only fields with custom display widget for pacific/auckland timezone

+++ b/core/lib/Drupal/Core/Session/AccountEvents.php
@@ -0,0 +1,28 @@
+final class AccountEvents {
...
+  const SET_USER = 'account.set';

+++ b/core/lib/Drupal/Core/Session/AccountSetEvent.php
@@ -0,0 +1,39 @@
+class AccountSetEvent extends Event {

Maybe instead of AccountEvents class just add const $eventName to To AccoutSetEvent class? Related #2825358: Event class constants for event names can cause fatal errors when a module is installed

andypost’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
2.91 KB
32.95 KB

Not sure about constant naming

Berdir’s picture

That issue hasn't been updated in more than a year and I don't think there are any existing events that follow what you just did? the discussion there isn't to *move* the constant, it's to remove it completely.

I'd rather not hold up an issue that was RTBC for so long about something like that.

andypost’s picture

@Berdir I see no rush here to get it in, but downsides of extra API-addition & useless autoloader call

Meanwhile no reason to block this change on #2825358: Event class constants for event names can cause fatal errors when a module is installed

kim.pepper’s picture

Miro Michalicka (@mirom) replied on that issue:

Hi, thanks for pointing out this issue. We're going to work on replacing our approach with your suggestions.

larowlan’s picture

Nice

andypost’s picture

Status: Needs review » Reviewed & tested by the community

Back to rtbc #80 abd commented other issue - let's keep current way

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 93: 3009377-93.patch, failed testing. View results

mpdonadio’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
32.95 KB

Spurious from the not-correct patch. Reuploading #80 so it is the one tested.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 100: 3009377-80.patch, failed testing. View results

kim.pepper’s picture

Status: Needs work » Reviewed & tested by the community

I believe this was mean to be rtbc

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

xjm’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record updates, +Needs release note
  1. +++ b/core/includes/bootstrap.inc
    @@ -530,20 +530,15 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
     function drupal_get_user_timezone() {
    +  @trigger_error('drupal_get_user_timezone() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use date_default_timezone_get() instead. https://www.drupal.org/node/3009387.', E_USER_DEPRECATED);
    +  return date_default_timezone_get();
    

    So this previously fetched the user's configured timezone and fell back to the site configured timezone followed by the results of the PHP function if there was none configured. Is this correct? If so, is there a behavior change when the user's configured timezone or the site configured timezone does not match that result?

    If there is a behavior change, we need to at a minimum document what it is in the CR. Right now it just tells you to replace the Drupal function use with the PHP base function, without explaining what impacts this might have. It might also need to be mentioned in the release notes for that reason and so we should add a release notes snippet.

    There's also a change where @ was previously being used with the function when it was the fallback, and it is not anymore. I'm guessing this is related to:

    // Ignore PHP strict notice if time zone has not yet been set in the php.ini
    // configuration.
    

    So that's another thing that changes besides the behavior (notices no longer suppressed), and probably also worth mentioning.

  2. +++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
    @@ -80,8 +80,6 @@ public function onKernelRequestAuthenticate(GetResponseEvent $event) {
           }
    -      // No account has been set explicitly, initialize the timezone here.
    -      date_default_timezone_set(drupal_get_user_timezone());
         }
       }
    

    What behavior changes might result from the removal of this fallback? Is it now returning NULL in cases where it wouldn't previously? Also might need to be documented in the CR?

  3. +++ b/core/lib/Drupal/Core/Session/AccountEvents.php
    @@ -0,0 +1,28 @@
    +final class AccountEvents {
    

    Not going to make a fuss, but I still don't think we should start peppering the codebase with final when there's disagreement about how we should use it.

  4. +++ b/core/lib/Drupal/Core/Session/AccountProxy.php
    @@ -38,6 +43,23 @@ class AccountProxy implements AccountProxyInterface {
    +  public function __construct(EventDispatcherInterface $eventDispatcher) {
    +    $this->eventDispatcher = $eventDispatcher;
    +  }
    

    Is there a reason we didn't try to provide some BC for this as described in #87? And also this should perhaps be mentioned in the CR if we've spotted actual contrib modules that might have been affected (even if upon further inspection they weren't).

  5. +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeCustomFormatter.php
    @@ -55,7 +55,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -    $timezone = $this->getSetting('timezone_override');
    +    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
    
    +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimePlainFormatter.php
    @@ -42,7 +42,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    -    $timezone = $this->getSetting('timezone_override');
    +    $timezone = $this->getSetting('timezone_override') ?: $date->getTimezone()->getName();
    

    Meanwhile this is changing behavior in he other direction. What impacts might that have and why is this one necessary?

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
3.22 KB
31.5 KB
  1. The logic is the same, just done in a different way. A the default time zone is set on the following events:
    • A request via KernelEvents::REQUEST
    • When time zone config changes, via ConfigCrudEvent
    • When a user is set via a a new AccountSetEvent

    The TimeZoneResolver handles these events and uses the same fall back logic:

    1. Use the user-specified timezone, or if not set
    2. Use the site default configured timezone, or if not set
    3. Use the system default timezone

    I've updated the change record with this info.

  2. I believe this is already handled as per above.
  3. Removed final
  4. I've added a @trigger_error for the new argument.
  5. Not entirely sure why that was necessary. I've reverted that change. Let's see if anything breaks.

Status: Needs review » Needs work

The last submitted patch, 105: 3009377-105.patch, failed testing. View results

alexpott’s picture

All the event constant classes are already final in core. This would be an outlier. They don't contain functionality and extending or overriding them makes no sense.

Berdir’s picture

On point 5, see #86 where alexpott asked the same and then he found #68 where I explained those changes in depth. In short, this makes timezone switching more reliable than before and that uncovered existing bugs where our test coverage didn't work because the timezone wasn't actually switched.

There was an existing bug about it that was closed as a duplicate because that issue was struggling to write a test for it and this has to fix it: #2861986: Plus one day on Date only fields with custom display widget for pacific/auckland timezone

kim.pepper’s picture

Status: Needs work » Needs review
FileSize
2.26 KB
33.31 KB

OK. I've restored #105.1 final and #105.5

I should have re-read the comment history!

kim.pepper’s picture

Updated IS with Release notes snippet.

Berdir’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Thanks, I've also added a note about the date/timezone bugfix that's included here to the issue sommary and also extended the API changes section with the AccountProxy stuff.

Back to RTBC, the feedback has been addressed.

I did ask in Slack if committing this to 8.7 is still an option, which would be nice as it does also fix bugs and isn't just a deprecation. But that is pretty unlikely as it contains the AccountProxy changes that will break at least one contrib module.

kim.pepper’s picture

Updates deprecation format and bumps to 8.8.0.

Berdir’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 112: 3009377-112.patch, failed testing. View results

andypost’s picture

kim.pepper’s picture

Status: Needs review » Reviewed & tested by the community
c7bamford’s picture

It would be nice to be able to pass in a user to get that user's timezone or default instead of the current user.

Berdir’s picture

If you want the timezone of a user then you can simply call $user->getTimezone(). The service that is added here is *not* an API, you are not supposed to call it, ever.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 115: 3009377-115.patch, failed testing. View results

Berdir’s picture

Status: Needs work » Reviewed & tested by the community

Random fail?

1) Drupal\Tests\media_library\FunctionalJavascript\MediaLibraryTest::testWidgetOEmbed
Behat\Mink\Exception\ElementNotFoundException: Form field with id|name|label|value "Select Another video" not
larowlan’s picture

I opened https://github.com/acquia/commerce-manager/pull/148/ for the commerce manager issue, their sub-class implementation is identical and therefore not needed

larowlan’s picture

https://github.com/acquia/commerce-manager/pull/148 is in, so this has no contrib impact now

kim.pepper’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll
acbramley’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
33.39 KB

Rerolled #115

kim.pepper’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC.

larowlan’s picture

Status: Reviewed & tested by the community » Needs review

Down to uber-nits now

  1. +++ b/core/lib/Drupal/Core/Session/AccountEvents.php
    @@ -0,0 +1,28 @@
    +class AccountEvents {
    

    as per #107 and other event classes in core, I think we should keep this as final as per earlier patches

  2. +++ b/core/modules/system/src/TimeZoneResolver.php
    @@ -0,0 +1,100 @@
    +    if ($saved_config->getName() == 'system.date' && ($event->isChanged('timezone.default') || $event->isChanged('timezone.user.configurable'))) {
    

    nit ===

kim.pepper’s picture

kim.pepper’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC for @larowlan's review.

andypost’s picture

Rtbc++

larowlan’s picture

larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed e47700f and pushed to 8.8.x. Thanks!

Published change record

  • larowlan committed e47700f on 8.8.x
    Issue #3009377 by kim.pepper, andypost, Berdir, alexpott, Wim Leers,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

jhedstrom’s picture

This seems to have caused some fairly serious regressions in terms of form entry for datetimes. See #3087606: Datetime::getInfo() caches user's timezone causing unpredictable timestamps. (core) and #3085947: Entered date is stored using cached timezone from previous user (scheduler). In manual testing, the Authored on node form entry now only displays in whatever the server's date is, even though the date/time displays correctly when viewing a node.

jhedstrom’s picture

As noted in #3087606-8: Datetime::getInfo() caches user's timezone causing unpredictable timestamps., I think this issue exacerbated an existing bug, rather than causing it outright.