Skip to content

Commit d158df1

Browse files
[Clock] Add DateTime: an immutable implementation with strict error handling and return types
1 parent bb8c76d commit d158df1

File tree

11 files changed

+217
-25
lines changed

11 files changed

+217
-25
lines changed

src/Symfony/Component/Clock/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.4
55
---
66

7+
* Add `DateTime`: an immutable implementation with strict error handling and return types
78
* Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate
89

910
6.3

src/Symfony/Component/Clock/Clock.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ public static function set(PsrClockInterface $clock): void
4444
self::$globalClock = $clock instanceof ClockInterface ? $clock : new self($clock);
4545
}
4646

47-
public function now(): \DateTimeImmutable
47+
public function now(): DateTime
4848
{
4949
$now = ($this->clock ?? self::get())->now();
5050

51+
if (!$now instanceof DateTime) {
52+
$now = DateTime::createFromInterface($now);
53+
}
54+
5155
return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now;
5256
}
5357

src/Symfony/Component/Clock/ClockAwareTrait.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ public function setClock(ClockInterface $clock): void
2929
$this->clock = $clock;
3030
}
3131

32+
/**
33+
* @return DateTime
34+
*/
3235
protected function now(): \DateTimeImmutable
3336
{
34-
return ($this->clock ??= new Clock())->now();
37+
$now = ($this->clock ??= new Clock())->now();
38+
39+
return $now instanceof DateTime ? $now : DateTime::createFromInterface($now);
3540
}
3641
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Clock;
13+
14+
use Psr\Clock\ClockInterface;
15+
16+
/**
17+
* An immmutable DateTime with stricter error handling and return types than the native one.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
final class DateTime extends \DateTimeImmutable
22+
{
23+
/**
24+
* @throws \DateMalformedStringException When $datetime is invalid
25+
*/
26+
public function __construct(string $datetime = 'now', \DateTimeZone $timezone = null, parent $reference = null)
27+
{
28+
$now = $reference ?? Clock::get()->now();
29+
30+
if ('now' !== $datetime) {
31+
if (!$now instanceof static) {
32+
$now = static::createFromInterface($now);
33+
}
34+
35+
$timezone = (new parent($datetime, $timezone ?? $now->getTimezone()))->getTimezone();
36+
$now = $now->setTimeZone($timezone)->modify($datetime);
37+
} elseif (null !== $timezone) {
38+
$now = $now->setTimezone($timezone);
39+
}
40+
41+
if (\PHP_VERSION_ID < 80200) {
42+
$now = (array) $now;
43+
$this->date = $now['date'];
44+
$this->timezone_type = $now['timezone_type'];
45+
$this->timezone = $now['timezone'];
46+
$this->__wakeup();
47+
48+
return;
49+
}
50+
51+
$this->__unserialize((array) $now);
52+
}
53+
54+
/**
55+
* @throws \DateMalformedStringException When $format or $datetime are invalid
56+
*/
57+
public static function createFromFormat(string $format, string $datetime, \DateTimeZone $timezone = null): static
58+
{
59+
return parent::createFromFormat($format, $datetime, $timezone) ?: throw new \DateMalformedStringException(static::getLastErrors()['errors'][0] ?? 'Invalid date string or format.');
60+
}
61+
62+
public static function createFromInterface(\DateTimeInterface $object): static
63+
{
64+
return parent::createFromInterface($object);
65+
}
66+
67+
public static function createFromMutable(\DateTime $object): static
68+
{
69+
return parent::createFromMutable($object);
70+
}
71+
72+
public function add(\DateInterval $interval): static
73+
{
74+
return parent::add($interval);
75+
}
76+
77+
public function sub(\DateInterval $interval): static
78+
{
79+
return parent::sub($interval);
80+
}
81+
82+
/**
83+
* @throws \DateMalformedStringException When $modifier is invalid
84+
*/
85+
public function modify(string $modifier): static
86+
{
87+
if (\PHP_VERSION_ID < 80300) {
88+
return @parent::modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? sprintf('Invalid modifier: "%s".', $modifier));
89+
}
90+
91+
return parent::modify($modifier);
92+
}
93+
94+
public function setTimestamp(int $value): static
95+
{
96+
return parent::setTimestamp($value);
97+
}
98+
99+
public function setDate(int $year, int $month, int $day): static
100+
{
101+
return parent::setDate($year, $month, $day);
102+
}
103+
104+
public function setISODate(int $year, int $week, int $day = 1): static
105+
{
106+
return parent::setISODate($year, $week, $day);
107+
}
108+
109+
public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static
110+
{
111+
return parent::setTime($hour, $minute, $second, $microsecond);
112+
}
113+
114+
public function setTimeZone(\DateTimeZone $timezone): static
115+
{
116+
return parent::setTimeZone($timezone);
117+
}
118+
119+
public function getTimezone(): \DateTimeZone
120+
{
121+
return parent::getTimezone() ?: throw new \DateInvalidTimeZoneException('The DateTime object has no timezone.');
122+
}
123+
}

