diff --git a/system/I18n/TimeTrait.php b/system/I18n/TimeTrait.php index 16454c5f57b4..242ce12570e2 100644 --- a/system/I18n/TimeTrait.php +++ b/system/I18n/TimeTrait.php @@ -1237,8 +1237,6 @@ public function __isset($name): bool } /** - * This is called when we unserialize the Time object. - * * @param array $data */ public function __unserialize(array $data): void @@ -1247,6 +1245,16 @@ public function __unserialize(array $data): void $timezone = $data['timezone'] ?? null; assert(is_string($date) && is_string($timezone)); - parent::__construct($date, new DateTimeZone($timezone)); + $locale = $data["\0*\0locale"] ?? null; + $toStringFormat = $data["\0*\0toStringFormat"] ?? null; + + $this->timezone = new DateTimeZone($timezone); + $this->locale = is_string($locale) ? $locale : Locale::getDefault(); + + if (is_string($toStringFormat)) { + $this->toStringFormat = $toStringFormat; + } + + parent::__construct($date, $this->timezone); } } diff --git a/tests/system/I18n/TimeLegacyTest.php b/tests/system/I18n/TimeLegacyTest.php index ec6cdb042640..d3098e6e8814 100644 --- a/tests/system/I18n/TimeLegacyTest.php +++ b/tests/system/I18n/TimeLegacyTest.php @@ -1147,6 +1147,15 @@ public function testUnserializeTimeObject(): void $this->assertNotSame($time1, $time2); } + public function testUnserializeRestoresTimezoneAndLocale(): void + { + $time = unserialize(serialize(TimeLegacy::parse('2024-03-01 10:00:00', 'Europe/Paris', 'fr_FR'))); + + $this->assertSame('Europe/Paris', $time->getTimezoneName()); + $this->assertSame('mars', $time->toLocalizedString('MMMM')); + $this->assertSame(1, $time->difference(TimeLegacy::parse('2024-03-02 10:00:00', 'Europe/Paris'))->getDays()); + } + public function testSetTestNowWithFaLocale(): void { Locale::setDefault('fa'); diff --git a/tests/system/I18n/TimeTest.php b/tests/system/I18n/TimeTest.php index 3368e5802d54..de032a86f03b 100644 --- a/tests/system/I18n/TimeTest.php +++ b/tests/system/I18n/TimeTest.php @@ -1358,6 +1358,29 @@ public function testUnserializeTimeObject(): void $this->assertNotSame($time1, $time2); } + public function testUnserializeRestoresTimezoneAndLocale(): void + { + $time = unserialize(serialize(Time::parse('2024-03-01 10:00:00', 'Europe/Paris', 'fr_FR'))); + + $this->assertSame('Europe/Paris', $time->getTimezoneName()); + $this->assertSame('mars', $time->toLocalizedString('MMMM')); + $this->assertSame(1, $time->difference(Time::parse('2024-03-02 10:00:00', 'Europe/Paris'))->getDays()); + } + + public function testUnserializeFallsBackWithoutTraitProperties(): void + { + $payload = sprintf( + 'O:%d:"%s":3:{s:4:"date";s:26:"2024-03-01 10:00:00.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:12:"Europe/Paris";}', + strlen(Time::class), + Time::class, + ); + $time = unserialize($payload); + + $this->assertSame('Europe/Paris', $time->getTimezoneName()); + $this->assertSame('March', $time->toLocalizedString('MMMM')); + $this->assertSame('2024-03-01 10:00:00', (string) $time); + } + public function testSetTestNowWithTimeZone(): void { Time::setTestNow('2017/03/10 12:00', 'Asia/Tokyo'); diff --git a/user_guide_src/source/changelogs/v4.7.6.rst b/user_guide_src/source/changelogs/v4.7.6.rst index af35a0fee7e9..ac31695456cc 100644 --- a/user_guide_src/source/changelogs/v4.7.6.rst +++ b/user_guide_src/source/changelogs/v4.7.6.rst @@ -51,6 +51,7 @@ Bugs Fixed - **HTTP:** Fixed a bug where ``Negotiate::media()`` threw an ``ErrorException`` when the ``Accept`` header contained a value without a subtype, such as ``json`` or an empty list element. Such values are now ignored. - **HTTP:** Fixed a bug where ``Negotiate`` could select a value that the request header excluded with ``q=0`` through a wildcard or a broader range. A ``q=0`` range now excludes every value it matches, such as ``text/html`` and ``text/html;charset=utf-8`` for ``text/*;q=0``, or ``en-US`` for ``en;q=0``, unless a more specific range in the header accepts the value. For example, ``Accept-Encoding: gzip;q=0, *`` no longer selects ``gzip``. - **HTTP:** Fixed stale ``$_REQUEST`` values after ``SiteURIFactory`` normalizes GET parameters, so ``Validation::withRequest()`` reads the normalized values while unrelated REQUEST entries are preserved. +- **I18n:** Fixed a regression in v4.7.1 where ``Time`` and ``TimeLegacy`` lost their timezone, locale and ``__toString()`` format after ``unserialize()``, so methods such as ``difference()`` and ``getTimezoneName()`` threw an ``Error``. - **Security:** Fixed a bug where a valid JSON request body that is not an object (for example a JSON array) was corrupted after CSRF verification when the token was sent via the ``X-CSRF-TOKEN`` header. See the repo's