I couldn’t find any similar issues on Drupal.org so I opened this one, if anyone knows of a similar issue please feel free to link and close this as duplicate.

Problem/Motivation

This was found while diagnosing an issue on the Scheduler module. However it also affects the “Authored On” field.
https://www.drupal.org/project/scheduler/issues/3085947

The node edit form's "Authored On" field uses the timezone of whatever user was on that form during the last cache clear. This can end up with a Chicago User saving a node that has an “Authored On” value in UTC.

This only seems to affect new nodes, editing existing nodes are not affected.

Steps to Reproduce
Requirements: Have two users, one with a UTC timezone and one with a Chicago Timezone

Step One: Create content as an UTC User

  1. Log in as the UTC user
  2. Clear the Drupal cache
  3. Create a new Basic Page
  4. Note the Authoring information (e.g 4:00:00 PM)
  5. Save and publish the node.

Step Two: Create content as a Chicago User

  1. Log in as the Chicago user
  2. Do not clear the cache
  3. Create a new Basic Page
  4. Note the Authoring information
  5. Observe that the Authoring information shows approximately the same time at UTC (e.g 4:02 PM)
  6. This should show a time relative to Chicago (e.g 11:00 AM)

For authoring information this is relatively benign however it does impact Scheduling.

Proposed resolution

It looks like this is being caused by code in the Datetime::getInfo() method.

core/lib/Drupal/Core/Datetime/Element/Datetime.php

public function getInfo() {

      // .. node: some code removed for brevity …

     return [
      '#input' => TRUE,
      '#element_validate' => [
        [$class, 'validateDatetime'],
      ],
      '#process' => [
        [$class, 'processDatetime'],
        [$class, 'processAjaxForm'],
        [$class, 'processGroup'],
      ],
      '#pre_render' => [
        [$class, 'preRenderGroup'],
      ],
      '#theme' => 'datetime_form',
      '#theme_wrappers' => ['datetime_wrapper'],
      '#date_date_format' => $date_format,
      '#date_date_element' => 'date',
      '#date_date_callbacks' => [],
      '#date_time_format' => $time_format,
      '#date_time_element' => 'time',
      '#date_time_callbacks' => [],
      '#date_year_range' => '1900:2050',
      '#date_increment' => 1,
      // Timezone is set here and gets cached. 
      '#date_timezone' => drupal_get_user_timezone(),
    ];
} 

The getInfo() method only gets called on cache clear, therefore the element that gets loaded gets the timezone of whichever user loads the page after the cache is cleared.

This bug has seemingly been in place for some time, but it was made much more visible with the deprecation of drupal_get_user_timezone() in #2799987: Datetime and Datelist element's timezone handling is fragile, buggy and wrongly documented. Prior to that the site's timezone would be used rather than the server's timezone when caches are built without a user (eg, via drush, or some other backend process.)

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

partdigital created an issue. See original summary.

partdigital’s picture

Issue summary: View changes
jonathan1055’s picture

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

Yes this is big problem on multi-location sites that are using Scheduler module.

First reported in #3085947: Entered date is stored using cached timezone from previous user then we realised it was a Core issue.

kim.pepper’s picture

This function was deprecated in #3009377: Replace drupal_get_user_timezone with a date_default_timezone_get() and an event listener in Drupal 8.8.0-dev. Are you able to test with the latest 8.8.x branch?

jonathan1055’s picture

Hi @kim.pepper,
Thanks for linking that issue. I have just tested with 8.8-dev updated as of today, and yes the problem is still there. You are right that in core/lib/Drupal/Core/Datetime/Element/Datetime.php, datetime::getInfo() method now has '#date_timezone' => date_default_timezone_get() not '#date_timezone' =>drupal_get_user_timezone(). However, that does not seem to solve the problem. I think as @partdigital pointed out in the issue summary, the getInfo() method only gets called on cache clear, and that is the key problem.

yunke’s picture

I also found this problem and tried to solve it as follows:
First, in getInfo() ,delete :

'#date_timezone' => date_default_timezone_get(),

then:

public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
        if(empty($element['#date_timezone'])){
            $element['#date_timezone']=date_default_timezone_get();
        }
       ...
}
larowlan’s picture

Issue tags: +Needs tests

@yunke yes that seems like a good approach - did it work?

jhedstrom’s picture

After further research, I don't think #3009377: Replace drupal_get_user_timezone with a date_default_timezone_get() and an event listener caused this issue, but it made a presumably already-existing bug much more noticable. Prior to that change, when caches are cleared on the backend (where the default timezone is set to UTC), the cached value at least fell to whatever the site's default timezone was.

jhedstrom’s picture

