Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Chronos.php
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,12 @@ public static function createFromTime(
/**
* Create an instance from a specific format
*
* Unlike PHP's native DateTimeImmutable::createFromFormat(), this method
* automatically appends the `|` modifier if no reset modifier (`|` or `!`)
* is present. This ensures that unparsed components are reset to zero
* instead of being filled from the current time, providing more predictable
* and deterministic behavior.
*
* @param string $format The date() compatible format string.
* @param string $time The formatted date string to interpret.
* @param \DateTimeZone|string|null $timezone The DateTimeZone object or timezone name the new instance should use.
Expand All @@ -679,6 +685,12 @@ public static function createFromFormat(
string $time,
DateTimeZone|string|null $timezone = null,
): static {
// Auto-append | modifier if no reset modifier is present
// This ensures unparsed components are zero instead of current time
if (!str_contains($format, '|') && !str_contains($format, '!')) {
$format .= '|';
}

if ($timezone !== null) {
$dateTime = parent::createFromFormat($format, $time, $timezone ? static::safeCreateDateTimeZone($timezone) : null);
} else {
Expand Down
61 changes: 58 additions & 3 deletions tests/TestCase/DateTime/CreateFromFormatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,49 @@ public function testCreateFromFormatReturnsInstance()
$this->assertTrue($d instanceof Chronos);
}

public function testCreateFromFormatMissingTimeIsZero()
{
// Missing time components should be zero, not current time
$d = Chronos::createFromFormat('Y-m-d', '2024-03-14');
$this->assertDateTime($d, 2024, 3, 14, 0, 0, 0);
$this->assertSame(0, $d->micro);
}

public function testCreateFromFormatMissingSecondsIsZero()
{
// Missing seconds should be zero
$d = Chronos::createFromFormat('Y-m-d H:i', '2024-03-14 12:30');
$this->assertDateTime($d, 2024, 3, 14, 12, 30, 0);
}

public function testCreateFromFormatMissingDateIsEpoch()
{
// Missing date components should be Unix epoch (1970-01-01)
$d = Chronos::createFromFormat('H:i:s', '12:30:45');
$this->assertDateTime($d, 1970, 1, 1, 12, 30, 45);
}

public function testCreateFromFormatMissingMicrosecondsIsZero()
{
// Missing microseconds should be zero
$d = Chronos::createFromFormat('Y-m-d H:i:s', '2024-03-14 12:30:45');
$this->assertSame(0, $d->micro);
}

public function testCreateFromFormatWithExplicitPipeModifier()
{
// Explicit | should still work
$d = Chronos::createFromFormat('Y-m-d|', '2024-03-14');
$this->assertDateTime($d, 2024, 3, 14, 0, 0, 0);
}

public function testCreateFromFormatWithExplicitBangModifier()
{
// Explicit ! should still work
$d = Chronos::createFromFormat('!Y-m-d', '2024-03-14');
$this->assertDateTime($d, 2024, 3, 14, 0, 0, 0);
}

public function testCreateFromFormatWithTimezoneString()
{
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', 'Europe/London');
Expand All @@ -49,6 +92,18 @@ public function testCreateFromFormatWithMillis()
$this->assertSame(254687, $d->micro);
}

public function testCreateFromFormatWithUnixTimestamp()
{
$d = Chronos::createFromFormat('U', '0');
$this->assertDateTime($d, 1970, 1, 1, 0, 0, 0);
}

public function testCreateFromFormatWithNegativeUnixTimestamp()
{
$d = Chronos::createFromFormat('U', '-1000');
$this->assertDateTime($d, 1969, 12, 31, 23, 43, 20);
}

public function testCreateFromFormatInvalidFormat()
{
$parseException = null;
Expand All @@ -65,9 +120,9 @@ public function testCreateFromFormatInvalidFormat()

public function testCreateFromFormatDoesNotUseTestNow()
{
// createFromFormat should not use testNow - it should behave like PHP's native method
// testNow should not affect createFromFormat - missing components are zero
Chronos::setTestNow(new Chronos('2020-12-01 14:30:45'));
$d = Chronos::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11');
$this->assertDateTime($d, 1975, 5, 21, 22, 32, 11);
$d = Chronos::createFromFormat('Y-m-d', '2024-03-14');
$this->assertDateTime($d, 2024, 3, 14, 0, 0, 0);
}
}
Loading