-
Notifications
You must be signed in to change notification settings - Fork 65
Open
Description
I have been trying to understand what is the issue why it is setting the wrong month when using the ->month() fluent setter.
I outline how to reproduce the issue, which can be seen at the composeDatetime method.
At the bottom of the bug, you can see a workaround that does work, for the provided scenarios.
Function Body (and dependencies):
class Service
{
private function composeDatetime(?Chronos $eventDatetime, Chronos $eventSeriesDay): Chronos
{
if (null === $eventDatetime) {
throw new LogicException();
}
$year = $eventSeriesDay->year;
$month = $eventSeriesDay->month;
$day = $eventSeriesDay->day;
// This does not work
return Chronos::create()
->year($year)
->month($month)
->day($day)
->hour($eventDatetime->hour)
->minute($eventDatetime->minute)
->second($eventDatetime->second);
}
public function fixStartAndEndDatesInRequest(
EventRequest $eventRequest,
Chronos $eventSeriesDay,
): void
{
$datetimeStart = $eventRequest->getDatetimeStart();
$datetimeEnd = $eventRequest->getDatetimeEnd();
$eventDatetimeStart = $this->composeDatetime($datetimeStart, $eventSeriesDay);
$eventDatetimeEnd = $this->composeDatetime($datetimeEnd, $eventSeriesDay);
$eventRequest->setDatetimeStart($eventDatetimeStart);
$eventRequest->setDatetimeEnd($eventDatetimeEnd);
}
}// dependencies for reproduction
class EventRequest {
private ?Chronos $datetimeStart = null;
private ?Chronos $datetimeEnd = null;
public function setDatetimeStart(?Chronos $datetimeStart): self {
$this->datetimeStart = $datetimeStart;
return $this;
}
public function setDatetimeEnd(?Chronos $datetimeEnd): self {
$this->datetimeEnd = $datetimeEnd;
return $this;
}
public function getDatetimeStart(): ?Chronos {
return $this->datetimeStart;
}
public function getDatetimeEnd(): ?Chronos {
return $this->datetimeEnd;
}
}Unit Test:
class ChronosTest extends \PHPUnit\Framework\TestCase
{
#[DataProvider('dataProviderForFixStartAndEndDatesInRequestDto')]
public function testFixStartAndEndDatesInRequestDto(array $provider): void
{
$datetimeStart = $provider['datetimeStart'];
$datetimeEnd = $provider['datetimeEnd'];
$eventSeriesDay = $provider['eventSeriesDay'];
$expectedDateTimeStart = $provider['expectedDateTimeStart'];
$expectedDateTimeEnd = $provider['expectedDateTimeEnd'];
$requestDto = new EventRequest()
->setDatetimeStart($datetimeStart)
->setDatetimeEnd($datetimeEnd);
$this->service->fixStartAndEndDatesInRequest($requestDto, $eventSeriesDay);
// Assert the date parts are updated correctly
$this->assertEquals(
$expectedDateTimeStart,
$requestDto->getDatetimeStart()->toNative(),
'Start date was not updated correctly'
);
$this->assertEquals(
$expectedDateTimeEnd,
$requestDto->getDatetimeEnd()->toNative(),
'End date was not updated correctly'
);
// Assert the times are ok
$this->assertEquals(
$datetimeStart->toTimeString(),
$requestDto->getDatetimeStart()->toTimeString(),
'Start time was not updated correctly'
);
$this->assertEquals(
$datetimeEnd->toTimeString(),
$requestDto->getDatetimeEnd()->toTimeString(),
'End time was not updated correctly'
);
}
public static function dataProviderForFixStartAndEndDatesInRequestDto(): array
{
$datetimeStartOct = new Chronos('2025-10-27 09:00:00');
$datetimeEndOct = new Chronos('2025-10-27 11:00:00');
$datetimeStartJan = new Chronos('2026-01-29 09:00:00.000000');
$datetimeEndJan = new Chronos('2026-01-29 10:00:00.000000');
$datetimeStartOct2 = new Chronos('2025-10-29 09:00:00');
$datetimeEndOct2 = new Chronos('2025-10-29 11:00:00');
return [
'Event start date is on October, series day is in November' => [
[
'datetimeStart' => $datetimeStartOct,
'datetimeEnd' => $datetimeEndOct,
'eventSeriesDay' => new Chronos('2025-11-01 00:00:00'),
'expectedDateTimeStart' => $datetimeStartOct->setDate(2025, 11, 1)->toNative(),
'expectedDateTimeEnd' => $datetimeEndOct->setDate(2025, 11, 1)->toNative(),
],
],
'Event start date is on October, series day is in February' => [
[
'datetimeStart' => $datetimeStartOct,
'datetimeEnd' => $datetimeEndOct,
'eventSeriesDay' => new Chronos('2026-02-04 00:00:00'),
'expectedDateTimeStart' => $datetimeStartOct->setDate(2026, 2, 4)->toNative(),
'expectedDateTimeEnd' => $datetimeEndOct->setDate(2026, 2, 4)->toNative(),
],
],
'Event start date is on October, series day is in February (2)' => [
[
'datetimeStart' => $datetimeStartOct2,
'datetimeEnd' => $datetimeEndOct2,
'eventSeriesDay' => new Chronos('2026-02-04 00:00:00'),
'expectedDateTimeStart' => $datetimeStartOct2->setDate(2026, 2, 4)->toNative(),
'expectedDateTimeEnd' => $datetimeEndOct2->setDate(2026, 2, 4)->toNative(),
],
],
'Event start date is on January, series day is in February' => [
[
'datetimeStart' => $datetimeStartJan,
'datetimeEnd' => $datetimeEndJan,
'eventSeriesDay' => new Chronos('2026-02-04 00:00:00'),
'expectedDateTimeStart' => $datetimeStartJan->setDate(2026, 2, 4)->toNative(),
'expectedDateTimeEnd' => $datetimeEndJan->setDate(2026, 2, 4)->toNative(),
],
],
];
}
}Now, if using the Chronos::create with parameters, straight away, it does work.
private function composeDatetime(?Chronos $eventDatetime, Chronos $eventSeriesDay): Chronos
{
if (null === $eventDatetime) {
throw new LogicException();
}
$year = $eventSeriesDay->year;
$month = $eventSeriesDay->month;
$day = $eventSeriesDay->day;
// works
return Chronos::create(
$year,
$month,
$day,
$eventDatetime->hour,
$eventDatetime->minute,
$eventDatetime->second,
);
}