Status: Active » Needs review
FileSize
1.31 KB

In manual testing, this resolves the issue. We'll need tests to ensure against future regression though.

jhedstrom’s picture

Component: forms system » datetime.module

I think this has more to do with dates than the form system, so moving component.

jhedstrom’s picture

This adds a test that demonstrates the current bug, and the fix. The test-only patch is also the interdiff.

jhedstrom’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Datetime/DatetimeElementFormTest.php
@@ -58,8 +58,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
-      '#date_date_format' => ['Y-m-d'],
-      '#date_time_format' => ['H:i:s'],

@@ -69,8 +69,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
-      '#date_date_format' => ['Y-m-d'],
-      '#date_time_format' => ['H:i:s'],

Note, these were straight-up bugs. The values here are strings, not arrays. No error was thrown because DateTimePlus catches all exceptions and logs them.

jhedstrom’s picture

acbramley’s picture

Test looks really nice to me, straight forward and to the point, nice one!

acbramley’s picture

+++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
@@ -69,6 +71,10 @@ public function getInfo() {
+    if (!isset($element['#date_timezone'])) {
+      $element['#date_timezone'] = date_default_timezone_get();
+    }

Über-nit: This could be a 1 liner

$element['#date_timezone'] = $element['#date_timezone'] ?? date_default_timezone_get();
kim.pepper’s picture

Looking forward to using

<?php
$element['#date_timezone'] ??= date_default_timezone_get();
?>

in PHP 7.4 :-)

larowlan’s picture

Looks great

The one-liner could also be

$element += ['#date_timezone' => date_default_timezone_get()];
kim.pepper’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Datetime/DatetimeElementFormTest.php
@@ -58,8 +58,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
-      '#date_date_format' => ['Y-m-d'],
-      '#date_time_format' => ['H:i:s'],
+      '#date_date_format' => 'Y-m-d',
+      '#date_time_format' => 'H:i:s',

Good catch! I agree this is ready on green.

The last submitted patch, 11: datetime_getinfo_cac-3087606-11-TEST-ONLY.patch, failed testing. View results

The last submitted patch, 13: datetime_getinfo_cac-3087606-13-TEST-ONLY.patch, failed testing. View results

Berdir’s picture

> Prior to that change, when caches are cleared on the backend (where the default timezone is set to UTC), the cached value at least fell to whatever the site's default timezone was.

Confused by this. What exactly is backend here? If you talk about /admin/something or the clear cache admin_toolbar link then that's just a regular request, the other issue doesn't care if it's frontend or backend.

But maybe backend here is actually drush or console.. and maybe they're not triggering the request subscriber that sets the default timezone. So that *would* be a regression of that issue, but I'd say that maybe drush should be responsible for setting that then?

jhedstrom’s picture

But maybe backend here is actually drush or console..

That's what I meant by backend. No user is set when doing a drush cr, so whatever date.timezone value is set in php.ini is used, in my case, UTC.

So that *would* be a regression of that issue

It's a regression, but the bug of caching whatever user or non-user had set in the Datetime::getInfo() was already a bug, this just makes it much more obvious. Either way, this should fix both I think :)

jhedstrom’s picture

Issue summary: View changes

Updating the IS to reflect the notes in #22. It would be great to get this fixed.

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

The fix and test looks fine to me.

Despite 3 different nitpicks on the same line, I think the current line to set the default is also fine :)

And I posted in https://github.com/drush-ops/drush/issues/1166#issuecomment-575320943 about the more general problem that might also affect other places that rely on the timezone being set correctly in drush.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 22: datetime_getinfo_cac-3087606-22.patch, failed testing. View results

jonathan1055’s picture

Status: Needs work » Reviewed & tested by the community

Patch #22 was passing before (many times), but failed this morning, so I re-queued it, and it passed. Setting back to RTBC as per #24

catch’s picture

Version: 8.9.x-dev » 8.8.x-dev
Status: Reviewed & tested by the community » Fixed

Committed 3d9ac25 and pushed to 9.0.x, 8.9.x, and 8.8.x. Thanks!

  • catch committed 3d9ac25 on 9.0.x
    Issue #3087606 by jhedstrom, kim.pepper, jonathan1055, partdigital,...

  • catch committed 7f0032c on 8.9.x
    Issue #3087606 by jhedstrom, kim.pepper, jonathan1055, partdigital,...

  • catch committed 8be92ba on 8.8.x
    Issue #3087606 by jhedstrom, kim.pepper, jonathan1055, partdigital,...
catch’s picture

Status: Fixed » Closed (fixed)

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

Oscaner’s picture

Patch for Drupal core 8.7.x