src/Symfony/Component/Clock/MockClock.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121
final class MockClock implements ClockInterface
2222
{
23-
private \DateTimeImmutable $now;
23+
private DateTime $now;
2424

2525
/**
2626
* @throws \DateMalformedStringException When $now is invalid
@@ -38,20 +38,16 @@ public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZon
3838
}
3939
}
4040

41-
if (\PHP_VERSION_ID >= 80300 && \is_string($now)) {
42-
$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-
}
41+
if (\is_string($now)) {
42+
$now = new DateTime($now, $timezone ?? new \DateTimeZone('UTC'));
43+
} elseif (!$now instanceof DateTime) {
44+
$now = DateTime::createFromInterface($now);
4945
}
5046

5147
$this->now = null !== $timezone ? $now->setTimezone($timezone) : $now;
5248
}
5349

54-
public function now(): \DateTimeImmutable
50+
public function now(): DateTime
5551
{
5652
return clone $this->now;
5753
}
@@ -62,7 +58,7 @@ public function sleep(float|int $seconds): void
6258
$now = substr_replace(sprintf('@%07.0F', $now), '.', -6, 0);
6359
$timezone = $this->now->getTimezone();
6460

65-
$this->now = (new \DateTimeImmutable($now, $timezone))->setTimezone($timezone);
61+
$this->now = DateTime::createFromInterface(new \DateTimeImmutable($now, $timezone))->setTimezone($timezone);
6662
}
6763

6864
/**

src/Symfony/Component/Clock/MonotonicClock.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __construct(\DateTimeZone|string $timezone = null)
3838
$this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone;
3939
}
4040

41-
public function now(): \DateTimeImmutable
41+
public function now(): DateTime
4242
{
4343
[$s, $us] = hrtime();
4444

@@ -56,7 +56,7 @@ public function now(): \DateTimeImmutable
5656

5757
$now = '@'.($s + $this->sOffset).'.'.$now;
5858

59-
return (new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone);
59+
return DateTime::createFromInterface(new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone);
6060
}
6161

6262
public function sleep(float|int $seconds): void

src/Symfony/Component/Clock/NativeClock.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ public function __construct(\DateTimeZone|string $timezone = null)
2828
$this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone;
2929
}
3030

31-
public function now(): \DateTimeImmutable
31+
public function now(): DateTime
3232
{
33-
return new \DateTimeImmutable('now', $this->timezone);
33+
return DateTime::createFromInterface(new \DateTimeImmutable('now', $this->timezone));
3434
}
3535

3636
public function sleep(float|int $seconds): void

src/Symfony/Component/Clock/Resources/now.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313

1414
if (!\function_exists(now::class)) {
1515
/**
16-
* Returns the current time as a DateTimeImmutable.
17-
*
18-
* Note that you should prefer injecting a ClockInterface or using
19-
* ClockAwareTrait when possible instead of using this function.
16+
* Returns the current time as a \Symfony\Component\Clock\DateTime.
2017
*/
21-
function now(): \DateTimeImmutable
18+
function now(): DateTime
2219
{
23-
return Clock::get()->now();
20+
$now = Clock::get()->now();
21+
22+
return $now instanceof DateTime ? $now : DateTime::createFromInterface($now);
2423
}
2524
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Clock\ClockAwareTrait;
16+
use Symfony\Component\Clock\DateTime;
1617
use Symfony\Component\Clock\MockClock;
1718

1819
class ClockAwareTraitTest extends TestCase
@@ -25,7 +26,7 @@ public function testTrait()
2526
}
2627
};
2728

28-
$this->assertInstanceOf(\DateTimeImmutable::class, $sut->now());
29+
$this->assertInstanceOf(DateTime::class, $sut->now());
2930

3031
$clock = new MockClock();
3132
$sut = new $sut();

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Psr\Clock\ClockInterface;
1616
use Symfony\Component\Clock\Clock;
17+
use Symfony\Component\Clock\DateTime;
1718
use Symfony\Component\Clock\MockClock;
1819
use Symfony\Component\Clock\NativeClock;
1920
use Symfony\Component\Clock\Test\ClockSensitiveTrait;
@@ -35,7 +36,7 @@ public function testMockClock()
3536

3637
public function testNativeClock()
3738
{
38-
$this->assertInstanceOf(\DateTimeImmutable::class, now());
39+
$this->assertInstanceOf(DateTime::class, now());
3940
$this->assertInstanceOf(NativeClock::class, Clock::get());
4041
}
4142

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