Skip to content

Commit bb8c76d

Browse files
feature #51412 [Clock] Throw DateMalformedStringException/DateInvalidTimeZoneException when appropriate (nicolas-grekas)
This PR was merged into the 6.4 branch. Discussion ---------- [Clock] Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - This PR leverages symfony/polyfill#440 and https://wiki.php.net/rfc/datetime-exceptions to provide consistent error handling when malformed dates/timezones are given. Commits ------- 0262573 [Clock] Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate
2 parents 1e6b567 + 0262573 commit bb8c76d

File tree

8 files changed

+103
-38
lines changed

8 files changed

+103
-38
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"symfony/polyfill-intl-idn": "^1.10",
5555
"symfony/polyfill-intl-normalizer": "~1.0",
5656
"symfony/polyfill-mbstring": "~1.0",
57-
"symfony/polyfill-php83": "^1.27",
57+
"symfony/polyfill-php83": "^1.28",
5858
"symfony/polyfill-uuid": "^1.15"
5959
},
6060
"replace": {

src/Symfony/Component/Clock/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.4
5+
---
6+
7+
* Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate
8+
49
6.3
510
---
611

src/Symfony/Component/Clock/Clock.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,23 @@ public function sleep(float|int $seconds): void
6262
}
6363
}
6464

65+
/**
66+
* @throws \DateInvalidTimeZoneException When $timezone is invalid
67+
*/
6568
public function withTimeZone(\DateTimeZone|string $timezone): static
6669
{
70+
if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) {
71+
$timezone = new \DateTimeZone($timezone);
72+
} elseif (\is_string($timezone)) {
73+
try {
74+
$timezone = new \DateTimeZone($timezone);
75+
} catch (\Exception $e) {
76+
throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e);
77+
}
78+
}
79+
6780
$clone = clone $this;
68-
$clone->timezone = \is_string($timezone) ? new \DateTimeZone($timezone) : $timezone;
81+
$clone->timezone = $timezone;
6982

7083
return $clone;
7184
}

src/Symfony/Component/Clock/MockClock.php

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,30 @@ final class MockClock implements ClockInterface
2222
{
2323
private \DateTimeImmutable $now;
2424

25+
/**
26+
* @throws \DateMalformedStringException When $now is invalid
27+
* @throws \DateInvalidTimeZoneException When $timezone is invalid
28+
*/
2529
public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZone|string $timezone = null)
2630
{
27-
if (\is_string($timezone)) {
31+
if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) {
2832
$timezone = new \DateTimeZone($timezone);
33+
} elseif (\is_string($timezone)) {
34+
try {
35+
$timezone = new \DateTimeZone($timezone);
36+
} catch (\Exception $e) {
37+
throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e);
38+
}
2939
}
3040

31-
if (\is_string($now)) {
41+
if (\PHP_VERSION_ID >= 80300 && \is_string($now)) {
3242
$now = new \DateTimeImmutable($now, $timezone ?? new \DateTimeZone('UTC'));
43+
} elseif (\is_string($now)) {
44+
try {
45+
$now = new \DateTimeImmutable($now, $timezone ?? new \DateTimeZone('UTC'));
46+
} catch (\Exception $e) {
47+
throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e);
48+
}
3349
}
3450

3551
$this->now = null !== $timezone ? $now->setTimezone($timezone) : $now;
@@ -49,24 +65,37 @@ public function sleep(float|int $seconds): void
4965
$this->now = (new \DateTimeImmutable($now, $timezone))->setTimezone($timezone);
5066
}
5167

68+
/**
69+
* @throws \DateMalformedStringException When $modifier is invalid
70+
*/
5271
public function modify(string $modifier): void
5372
{
54-
try {
55-
$modifiedNow = @$this->now->modify($modifier);
56-
} catch (\DateMalformedStringException) {
57-
$modifiedNow = false;
58-
}
59-
if (false === $modifiedNow) {
60-
throw new \InvalidArgumentException(sprintf('Invalid modifier: "%s". Could not modify MockClock.', $modifier));
73+
if (\PHP_VERSION_ID < 80300) {
74+
$this->now = @$this->now->modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? sprintf('Invalid modifier: "%s". Could not modify MockClock.', $modifier));
75+
76+
return;
6177
}
6278

63-
$this->now = $modifiedNow;
79+
$this->now = $this->now->modify($modifier);
6480
}
6581

82+
/**
83+
* @throws \DateInvalidTimeZoneException When the timezone name is invalid
84+
*/
6685
public function withTimeZone(\DateTimeZone|string $timezone): static
6786
{
87+
if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) {
88+
$timezone = new \DateTimeZone($timezone);
89+
} elseif (\is_string($timezone)) {
90+
try {
91+
$timezone = new \DateTimeZone($timezone);
92+
} catch (\Exception $e) {
93+
throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e);
94+
}
95+
}
96+
6897
$clone = clone $this;
69-
$clone->now = $clone->now->setTimezone(\is_string($timezone) ? new \DateTimeZone($timezone) : $timezone);
98+
$clone->now = $clone->now->setTimezone($timezone);
7099

71100
return $clone;
72101
}

