Skip to content

Commit b8cdd8c

Browse files
committed
Make testNow in createFromFormat opt-in for BC
The change in 3.3.2 that made createFromFormat automatically respect testNow for missing components was a BC break. Users expect createFromFormat to behave exactly like PHP's native method by default. This makes the testNow behavior opt-in via a new $useTestNow parameter: Chronos::createFromFormat('Y-m-d', '2024-01-15', null, useTestNow: true); When useTestNow is false (default), createFromFormat behaves exactly like PHP's native DateTimeImmutable::createFromFormat. When useTestNow is true and testNow is set, missing date/time components will be filled from testNow instead of current time.
1 parent 77a248b commit b8cdd8c

2 files changed

Lines changed: 52 additions & 87 deletions

File tree

src/Chronos.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,13 +639,16 @@ public static function createFromTime(
639639
* @param string $format The date() compatible format string.
640640
* @param string $time The formatted date string to interpret.
641641
* @param \DateTimeZone|string|null $timezone The DateTimeZone object or timezone name the new instance should use.
642+
* @param bool $useTestNow When true and testNow is set, missing date/time components will be
643+
* filled from testNow instead of the current time. Defaults to false for BC compatibility.
642644
* @return static
643645
* @throws \InvalidArgumentException
644646
*/
645647
public static function createFromFormat(
646648
string $format,
647649
string $time,
648650
DateTimeZone|string|null $timezone = null,
651+
bool $useTestNow = false,
649652
): static {
650653
if ($timezone !== null) {
651654
$dateTime = parent::createFromFormat($format, $time, $timezone ? static::safeCreateDateTimeZone($timezone) : null);
@@ -661,7 +664,7 @@ public static function createFromFormat(
661664
}
662665

663666
$testNow = static::getTestNow();
664-
if ($testNow !== null) {
667+
if ($useTestNow && $testNow !== null) {
665668
$dateTime = static::applyTestNowToMissingComponents($dateTime, $format, $testNow);
666669
}
667670

tests/TestCase/DateTime/CreateFromFormatTest.php

Lines changed: 48 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -29,135 +29,97 @@ public function testCreateFromFormatReturnsInstance()
2929
$this->assertTrue($d instanceof Chronos);
3030
}
3131

32-
public function testCreateFromFormatWithTestNowMissingYear()
32+
public function testCreateFromFormatWithTimezoneString()
3333
{
34-
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
35-
$d = Chronos::createFromFormat('m-d H:i:s', '10-05 09:15:30');
36-
$this->assertDateTime($d, 2020, 10, 5, 9, 15, 30);
34+
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', 'Europe/London');
35+
$this->assertDateTime($d, 1975, 5, 21, 22, 32, 11);
36+
$this->assertSame('Europe/London', $d->tzName);
3737
}
3838

39-
public function testCreateFromFormatWithTestNowMissingDate()
39+
public function testCreateFromFormatWithTimezone()
4040
{
41-
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
42-
$d = Chronos::createFromFormat('H:i:s', '09:15:30');
43-
$this->assertDateTime($d, 2020, 12, 1, 9, 15, 30);
41+
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', new DateTimeZone('Europe/London'));
42+
$this->assertDateTime($d, 1975, 5, 21, 22, 32, 11);
43+
$this->assertSame('Europe/London', $d->tzName);
4444
}
4545

46-
public function testCreateFromFormatWithTestNowMissingTime()
46+
public function testCreateFromFormatWithMillis()
4747
{
48-
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
49-
$d = Chronos::createFromFormat('Y-m-d', '2021-06-15');
50-
$this->assertDateTime($d, 2021, 6, 15, 14, 30, 45);
48+
$d = Chronos::createFromFormat('Y-m-d H:i:s.u', '1975-05-21 22:32:11.254687');
49+
$this->assertSame(254687, $d->micro);
5150
}
5251

53-
public function testCreateFromFormatWithTestNowPartialDate()
52+
public function testCreateFromFormatInvalidFormat()
5453
{
55-
Chronos::setTestNow(new Chronos('2020-12-01 00:00:00'));
56-
$d = Chronos::createFromFormat('m-d', '10-05');
57-
$this->assertDateTime($d, 2020, 10, 5, 0, 0, 0);
58-
}
54+
$parseException = null;
55+
try {
56+
Chronos::createFromFormat('Y-m-d H:i:s.u', '1975-05-21');
57+
} catch (InvalidArgumentException $e) {
58+
$parseException = $e;
59+
}
5960

60-
public function testCreateFromFormatWithTestNowDayOnly()
61-
{
62-
Chronos::setTestNow(new Chronos('2020-12-01 00:00:00'));
63-
$d = Chronos::createFromFormat('d', '05');
64-
$this->assertDateTime($d, 2020, 12, 5, 0, 0, 0);
61+
$this->assertNotNull($parseException);
62+
$this->assertIsArray(Chronos::getLastErrors());
63+
$this->assertNotEmpty(Chronos::getLastErrors()['errors']);
6564
}
6665

67-
public function testCreateFromFormatWithTestNowComplete()
66+
public function testCreateFromFormatDoesNotUseTestNowByDefault()
6867
{
69-
// When format is complete, testNow should not affect the result
68+
// By default, createFromFormat should not use testNow (BC behavior)
7069
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
7170
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11');
7271
$this->assertDateTime($d, 1975, 5, 21, 22, 32, 11);
7372
}
7473

75-
public function testCreateFromFormatWithTestNowResetModifier()
74+
public function testCreateFromFormatWithUseTestNowMissingYear()
7675
{
77-
// The '!' modifier resets to Unix epoch, should not use testNow
7876
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
79-
$d = Chronos::createFromFormat('!Y-m-d', '2021-06-15');
80-
$this->assertDateTime($d, 2021, 6, 15, 0, 0, 0);
77+
$d = Chronos::createFromFormat('m-d H:i:s', '10-05 09:15:30', null, true);
78+
$this->assertDateTime($d, 2020, 10, 5, 9, 15, 30);
8179
}
8280

83-
public function testCreateFromFormatWithTestNowPipeModifier()
81+
public function testCreateFromFormatWithUseTestNowMissingDate()
8482
{
85-
// The '|' modifier resets unspecified components to zero, should not use testNow
8683
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
87-
$d = Chronos::createFromFormat('Y-m-d|', '2021-06-15');
88-
$this->assertDateTime($d, 2021, 6, 15, 0, 0, 0);
84+
$d = Chronos::createFromFormat('H:i:s', '09:15:30', null, true);
85+
$this->assertDateTime($d, 2020, 12, 1, 9, 15, 30);
8986
}
9087

91-
public function testCreateFromFormatWithoutTestNow()
88+
public function testCreateFromFormatWithUseTestNowMissingTime()
9289
{
93-
// Without testNow set, behavior should use real current time for missing components
94-
Chronos::setTestNow(null);
95-
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11');
96-
$this->assertDateTime($d, 1975, 5, 21, 22, 32, 11);
90+
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
91+
$d = Chronos::createFromFormat('Y-m-d', '2021-06-15', null, true);
92+
$this->assertDateTime($d, 2021, 6, 15, 14, 30, 45);
9793
}
9894

99-
public function testCreateFromFormatWithTestNowEscapedCharacters()
95+
public function testCreateFromFormatWithUseTestNowResetModifier()
10096
{
101-
// Escaped format characters should not be treated as format specifiers
97+
// The '!' modifier resets to Unix epoch, should not use testNow even when opted in
10298
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
103-
$d = Chronos::createFromFormat('\Y\-m-d', 'Y-10-05');
104-
$this->assertDateTime($d, 2020, 10, 5, 14, 30, 45);
99+
$d = Chronos::createFromFormat('!Y-m-d', '2021-06-15', null, true);
100+
$this->assertDateTime($d, 2021, 6, 15, 0, 0, 0);
105101
}
106102

107-
public function testCreateFromFormatWithTestNowMicroseconds()
103+
public function testCreateFromFormatWithUseTestNowPipeModifier()
108104
{
109-
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45.123456'));
110-
$d = Chronos::createFromFormat('Y-m-d H:i:s', '2021-06-15 09:15:30');
111-
$this->assertSame(123456, $d->micro);
105+
// The '|' modifier resets unspecified components to zero, should not use testNow
106+
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
107+
$d = Chronos::createFromFormat('Y-m-d|', '2021-06-15', null, true);
108+
$this->assertDateTime($d, 2021, 6, 15, 0, 0, 0);
112109
}
113110

114-
public function testCreateFromFormatWithTestNowUnixTimestamp()
111+
public function testCreateFromFormatWithUseTestNowUnixTimestamp()
115112
{
116113
// Unix timestamp ('U' format) sets all components, should not use testNow
117114
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
118-
$d = Chronos::createFromFormat('U', '0');
115+
$d = Chronos::createFromFormat('U', '0', null, true);
119116
$this->assertDateTime($d, 1970, 1, 1, 0, 0, 0);
120117
}
121118

122-
public function testCreateFromFormatWithTestNowNegativeUnixTimestamp()
123-
{
124-
// Negative Unix timestamp should also not use testNow
125-
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
126-
$d = Chronos::createFromFormat('U', '-1000');
127-
$this->assertDateTime($d, 1969, 12, 31, 23, 43, 20);
128-
}
129-
130-
public function testCreateFromFormatWithTimezoneString()
131-
{
132-
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', 'Europe/London');
133-
$this->assertDateTime($d, 1975, 5, 21, 22, 32, 11);
134-
$this->assertSame('Europe/London', $d->tzName);
135-
}
136-
137-
public function testCreateFromFormatWithTimezone()
138-
{
139-
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', new DateTimeZone('Europe/London'));
140-
$this->assertDateTime($d, 1975, 5, 21, 22, 32, 11);
141-
$this->assertSame('Europe/London', $d->tzName);
142-
}
143-
144-
public function testCreateFromFormatWithMillis()
145-
{
146-
$d = Chronos::createFromFormat('Y-m-d H:i:s.u', '1975-05-21 22:32:11.254687');
147-
$this->assertSame(254687, $d->micro);
148-
}
149-
150-
public function testCreateFromFormatInvalidFormat()
119+
public function testCreateFromFormatWithUseTestNowMicroseconds()
151120
{
152-
$parseException = null;
153-
try {
154-
Chronos::createFromFormat('Y-m-d H:i:s.u', '1975-05-21');
155-
} catch (InvalidArgumentException $e) {
156-
$parseException = $e;
157-
}
158-
159-
$this->assertNotNull($parseException);
160-
$this->assertIsArray(Chronos::getLastErrors());
161-
$this->assertNotEmpty(Chronos::getLastErrors()['errors']);
121+
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45.123456'));
122+
$d = Chronos::createFromFormat('Y-m-d H:i:s', '2021-06-15 09:15:30', null, true);
123+
$this->assertSame(123456, $d->micro);
162124
}
163125
}

0 commit comments

Comments
 (0)