8000 feature #51412 [Clock] Throw `DateMalformedStringException`/`DateInva… · symfony/symfony@bb8c76d · GitHub
[go: up one dir, main page]

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)
0