src/Symfony/Component/Clock/MonotonicClock.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ final class MonotonicClock implements ClockInterface
2222
private int $usOffset;
2323
private \DateTimeZone $timezone;
2424

25+
/**
26+
* @throws \DateInvalidTimeZoneException When $timezone is invalid
27+
*/
2528
public function __construct(\DateTimeZone|string $timezone = null)
2629
{
2730
if (false === $offset = hrtime()) {
@@ -32,11 +35,7 @@ public function __construct(\DateTimeZone|string $timezone = null)
3235
$this->sOffset = $time[1] - $offset[0];
3336
$this->usOffset = (int) ($time[0] * 1000000) - (int) ($offset[1] / 1000);
3437

35-
if (\is_string($timezone ??= date_default_timezone_get())) {
36-
$this->timezone = new \DateTimeZone($timezone);
37-
} else {
38-
$this->timezone = $timezone;
39-
}
38+
$this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone;
4039
}
4140

4241
public function now(): \DateTimeImmutable
@@ -71,10 +70,23 @@ public function sleep(float|int $seconds): void
7170
}
7271
}
7372

73+
/**
74+
* @throws \DateInvalidTimeZoneException When $timezone is invalid
75+
*/
7476
public function withTimeZone(\DateTimeZone|string $timezone): static
7577
{
78+
if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) {
79+
$timezone = new \DateTimeZone($timezone);
80+
} elseif (\is_string($timezone)) {
81+
try {
82+
$timezone = new \DateTimeZone($timezone);
83+
} catch (\Exception $e) {
84+
throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e);
85+
}
86+
}
87+
7688
$clone = clone $this;
77-
$clone->timezone = \is_string($timezone) ? new \DateTimeZone($timezone) : $timezone;
89+
$clone->timezone = $timezone;
7890

7991
return $clone;
8092
}

src/Symfony/Component/Clock/NativeClock.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@ final class NativeClock implements ClockInterface
2020
{
2121
private \DateTimeZone $timezone;
2222

23+
/**
24+
* @throws \DateInvalidTimeZoneException When $timezone is invalid
25+
*/
2326
public function __construct(\DateTimeZone|string $timezone = null)
2427
{
25-
if (\is_string($timezone ??= date_default_timezone_get())) {
26-
$this->timezone = new \DateTimeZone($timezone);
27-
} else {
28-
$this->timezone = $timezone;
29-
}
28+
$this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone;
3029
}
3130

3231
public function now(): \DateTimeImmutable
@@ -45,10 +44,23 @@ public function sleep(float|int $seconds): void
4544
}
4645
}
4746

47+
/**
48+
* @throws \DateInvalidTimeZoneException When $timezone is invalid
49+
*/
4850
public function withTimeZone(\DateTimeZone|string $timezone): static
4951
{
52+
if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) {
53+
$timezone = new \DateTimeZone($timezone);
54+
} elseif (\is_string($timezone)) {
55+
try {
56+
$timezone = new \DateTimeZone($timezone);
57+
} catch (\Exception $e) {
58+
throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e);
59+
}
60+
}
61+
5062
$clone = clone $this;
51-
$clone->timezone = \is_string($timezone) ? new \DateTimeZone($timezone) : $timezone;
63+
$clone->timezone = $timezone;
5264

5365
return $clone;
5466
}

src/Symfony/Component/Clock/Tests/MockClockTest.php

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,26 +92,19 @@ public function testModifyWithSpecificDateTime(string $modifiedNow, string $expe
9292

9393
public static function provideInvalidModifyStrings(): iterable
9494
{
95-
yield 'Named holiday is not recognized' => [
96-
'Halloween',
97-
'Invalid modifier: "Halloween". Could not modify MockClock.',
98-
];
99-
100-
yield 'empty string' => [
101-
'',
102-
'Invalid modifier: "". Could not modify MockClock.',
103-
];
95+
yield 'Named holiday is not recognized' => ['Halloween'];
96+
yield 'empty string' => [''];
10497
}
10598

10699
/**
107100
* @dataProvider provideInvalidModifyStrings
108101
*/
109-
public function testModifyThrowsOnInvalidString(string $modifiedNow, string $expectedMessage)
102+
public function testModifyThrowsOnInvalidString(string $modifiedNow)
110103
{
111104
$clock = new MockClock((new \DateTimeImmutable('2112-09-17 23:53:00.999Z'))->setTimezone(new \DateTimeZone('UTC')));
112105

113-
$this->expectException(\InvalidArgumentException::class);
114-
$this->expectExceptionMessage($expectedMessage);
106+
$this->expectException(\DateMalformedStringException::class);
107+
$this->expectExceptionMessage("Failed to parse time string ($modifiedNow)");
115108

116109
$clock->modify($modifiedNow);
117110
}

src/Symfony/Component/Clock/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
},
2121
"require": {
2222
"php": ">=8.1",
23-
"psr/clock": "^1.0"
23+
"psr/clock": "^1.0",
24+
"symfony/polyfill-php83": "^1.28"
2425
},
2526
"autoload": {
2627
"files": [ "Resources/now.php" ],

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy