From 5f7c543e2aea1f282f1739a0bb51ce466bd202c6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 7 Aug 2024 10:32:42 +0900 Subject: [PATCH 01/15] test: add test for Entity Casts datetime with timestamp --- tests/system/Entity/EntityTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/system/Entity/EntityTest.php b/tests/system/Entity/EntityTest.php index 8dd7ceac31fa..b0ec5ebbda92 100644 --- a/tests/system/Entity/EntityTest.php +++ b/tests/system/Entity/EntityTest.php @@ -460,6 +460,27 @@ public function testCastDateTime(): void $this->assertSame('2017-03-12', $entity->eighth->format('Y-m-d')); } + public function testCastDateTimeWithTimestampTimezone(): void + { + // Save the current timezone. + $tz = date_default_timezone_get(); + + // Change the timezone other than UTC. + date_default_timezone_set('Asia/Tokyo'); // +09:00 + + $entity = $this->getCastEntity(); + + $entity->eighth = 1722988800; // 2024-08-07 00:00:00 UTC + + $this->assertInstanceOf(DateTimeInterface::class, $entity->eighth); + // The timezone is the default timezone, not UTC. + $this->assertSame('2024-08-07 09:00:00', $entity->eighth->format('Y-m-d H:i:s')); + $this->assertSame('Asia/Tokyo', $entity->eighth->getTimezoneName()); + + // Restore timezone. + date_default_timezone_set($tz); + } + public function testCastTimestamp(): void { $entity = $this->getCastEntity(); From 1f081ec7cbd33cba13387b282315156e590fe49e Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 7 Aug 2024 11:00:20 +0900 Subject: [PATCH 02/15] test: add timezone for DataCaster TimestampCast --- .../DataConverter/DataConverterTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/system/DataConverter/DataConverterTest.php b/tests/system/DataConverter/DataConverterTest.php index 53a6361bb0de..3383bbfdc102 100644 --- a/tests/system/DataConverter/DataConverterTest.php +++ b/tests/system/DataConverter/DataConverterTest.php @@ -398,6 +398,12 @@ public function testDateTimeConvertDataToDBWithFormat(): void public function testTimestampConvertDataFromDB(): void { + // Save the current timezone. + $tz = date_default_timezone_get(); + + // Change the timezone other than UTC. + date_default_timezone_set('Asia/Tokyo'); // +09:00 + $types = [ 'id' => 'int', 'date' => 'timestamp', @@ -412,10 +418,20 @@ public function testTimestampConvertDataFromDB(): void $this->assertInstanceOf(Time::class, $data['date']); $this->assertSame(1_700_285_831, $data['date']->getTimestamp()); + $this->assertSame('Asia/Tokyo', $data['date']->getTimezoneName()); + + // Restore timezone. + date_default_timezone_set($tz); } public function testTimestampConvertDataToDB(): void { + // Save the current timezone. + $tz = date_default_timezone_get(); + + // Change the timezone other than UTC. + date_default_timezone_set('Asia/Tokyo'); // +09:00 + $types = [ 'id' => 'int', 'date' => 'timestamp', @@ -429,6 +445,9 @@ public function testTimestampConvertDataToDB(): void $data = $converter->toDataSource($phpData); $this->assertSame(1_700_285_831, $data['date']); + + // Restore timezone. + date_default_timezone_set($tz); } public function testURIConvertDataFromDB(): void From da76c864b3ec3aacc978742dbfb3746c1b17f774 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 09:52:37 +0900 Subject: [PATCH 03/15] fix!: now Time::createFromTimestamp() returns Time with UTC by default --- system/I18n/TimeTrait.php | 2 +- tests/system/I18n/TimeTest.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/system/I18n/TimeTrait.php b/system/I18n/TimeTrait.php index 3257ad06189f..1ca8f5d9999a 100644 --- a/system/I18n/TimeTrait.php +++ b/system/I18n/TimeTrait.php @@ -269,7 +269,7 @@ public static function createFromTimestamp(int $timestamp, $timezone = null, ?st { $time = new self(gmdate('Y-m-d H:i:s', $timestamp), 'UTC', $locale); - $timezone ??= date_default_timezone_get(); + $timezone ??= 'UTC'; return $time->setTimezone($timezone); } diff --git a/tests/system/I18n/TimeTest.php b/tests/system/I18n/TimeTest.php index 0e5bc733407c..96482554cbb8 100644 --- a/tests/system/I18n/TimeTest.php +++ b/tests/system/I18n/TimeTest.php @@ -277,10 +277,11 @@ public function testCreateFromTimestamp(): void $timestamp = strtotime('2017-03-18 midnight'); + // The timezone will be UTC if you don't specify. $time = Time::createFromTimestamp($timestamp); - $this->assertSame('Asia/Tokyo', $time->getTimezone()->getName()); - $this->assertSame('2017-03-18 00:00:00', $time->format('Y-m-d H:i:s')); + $this->assertSame('UTC', $time->getTimezone()->getName()); + $this->assertSame('2017-03-17 15:00:00', $time->format('Y-m-d H:i:s')); // Restore timezone. date_default_timezone_set($tz); From cc6a54d094f79acab0ee28e98d286cc9c4270410 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 09:53:27 +0900 Subject: [PATCH 04/15] docs: add docs --- user_guide_src/source/changelogs/v4.6.0.rst | 10 +++++++++- .../source/installation/upgrade_460.rst | 20 +++++++++++++++++++ user_guide_src/source/libraries/time.rst | 6 ++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index 1cd78a3d51e2..d4cd6b3ae606 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -57,7 +57,13 @@ Added check to prevent Auto-Discovery of Registrars from running twice. If it is executed twice, an exception will be thrown. See :ref:`upgrade-460-registrars-with-dirty-hack`. -.. _v460-interface-changes: +Time::createFromTimestamp() +--------------------------- + +``Time::createFromTimestamp()`` handles timezones differently. If ``$timezone`` +is not explicitly passed then the instance has timezone set to UTC unlike earlier +where the currently set default timezone was used. +See :ref:`Upgrading Guide ` for details. Time with Microseconds ---------------------- @@ -65,6 +71,8 @@ Time with Microseconds Fixed bugs that some methods in ``Time`` to lose microseconds have been fixed. See :ref:`Upgrading Guide ` for details. +.. _v460-interface-changes: + Interface Changes ================= diff --git a/user_guide_src/source/installation/upgrade_460.rst b/user_guide_src/source/installation/upgrade_460.rst index 7078110b7068..5cbfd664bf92 100644 --- a/user_guide_src/source/installation/upgrade_460.rst +++ b/user_guide_src/source/installation/upgrade_460.rst @@ -29,6 +29,26 @@ See :ref:`ChangeLog ` for details. If you have code that catches these exceptions, change the exception classes. +.. _upgrade-460-time-create-from-timestamp: + +Time::createFromTimestamp() Timezone Change +=========================================== + +When you do not explicitly pass a timezone, now +:ref:`Time::createFromTimestamp() ` returns a Time +instance with **UTC**. In v4.4.6 to prior to v4.6.0, a Time instance with the +currently set default timezone was returned. + +This behavior change normalizes behavior with changes in PHP 8.4 which adds a +new ``DateTimeInterface::createFromTimestamp()`` method. + +If you want to keep the default timezone, you need to pass the timezone as the +second parameter:: + + use CodeIgniter\I18n\Time; + + $time = Time::createFromTimestamp(1501821586, date_default_timezone_get()); + .. _upgrade-460-time-keeps-microseconds: Time keeps Microseconds diff --git a/user_guide_src/source/libraries/time.rst b/user_guide_src/source/libraries/time.rst index b18e56350403..ccc008f5c533 100644 --- a/user_guide_src/source/libraries/time.rst +++ b/user_guide_src/source/libraries/time.rst @@ -126,8 +126,10 @@ This method takes a UNIX timestamp and, optionally, the timezone and locale, to .. literalinclude:: time/012.php -.. note:: Due to a bug, prior to v4.4.6, this method returned a Time instance - in timezone UTC when you do not specify a timezone. +If you do not explicitly pass a timezone, it returns a Time instance with **UTC**. + +.. note:: In v4.4.6 to prior to v4.6.0, this method returned a Time instance + with the default timezone when you do not specify a timezone. createFromInstance() ==================== From d9ad9bc5fc733f2ba5681a3073709ba78bdfcfb1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 10:36:53 +0900 Subject: [PATCH 05/15] docs: break long line --- user_guide_src/source/libraries/time.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/libraries/time.rst b/user_guide_src/source/libraries/time.rst index ccc008f5c533..1b844429aef6 100644 --- a/user_guide_src/source/libraries/time.rst +++ b/user_guide_src/source/libraries/time.rst @@ -122,7 +122,8 @@ and returns a ``Time`` instance, instead of DateTimeImmutable: createFromTimestamp() ===================== -This method takes a UNIX timestamp and, optionally, the timezone and locale, to create a new Time instance: +This method takes a UNIX timestamp and, optionally, the timezone and locale, to +create a new Time instance: .. literalinclude:: time/012.php From e1413fd908d77f3180890524c2053d391933106d Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 10:39:16 +0900 Subject: [PATCH 06/15] docs: add note for timezone --- user_guide_src/source/libraries/time.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/user_guide_src/source/libraries/time.rst b/user_guide_src/source/libraries/time.rst index 1b844429aef6..9fd986d31421 100644 --- a/user_guide_src/source/libraries/time.rst +++ b/user_guide_src/source/libraries/time.rst @@ -129,6 +129,9 @@ create a new Time instance: If you do not explicitly pass a timezone, it returns a Time instance with **UTC**. +.. note:: We recommend to always call ``createFromTimestamp()`` with 2 parameters + (i.e. explicitly pass a timezone) unless using UTC as the default timezone. + .. note:: In v4.4.6 to prior to v4.6.0, this method returned a Time instance with the default timezone when you do not specify a timezone. From 75a92304941e9f359cdb5d192214866d5d79573d Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 10:49:39 +0900 Subject: [PATCH 07/15] fix!: signature of createFromTimestamp() for PHP 8.4 --- system/I18n/TimeTrait.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/system/I18n/TimeTrait.php b/system/I18n/TimeTrait.php index 1ca8f5d9999a..b934a6b307bd 100644 --- a/system/I18n/TimeTrait.php +++ b/system/I18n/TimeTrait.php @@ -261,11 +261,9 @@ public static function createFromFormat($format, $datetime, $timezone = null) * * @param DateTimeZone|string|null $timezone * - * @return self - * * @throws Exception */ - public static function createFromTimestamp(int $timestamp, $timezone = null, ?string $locale = null) + public static function createFromTimestamp(float|int $timestamp, $timezone = null, ?string $locale = null): static { $time = new self(gmdate('Y-m-d H:i:s', $timestamp), 'UTC', $locale); From 1e90ad55c3043cda24deac7ff46c924f7eb08864 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 11:21:58 +0900 Subject: [PATCH 08/15] fix: Time::createFromTimestamp() loses microseconds --- system/I18n/TimeTrait.php | 2 +- tests/system/I18n/TimeTest.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/system/I18n/TimeTrait.php b/system/I18n/TimeTrait.php index b934a6b307bd..f96b870610b4 100644 --- a/system/I18n/TimeTrait.php +++ b/system/I18n/TimeTrait.php @@ -265,7 +265,7 @@ public static function createFromFormat($format, $datetime, $timezone = null) */ public static function createFromTimestamp(float|int $timestamp, $timezone = null, ?string $locale = null): static { - $time = new self(gmdate('Y-m-d H:i:s', $timestamp), 'UTC', $locale); + $time = new self('@' . $timestamp, 'UTC', $locale); $timezone ??= 'UTC'; diff --git a/tests/system/I18n/TimeTest.php b/tests/system/I18n/TimeTest.php index 96482554cbb8..bf8512dbeaf5 100644 --- a/tests/system/I18n/TimeTest.php +++ b/tests/system/I18n/TimeTest.php @@ -287,6 +287,17 @@ public function testCreateFromTimestamp(): void date_default_timezone_set($tz); } + public function testCreateFromTimestampWithMicrotime(): void + { + $timestamp = 1489762800.654321; + + // The timezone will be UTC if you don't specify. + $time = Time::createFromTimestamp($timestamp); + + // float cannot handle the number precisely. + $this->assertSame('2017-03-17 15:00:00.654300', $time->format('Y-m-d H:i:s.u')); + } + public function testCreateFromTimestampWithTimezone(): void { // Set the timezone temporarily to UTC to make sure the test timestamp is correct From 613aba2f6b83cc9d488174a8c6f3196bc4a286d3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 11:28:19 +0900 Subject: [PATCH 09/15] docs: add changelog --- user_guide_src/source/changelogs/v4.6.0.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index d4cd6b3ae606..51c43456ae28 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -97,6 +97,9 @@ Method Signature Changes changed. The ``RouteCollection`` typehint has been changed to ``RouteCollectionInterface``. - **View:** The return type of the ``renderSection()`` method has been changed to ``string``, and now the method does not call ``echo``. +- **Time:** The first parameter type of the ``createFromTimestamp()`` has been + changed from ``int`` to ``int|float``, and the return type ``static``` has been + added. Removed Type Definitions ------------------------ From 8992ac024c405f5ecd82b952c26d03fc4e294766 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 6 Aug 2024 13:14:48 +0900 Subject: [PATCH 10/15] refactor: replace self with static --- phpstan-baseline.php | 24 ------------ system/I18n/Time.php | 2 + system/I18n/TimeLegacy.php | 2 + system/I18n/TimeTrait.php | 80 +++++++++++++++++++------------------- 4 files changed, 44 insertions(+), 64 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 0f022eca7ac8..7a78a6d3a104 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -7483,18 +7483,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/I18n/Time.php', ]; -$ignoreErrors[] = [ - // identifier: method.childReturnType - 'message' => '#^Return type \\(CodeIgniter\\\\I18n\\\\Time\\) of method CodeIgniter\\\\I18n\\\\Time\\:\\:setTimestamp\\(\\) should be covariant with return type \\(static\\(DateTimeImmutable\\)\\) of method DateTimeImmutable\\:\\:setTimestamp\\(\\)$#', - 'count' => 1, - 'path' => __DIR__ . '/system/I18n/Time.php', -]; -$ignoreErrors[] = [ - // identifier: method.childReturnType - 'message' => '#^Return type \\(CodeIgniter\\\\I18n\\\\Time\\) of method CodeIgniter\\\\I18n\\\\Time\\:\\:setTimezone\\(\\) should be covariant with return type \\(static\\(DateTimeImmutable\\)\\) of method DateTimeImmutable\\:\\:setTimezone\\(\\)$#', - 'count' => 1, - 'path' => __DIR__ . '/system/I18n/Time.php', -]; $ignoreErrors[] = [ // identifier: ternary.shortNotAllowed 'message' => '#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#', @@ -7507,18 +7495,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/I18n/TimeLegacy.php', ]; -$ignoreErrors[] = [ - // identifier: method.childReturnType - 'message' => '#^Return type \\(CodeIgniter\\\\I18n\\\\TimeLegacy\\) of method CodeIgniter\\\\I18n\\\\TimeLegacy\\:\\:setTimestamp\\(\\) should be covariant with return type \\(static\\(DateTime\\)\\) of method DateTime\\:\\:setTimestamp\\(\\)$#', - 'count' => 1, - 'path' => __DIR__ . '/system/I18n/TimeLegacy.php', -]; -$ignoreErrors[] = [ - // identifier: method.childReturnType - 'message' => '#^Return type \\(CodeIgniter\\\\I18n\\\\TimeLegacy\\) of method CodeIgniter\\\\I18n\\\\TimeLegacy\\:\\:setTimezone\\(\\) should be covariant with return type \\(static\\(DateTime\\)\\) of method DateTime\\:\\:setTimezone\\(\\)$#', - 'count' => 1, - 'path' => __DIR__ . '/system/I18n/TimeLegacy.php', -]; $ignoreErrors[] = [ // identifier: ternary.shortNotAllowed 'message' => '#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#', diff --git a/system/I18n/Time.php b/system/I18n/Time.php index 906479470b17..255f879a90ec 100644 --- a/system/I18n/Time.php +++ b/system/I18n/Time.php @@ -39,6 +39,8 @@ * @property-read string $weekOfYear * @property-read string $year * + * @phpstan-consistent-constructor + * * @see \CodeIgniter\I18n\TimeTest */ class Time extends DateTimeImmutable implements Stringable diff --git a/system/I18n/TimeLegacy.php b/system/I18n/TimeLegacy.php index 403fced2108d..b62877ec40d6 100644 --- a/system/I18n/TimeLegacy.php +++ b/system/I18n/TimeLegacy.php @@ -39,6 +39,8 @@ * @property string $weekOfYear read-only * @property string $year read-only * + * @phpstan-consistent-constructor + * * @deprecated Use Time instead. * @see \CodeIgniter\I18n\TimeLegacyTest */ diff --git a/system/I18n/TimeTrait.php b/system/I18n/TimeTrait.php index f96b870610b4..1f4f630750b2 100644 --- a/system/I18n/TimeTrait.php +++ b/system/I18n/TimeTrait.php @@ -78,7 +78,7 @@ public function __construct(?string $time = null, $timezone = null, ?string $loc $time ??= ''; // If a test instance has been provided, use it instead. - if ($time === '' && static::$testNow instanceof self) { + if ($time === '' && static::$testNow instanceof static) { if ($timezone !== null) { $testNow = static::$testNow->setTimezone($timezone); $time = $testNow->format('Y-m-d H:i:s.u'); @@ -108,13 +108,13 @@ public function __construct(?string $time = null, $timezone = null, ?string $loc * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ public static function now($timezone = null, ?string $locale = null) { - return new self(null, $timezone, $locale); + return new static(null, $timezone, $locale); } /** @@ -125,13 +125,13 @@ public static function now($timezone = null, ?string $locale = null) * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ public static function parse(string $datetime, $timezone = null, ?string $locale = null) { - return new self($datetime, $timezone, $locale); + return new static($datetime, $timezone, $locale); } /** @@ -139,13 +139,13 @@ public static function parse(string $datetime, $timezone = null, ?string $locale * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ public static function today($timezone = null, ?string $locale = null) { - return new self(date('Y-m-d 00:00:00'), $timezone, $locale); + return new static(date('Y-m-d 00:00:00'), $timezone, $locale); } /** @@ -153,13 +153,13 @@ public static function today($timezone = null, ?string $locale = null) * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ public static function yesterday($timezone = null, ?string $locale = null) { - return new self(date('Y-m-d 00:00:00', strtotime('-1 day')), $timezone, $locale); + return new static(date('Y-m-d 00:00:00', strtotime('-1 day')), $timezone, $locale); } /** @@ -167,13 +167,13 @@ public static function yesterday($timezone = null, ?string $locale = null) * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ public static function tomorrow($timezone = null, ?string $locale = null) { - return new self(date('Y-m-d 00:00:00', strtotime('+1 day')), $timezone, $locale); + return new static(date('Y-m-d 00:00:00', strtotime('+1 day')), $timezone, $locale); } /** @@ -182,7 +182,7 @@ public static function tomorrow($timezone = null, ?string $locale = null) * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ @@ -196,7 +196,7 @@ public static function createFromDate(?int $year = null, ?int $month = null, ?in * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ @@ -210,7 +210,7 @@ public static function createFromTime(?int $hour = null, ?int $minutes = null, ? * * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ @@ -231,7 +231,7 @@ public static function create( $minutes ??= 0; $seconds ??= 0; - return new self(date('Y-m-d H:i:s', strtotime("{$year}-{$month}-{$day} {$hour}:{$minutes}:{$seconds}")), $timezone, $locale); + return new static(date('Y-m-d H:i:s', strtotime("{$year}-{$month}-{$day} {$hour}:{$minutes}:{$seconds}")), $timezone, $locale); } /** @@ -242,7 +242,7 @@ public static function create( * @param string $datetime * @param DateTimeZone|string|null $timezone * - * @return self + * @return static * * @throws Exception */ @@ -253,7 +253,7 @@ public static function createFromFormat($format, $datetime, $timezone = null) throw I18nException::forInvalidFormat($format); } - return new self($date->format('Y-m-d H:i:s.u'), $timezone); + return new static($date->format('Y-m-d H:i:s.u'), $timezone); } /** @@ -265,7 +265,7 @@ public static function createFromFormat($format, $datetime, $timezone = null) */ public static function createFromTimestamp(float|int $timestamp, $timezone = null, ?string $locale = null): static { - $time = new self('@' . $timestamp, 'UTC', $locale); + $time = new static('@' . $timestamp, 'UTC', $locale); $timezone ??= 'UTC'; @@ -275,7 +275,7 @@ public static function createFromTimestamp(float|int $timestamp, $timezone = nul /** * Takes an instance of DateTimeInterface and returns an instance of Time with it's same values. * - * @return self + * @return static * * @throws Exception */ @@ -284,13 +284,13 @@ public static function createFromInstance(DateTimeInterface $dateTime, ?string $ $date = $dateTime->format('Y-m-d H:i:s.u'); $timezone = $dateTime->getTimezone(); - return new self($date, $timezone, $locale); + return new static($date, $timezone, $locale); } /** * Takes an instance of DateTime and returns an instance of Time with it's same values. * - * @return self + * @return static * * @throws Exception * @@ -300,7 +300,7 @@ public static function createFromInstance(DateTimeInterface $dateTime, ?string $ */ public static function instance(DateTime $dateTime, ?string $locale = null) { - return self::createFromInstance($dateTime, $locale); + return static::createFromInstance($dateTime, $locale); } /** @@ -345,9 +345,9 @@ public static function setTestNow($datetime = null, $timezone = null, ?string $l // Convert to a Time instance if (is_string($datetime)) { - $datetime = new self($datetime, $timezone, $locale); - } elseif ($datetime instanceof DateTimeInterface && ! $datetime instanceof self) { - $datetime = new self($datetime->format('Y-m-d H:i:s.u'), $timezone); + $datetime = new static($datetime, $timezone, $locale); + } elseif ($datetime instanceof DateTimeInterface && ! $datetime instanceof static) { + $datetime = new static($datetime->format('Y-m-d H:i:s.u'), $timezone); } static::$testNow = $datetime; @@ -475,7 +475,7 @@ public function getWeekOfYear(): string public function getAge() { // future dates have no age - return max(0, $this->difference(self::now())->getYears()); + return max(0, $this->difference(static::now())->getYears()); } /** @@ -532,7 +532,7 @@ public function getTimezoneName(): string * * @param int|string $value * - * @return self + * @return static * * @throws Exception */ @@ -546,7 +546,7 @@ public function setYear($value) * * @param int|string $value * - * @return self + * @return static * * @throws Exception */ @@ -568,7 +568,7 @@ public function setMonth($value) * * @param int|string $value * - * @return self + * @return static * * @throws Exception */ @@ -592,7 +592,7 @@ public function setDay($value) * * @param int|string $value * - * @return self + * @return static * * @throws Exception */ @@ -610,7 +610,7 @@ public function setHour($value) * * @param int|string $value * - * @return self + * @return static * * @throws Exception */ @@ -628,7 +628,7 @@ public function setMinute($value) * * @param int|string $value * - * @return self + * @return static * * @throws Exception */ @@ -646,7 +646,7 @@ public function setSecond($value) * * @param int $value * - * @return self + * @return static * * @throws Exception */ @@ -656,7 +656,7 @@ protected function setValue(string $name, $value) ${$name} = $value; - return self::create( + return static::create( (int) $year, (int) $month, (int) $day, @@ -673,7 +673,7 @@ protected function setValue(string $name, $value) * * @param DateTimeZone|string $timezone * - * @return self + * @return static * * @throws Exception */ @@ -682,7 +682,7 @@ public function setTimezone($timezone) { $timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone); - return self::createFromInstance($this->toDateTime()->setTimezone($timezone), $this->locale); + return static::createFromInstance($this->toDateTime()->setTimezone($timezone), $this->locale); } /** @@ -690,7 +690,7 @@ public function setTimezone($timezone) * * @param int $timestamp * - * @return self + * @return static * * @throws Exception */ @@ -699,7 +699,7 @@ public function setTimestamp($timestamp) { $time = date('Y-m-d H:i:s', $timestamp); - return self::parse($time, $this->timezone, $this->locale); + return static::parse($time, $this->timezone, $this->locale); } // -------------------------------------------------------------------- @@ -1085,7 +1085,7 @@ public function difference($testTime, ?string $timezone = null) if (is_string($testTime)) { $timezone = ($timezone !== null) ? new DateTimeZone($timezone) : $this->timezone; $testTime = new DateTime($testTime, $timezone); - } elseif ($testTime instanceof self) { + } elseif ($testTime instanceof static) { $testTime = $testTime->toDateTime(); } @@ -1116,7 +1116,7 @@ public function difference($testTime, ?string $timezone = null) */ public function getUTCObject($time, ?string $timezone = null) { - if ($time instanceof self) { + if ($time instanceof static) { $time = $time->toDateTime(); } elseif (is_string($time)) { $timezone = $timezone ?: $this->timezone; From 57b511281396fd0442cf6432ec4b12ddb8cbb321 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 7 Aug 2024 10:39:15 +0900 Subject: [PATCH 11/15] fix: keep the behavior of Entity casts datetime with timestamp It returns Time with the default timezone as before. --- system/Entity/Cast/DatetimeCast.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Entity/Cast/DatetimeCast.php b/system/Entity/Cast/DatetimeCast.php index 2d01ad79b0ae..88b7b29267e0 100644 --- a/system/Entity/Cast/DatetimeCast.php +++ b/system/Entity/Cast/DatetimeCast.php @@ -40,7 +40,7 @@ public static function get($value, array $params = []) } if (is_numeric($value)) { - return Time::createFromTimestamp((int) $value); + return Time::createFromTimestamp((int) $value, date_default_timezone_get()); } if (is_string($value)) { From f38d4a397751bf7dd21ccd3e8e6c217e228ba884 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 7 Aug 2024 11:03:56 +0900 Subject: [PATCH 12/15] fix: keep the behavior of Model casts datetime with timestamp It returns Time with the default timezone as before. --- system/DataCaster/Cast/TimestampCast.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/DataCaster/Cast/TimestampCast.php b/system/DataCaster/Cast/TimestampCast.php index 52a4d88f9e46..f19d1a78810f 100644 --- a/system/DataCaster/Cast/TimestampCast.php +++ b/system/DataCaster/Cast/TimestampCast.php @@ -32,7 +32,7 @@ public static function get( self::invalidTypeValueError($value); } - return Time::createFromTimestamp((int) $value); + return Time::createFromTimestamp((int) $value, date_default_timezone_get()); } public static function set( From 97148ef46d4cd3ce1b98341318197df55673aa72 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 7 Aug 2024 11:10:47 +0900 Subject: [PATCH 13/15] docs: add description for timestamp cast timezone --- user_guide_src/source/models/model.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/user_guide_src/source/models/model.rst b/user_guide_src/source/models/model.rst index c1cbf21a4dbd..9057001ae785 100644 --- a/user_guide_src/source/models/model.rst +++ b/user_guide_src/source/models/model.rst @@ -407,6 +407,12 @@ The datetime format is set in the ``dateFormat`` array of the .. note:: Prior to v4.6.0, you cannot use ``ms`` or ``us`` as a parameter. Because the second's fractional part of Time was lost due to bugs. +timestamp +--------- + +The timezone of the ``Time`` instance created will be the default timezone +(app's timezone), not UTC. + Custom Casting ============== From f42b1a0c8185fa22f638fc5da978ba640edc3495 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 20 Aug 2024 10:56:57 +0900 Subject: [PATCH 14/15] test: fix test code for Time::createFromTimestamp() The microseconds should be preserved without rounding. --- tests/system/I18n/TimeTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/system/I18n/TimeTest.php b/tests/system/I18n/TimeTest.php index bf8512dbeaf5..10e38e2110a2 100644 --- a/tests/system/I18n/TimeTest.php +++ b/tests/system/I18n/TimeTest.php @@ -287,15 +287,14 @@ public function testCreateFromTimestamp(): void date_default_timezone_set($tz); } - public function testCreateFromTimestampWithMicrotime(): void + public function testCreateFromTimestampWithMicroseconds(): void { $timestamp = 1489762800.654321; // The timezone will be UTC if you don't specify. $time = Time::createFromTimestamp($timestamp); - // float cannot handle the number precisely. - $this->assertSame('2017-03-17 15:00:00.654300', $time->format('Y-m-d H:i:s.u')); + $this->assertSame('2017-03-17 15:00:00.654321', $time->format('Y-m-d H:i:s.u')); } public function testCreateFromTimestampWithTimezone(): void From 06b02bbf80206a74885e7279240f396dd6b8fa2d Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 20 Aug 2024 11:02:48 +0900 Subject: [PATCH 15/15] fix: microseconds are not kept to full precision --- system/I18n/TimeTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/I18n/TimeTrait.php b/system/I18n/TimeTrait.php index 1f4f630750b2..397d9a5a870d 100644 --- a/system/I18n/TimeTrait.php +++ b/system/I18n/TimeTrait.php @@ -265,7 +265,7 @@ public static function createFromFormat($format, $datetime, $timezone = null) */ public static function createFromTimestamp(float|int $timestamp, $timezone = null, ?string $locale = null): static { - $time = new static('@' . $timestamp, 'UTC', $locale); + $time = new static(sprintf('@%.6f', $timestamp), 'UTC', $locale); $timezone ??= 'UTC';