Problem/Motivation
The DateTimeIso8601::getDateTime()
method on the @DataType=datetime_iso8601
plugin incorrectly constructs a \Drupal\Core\Datetime\DrupalDateTime
object: it forgot to specify the timezone of the stored value (which is always UTC).
In Drupal core, there are zero calls to DateTimeIso8601::getDateTime()
. Only tests are calling it. It's thanks to the API-First Initiative adding a normalizer to correctly and consistently normalize datetime information that we discovered this at all.
- Reported by @joelstein in #2926508-132: Add DateTimeNormalizer+TimestampNormalizer, deprecate TimestampItemNormalizer: @DataType-level normalizers are reusable by JSON:API.
- Root cause traced by @Wim Leers in #2926508-135: Add DateTimeNormalizer+TimestampNormalizer, deprecate TimestampItemNormalizer: @DataType-level normalizers are reusable by JSON:API.
- Confirmed by @mpdonadio in #2926508-139: Add DateTimeNormalizer+TimestampNormalizer, deprecate TimestampItemNormalizer: @DataType-level normalizers are reusable by JSON:API, and asked to be split to a separate issue. This is that separate issue.
Proposed resolution
Start specifying the timezone!
Expand test coverage: the existing test coverage was a low-level kernel test which specifically had a timezone offset specified (which was done explicitly for testing the TypedData aspects), but in reality datetime strings are stored in the database without timezone offsets (see @joelstein in #2926508-132: Add DateTimeNormalizer+TimestampNormalizer, deprecate TimestampItemNormalizer: @DataType-level normalizers are reusable by JSON:API).
Remaining tasks
Review.
User interface changes
None.
API changes
None. The \Drupal\Core\Datetime\DrupalDateTime
instance returned by \Drupal\Core\TypedData\Plugin\DataType\DateTimeIso8601::getDateTime()
now has the appropriate timezone set.
Data model changes
None.
BC implications
This patch changes nothing about current or future stored values. This only changes the mapping of stored values into DrupalDateTime
objects, which almost no code does (in core, only tests do this).
Comment | File | Size | Author |
---|---|---|---|
#18 | 3002164-18.patch | 8.34 KB | Wim Leers |
#18 | interdiff.txt | 1.25 KB | Wim Leers |
#13 | interdiff-11-13.txt | 3.1 KB | mpdonadio |
#11 | 3002164-11.patch | 8.04 KB | Wim Leers |
#11 | interdiff.txt | 2.52 KB | Wim Leers |
Comments
Comment #2
Wim LeersThis should pass tests, but it doesn't.
Comment #3
Wim LeersThis is the solution.
Comment #4
Wim LeersAssigning to @mpdonadio and @jhedstrom for review.
Comment #6
mpdonadioThis couples core to the datetime.module, so maybe explicit 'UTC' w/ a comment?
The new assertion looks good, but I think we also need this in TypedDataTest. The problem there is that
all of those date strings have a UTC offset in them, which implicitly sets them to UTC (kinda, see https://3v4l.org/jctWg). When you parse a string w/ an offset that will override the time zone in the constructor.
Not totally sure what is best here.
Comment #7
Wim LeersThat's exactly what I contemplated too. I sort of hoped you would say this :D Not sure what the comment should look like. Whatever you prefer there I'm probably fine with.
Indeed. For that reason, that test coverage doesn't reflect the reality of actually executed code.
I think I know: just add additional test coverage with a non-timezone-annotated value. The
assertion does not pass for this new test coverage, which again suggests that
@DataType=datetime_iso8601
was always meant to include timezone information. Solving that is out of scope here though; this issue is about making the bare minimum change to fix the problem at hand; #2716891: DateTimeIso8601::setDateTime() vs. DateTimeItem::schema(): (at least) one of them is broken exists for restoring that assertion.Comment #10
Wim LeersThe non-test-only patch did in fact pass tests. It just has some CS violations.
Back to NR.
Comment #11
Wim LeersDone.
Comment #12
Wim LeersPinged @mpdonadio: https://twitter.com/wimleers/status/1065252796079968256
Comment #13
mpdonadioThis had me scratching my head for a while. The default timezone for tests is 'Australia/Sydney', which happens to have a UTC offset of +11:00 on 2014-01-02.
Let's look closer
The test does `$typed_data->setDateTime(new DrupalDateTime('2014-01-02T20:00:00'));`. The TZ of the anonymous object passed to `setDateTime()` will be 'Australia/Sydney', per PHP's rules. Set a breakpoint to see.
At "NOTE 1", `$this->value` gets set to '2014-01-02T20:00:00+11:00', which stores the timezone offset and not the timezone name in the string.
At "NOTE 2", we build a new `DrupalDateTime()` from the string '2014-01-02T20:00:00+11:00'. The timezone name gets set to '+11:00' per PHP's rules, not 'Australia/Sydney'. This then is what is in the assertion.
To be honest, I don't think we can really do anything about this. Using '+00:00' in `getDateTime()` smells bad. If we want to be pedantic, then I think the attached interdiff may be best. I am not wed to that though.
My issues about in #6 are addressed to my satisfaction (and sorry for not being more specific about what "kinda" meant).
Comment #14
Wim LeersWoot!
Thank you for being so diligent! 👌
Comment #15
Wim LeersWoot!
Thank you for being so diligent! 👌
Comment #16
alexpottDoes this work have an BC implications. What about existing code?
I think we need a change record to detail the change and the issue summary could be updated to be more explicit about why this change is correct rather than pointing to lots of other issues to work out why.
Given this had a datatime maintainer scratching their head maybe we should add a comment for future us.
Comment #17
Wim LeersIssue summary updated, change record created.
Comment #18
Wim LeersOh,
— did that too.Comment #19
Wim LeersComment #20
alexpottI committed this to 8.7.x - as I'm unsure of the implications for contrib if they have used this to manipulate and then store values I'm going to leave there. If there are compelling reasons to backport then we can discuss and do that.
Committed f51bfe1 and pushed to 8.7.x. Thanks!
Comment #22
Wim Leers#2999438: [upstream] Datetime field shown with wrong timezone offset just landed, which had to effectively simulate/backport this so that JSON:API responses in Drupal 8.6 and 8.5 are also correct. I don't expect core to cherry-pick this patch into the 8.6 and 8.5 branches, although I certainly would not oppose it either :)