"Same" DateInterval instances lead to different results, depending on the origin (in combination with DST-switches)
Open
Nobody has claimed this yet.
Bug
Extension: date
Status: Needs Triage
- Dominant language
- C
- Stars
- 40.4k
- Forks
- 8.1k
- Avg merge
- 2d 13h
- Merged PRs (30d)
- 96
Description
Description
The following code:
<?php
function getIntervalSpecificationFromDateInterval(DateInterval $interval): string
{
$specification = '';
if ($interval->invert) {
$specification .= '-';
}
$specification .= 'P';
$specification .= $interval->y ? $interval->y . 'Y' : '';
$specification .= $interval->m ? $interval->m . 'M' : '';
$specification .= $interval->d ? $interval->d . 'D' : '';
if ($interval->h + $interval->i + $interval->s) {
$specification .= 'T';
$specification .= $interval->h ? $interval->h . 'H' : '';
$specification .= $interval->i ? $interval->i . 'M' : '';
$specification .= $interval->s ? $interval->s . 'S' : '';
}
if ($specification === 'P' || $specification === '-P') {
$specification = 'PT0S';
}
return $specification;
}
$timezone = new DateTimeZone('Europe/Berlin');
$dstSwitchA = new DateTimeImmutable('2025-03-30 01:00:00', $timezone);
$dstSwitchB = new DateTimeImmutable('2025-10-26 01:00:00', $timezone);
$durationA = new DateInterval('PT3H');
$durationB = (new DateTimeImmutable('2025-03-28 01:00:00', $timezone))->diff(
new DateTimeImmutable('2025-03-28 04:00:00', $timezone),
true
);
$durationC = (new DateTimeImmutable('2025-03-28 01:00:00', $timezone))->diff(
new DateTimeImmutable('2025-03-28 04:00:00', $timezone),
);
$durationD = (new DateTimeImmutable('2025-03-28 01:00:00', $timezone))->diff(
new DateTimeImmutable('2025-03-28 04:00:00', $timezone),
);
$durationD = new DateInterval(getIntervalSpecificationFromDateInterval($durationD));
// Test 1st DST-switch (+1 hour)
echo 'TEST 1 (DST-switch +1 hour)' . PHP_EOL;
$datetimeA = $dstSwitchA->setTimezone($timezone)->add($durationA);
echo 'Constructed: ' . $datetimeA->format('Y-m-d H:i:s') . PHP_EOL; // expected: 2025-03-30 05:00:00
$datetimeB = $dstSwitchA->setTimezone($timezone)->add($durationB);
echo 'Diff (absolute): ' . $datetimeB->format('Y-m-d H:i:s') . PHP_EOL; // expected: 2025-03-30 05:00:00
$datetimeC = $dstSwitchA->setTimezone($timezone)->add($durationC);
echo 'Diff (relative): ' . $datetimeC->format('Y-m-d H:i:s') . PHP_EOL; // expected: 2025-03-30 05:00:00
$datetimeD = $dstSwitchA->setTimezone($timezone)->add($durationD);
echo 'Workaround: ' . $datetimeD->format('Y-m-d H:i:s') . PHP_EOL; // expected: 2025-03-30 05:00:00
echo PHP_EOL;
echo 'TEST 2 (DST-switch -1 hour)' . PHP_EOL;
// Test 2nd DST-switch (-1 hour)
// Expected value: 2025-10-26 03:00:00
$datetimeA = $dstSwitchB->setTimezone($timezone)->add($durationA);
echo 'Constructed: ' . $datetimeA->format('Y-m-d H:i:s') . PHP_EOL;
$datetimeB = $dstSwitchB->setTimezone($timezone)->add($durationB);
echo 'Diff (absolute): ' . $datetimeB->format('Y-m-d H:i:s') . PHP_EOL;
$datetimeC = $dstSwitchB->setTimezone($timezone)->add($durationC);
echo 'Diff (relative): ' . $datetimeC->format('Y-m-d H:i:s') . PHP_EOL;
$datetimeD = $dstSwitchB->setTimezone($timezone)->add($durationD);
echo 'Workaround: ' . $datetimeD->format('Y-m-d H:i:s') . PHP_EOL;
Resulted in this output:
TEST 1 (DST-switch +1 hour)
Constructed: 2025-03-30 05:00:00
Diff (absolute): 2025-03-30 04:00:00
Diff (relative): 2025-03-30 04:00:00
Workaround: 2025-03-30 05:00:00
TEST 2 (DST-switch -1 hour)
Constructed: 2025-10-26 03:00:00
Diff (absolute): 2025-10-26 04:00:00
Diff (relative): 2025-10-26 04:00:00
Workaround: 2025-10-26 03:00:00
But I expected this output instead:
TEST 1 (DST-switch +1 hour)
Constructed: 2025-03-30 05:00:00
Diff (absolute): 2025-03-30 05:00:00
Diff (relative): 2025-03-30 05:00:00
Workaround: 2025-03-30 05:00:00
TEST 2 (DST-switch -1 hour)
Constructed: 2025-10-26 03:00:00
Diff (absolute): 2025-10-26 03:00:00
Diff (relative): 2025-10-26 03:00:00
Workaround: 2025-10-26 03:00:00
PHP Version
PHP 8.3.16 (cli) (built: Jan 14 2025 20:07:09) (NTS Visual C++ 2019 x64)
Copyright (c) The PHP Group
Zend Engine v4.3.16, Copyright (c) Zend Technologies
with Zend OPcache v8.3.16, Copyright (c), by Zend Technologies
with Xdebug v3.4.1, Copyright (c) 2002-2025, by Derick Rethans
Tested with PHP 8.1.0 to 8.4.8
Operating System
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the provided PHP reproduction against the DateTimeImmutable::add() and DateInterval behavior described in the issue, comparing intervals created by the constructor and DateTimeImmutable::diff(). Trace the PHP date/time implementation responsible for DST transitions; done means equivalent three-hour intervals produce the expected timestamps in both DST-switch cases, with regression coverage for the demonstrated scenarios.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100