From 29c247b514a2e930f153df37ed8c9607e65be6d1 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 3 Apr 2026 06:55:12 +0200 Subject: [PATCH 1/2] Add timestamp-based add/sub methods for DST-safe time arithmetic Adds new methods that use Unix timestamp arithmetic instead of wall clock time, ensuring correct behavior across DST transitions: - addHoursWithTimestamp() / subHoursWithTimestamp() - addMinutesWithTimestamp() / subMinutesWithTimestamp() - addSecondsWithTimestamp() / subSecondsWithTimestamp() These methods are the true inverse of diffInMinutes/diffInSeconds/diffInHours, which return elapsed time based on timestamps. --- docs/en/modifying.md | 55 +++++- src/Chronos.php | 84 +++++++++ tests/TestCase/DateTime/TimestampAddTest.php | 184 +++++++++++++++++++ 3 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 tests/TestCase/DateTime/TimestampAddTest.php diff --git a/docs/en/modifying.md b/docs/en/modifying.md index c5a96a63..cb61884a 100644 --- a/docs/en/modifying.md +++ b/docs/en/modifying.md @@ -42,6 +42,12 @@ Available add/sub methods: - `addMinutes()` / `subMinutes()` - `addSeconds()` / `subSeconds()` +For DST-safe operations that add actual elapsed time (see [DST Considerations](#dst-considerations)): + +- `addHoursWithTimestamp()` / `subHoursWithTimestamp()` +- `addMinutesWithTimestamp()` / `subMinutesWithTimestamp()` +- `addSecondsWithTimestamp()` / `subSecondsWithTimestamp()` + ### Month Overflow Handling By default, adding months will clamp the day if it would overflow: @@ -169,8 +175,53 @@ information and you need to assign the correct timezone. When modifying dates/times across DST (Daylight Savings Time) transitions, your operations may gain/lose an additional hour resulting in values that -don't add up. You can avoid these issues by first changing your timezone to -UTC, modifying the time, then converting back: +don't add up. Methods like `addHours()`, `addMinutes()`, and `addSeconds()` +add "wall clock" time, which can produce unexpected results during DST +transitions. + +### Timestamp-Based Methods + +For operations that need to add actual elapsed time (not wall clock time), +use the timestamp-based variants: + +- `addHoursWithTimestamp()` / `subHoursWithTimestamp()` +- `addMinutesWithTimestamp()` / `subMinutesWithTimestamp()` +- `addSecondsWithTimestamp()` / `subSecondsWithTimestamp()` + +These methods manipulate the Unix timestamp directly, ensuring that adding +600 minutes always means exactly 36000 seconds of elapsed time: + +```php +// Australia/Melbourne DST ends April 5, 2026 at 3:00 AM +// Clocks go back from 3:00 AM AEDT (+11) to 2:00 AM AEST (+10) +$startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); + +// Wall clock addition - adds 10 hours of "clock time" +$wallClock = $startOfDay->addMinutes(600); +// Result: 2026-04-05T10:00:00+10:00 + +// Timestamp addition - adds 10 hours of elapsed time +$elapsed = $startOfDay->addMinutesWithTimestamp(600); +// Result: 2026-04-05T09:00:00+10:00 +``` + +The timestamp-based methods ensure that `diffInMinutes()` and +`addMinutesWithTimestamp()` are true inverses of each other: + +```php +$time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne'); +$startOfDay = $time->startOfDay(); + +$diff = $time->diffInMinutes($startOfDay); // 600 + +// Reconstructing the original time works correctly +$reconstructed = $startOfDay->addMinutesWithTimestamp($diff); +// $reconstructed equals $time +``` + +### Manual UTC Conversion + +Alternatively, you can manually convert to UTC, modify, then convert back: ```php // Additional hour gained diff --git a/src/Chronos.php b/src/Chronos.php index d1e7d2da..98ec6712 100644 --- a/src/Chronos.php +++ b/src/Chronos.php @@ -1470,6 +1470,90 @@ public function subSeconds(int $value): static return $this->addSeconds(-$value); } + /** + * Add hours to the instance using timestamp arithmetic. + * + * Unlike `addHours()` which uses wall clock time, this method + * adds actual elapsed time by manipulating the Unix timestamp. + * This is important when working across DST transitions where + * wall clock time and elapsed time differ. + * + * @param int $value The number of hours to add. + * @return static + */ + public function addHoursWithTimestamp(int $value): static + { + return $this->setTimestamp($this->getTimestamp() + ($value * 3600)); + } + + /** + * Remove hours from the instance using timestamp arithmetic. + * + * @param int $value The number of hours to remove. + * @return static + * @see addHoursWithTimestamp() + */ + public function subHoursWithTimestamp(int $value): static + { + return $this->addHoursWithTimestamp(-$value); + } + + /** + * Add minutes to the instance using timestamp arithmetic. + * + * Unlike `addMinutes()` which uses wall clock time, this method + * adds actual elapsed time by manipulating the Unix timestamp. + * This is important when working across DST transitions where + * wall clock time and elapsed time differ. + * + * @param int $value The number of minutes to add. + * @return static + */ + public function addMinutesWithTimestamp(int $value): static + { + return $this->setTimestamp($this->getTimestamp() + ($value * 60)); + } + + /** + * Remove minutes from the instance using timestamp arithmetic. + * + * @param int $value The number of minutes to remove. + * @return static + * @see addMinutesWithTimestamp() + */ + public function subMinutesWithTimestamp(int $value): static + { + return $this->addMinutesWithTimestamp(-$value); + } + + /** + * Add seconds to the instance using timestamp arithmetic. + * + * Unlike `addSeconds()` which uses wall clock time, this method + * adds actual elapsed time by manipulating the Unix timestamp. + * This is important when working across DST transitions where + * wall clock time and elapsed time differ. + * + * @param int $value The number of seconds to add. + * @return static + */ + public function addSecondsWithTimestamp(int $value): static + { + return $this->setTimestamp($this->getTimestamp() + $value); + } + + /** + * Remove seconds from the instance using timestamp arithmetic. + * + * @param int $value The number of seconds to remove. + * @return static + * @see addSecondsWithTimestamp() + */ + public function subSecondsWithTimestamp(int $value): static + { + return $this->addSecondsWithTimestamp(-$value); + } + /** * Sets the time to 00:00:00 * diff --git a/tests/TestCase/DateTime/TimestampAddTest.php b/tests/TestCase/DateTime/TimestampAddTest.php new file mode 100644 index 00000000..edbf1c89 --- /dev/null +++ b/tests/TestCase/DateTime/TimestampAddTest.php @@ -0,0 +1,184 @@ + + * @link https://cakephp.org CakePHP(tm) Project + * @license https://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Cake\Chronos\Test\TestCase\DateTime; + +use Cake\Chronos\Chronos; +use Cake\Chronos\Test\TestCase\TestCase; + +class TimestampAddTest extends TestCase +{ + public function testAddSecondsWithTimestamp(): void + { + $time = Chronos::parse('2024-01-15 12:00:00', 'UTC'); + $result = $time->addSecondsWithTimestamp(30); + $this->assertSame('2024-01-15 12:00:30', $result->format('Y-m-d H:i:s')); + } + + public function testAddSecondsWithTimestampNegative(): void + { + $time = Chronos::parse('2024-01-15 12:00:30', 'UTC'); + $result = $time->addSecondsWithTimestamp(-30); + $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); + } + + public function testSubSecondsWithTimestamp(): void + { + $time = Chronos::parse('2024-01-15 12:00:30', 'UTC'); + $result = $time->subSecondsWithTimestamp(30); + $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); + } + + public function testAddMinutesWithTimestamp(): void + { + $time = Chronos::parse('2024-01-15 12:00:00', 'UTC'); + $result = $time->addMinutesWithTimestamp(30); + $this->assertSame('2024-01-15 12:30:00', $result->format('Y-m-d H:i:s')); + } + + public function testAddMinutesWithTimestampNegative(): void + { + $time = Chronos::parse('2024-01-15 12:30:00', 'UTC'); + $result = $time->addMinutesWithTimestamp(-30); + $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); + } + + public function testSubMinutesWithTimestamp(): void + { + $time = Chronos::parse('2024-01-15 12:30:00', 'UTC'); + $result = $time->subMinutesWithTimestamp(30); + $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); + } + + public function testAddHoursWithTimestamp(): void + { + $time = Chronos::parse('2024-01-15 12:00:00', 'UTC'); + $result = $time->addHoursWithTimestamp(2); + $this->assertSame('2024-01-15 14:00:00', $result->format('Y-m-d H:i:s')); + } + + public function testAddHoursWithTimestampNegative(): void + { + $time = Chronos::parse('2024-01-15 14:00:00', 'UTC'); + $result = $time->addHoursWithTimestamp(-2); + $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); + } + + public function testSubHoursWithTimestamp(): void + { + $time = Chronos::parse('2024-01-15 14:00:00', 'UTC'); + $result = $time->subHoursWithTimestamp(2); + $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); + } + + /** + * Test DST transition when clocks go BACK (fall back). + * Australia/Melbourne changes out of daylight savings on 5th April 2026 + * at 3:00 AM AEDT (+11) -> 2:00 AM AEST (+10) + */ + public function testAddMinutesWithTimestampAcrossDstFallBack(): void + { + $time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne'); + + $this->assertSame('2026-04-05T09:00:00+10:00', $time->toIso8601String()); + $this->assertSame('2026-04-05T00:00:00+11:00', $time->startOfDay()->toIso8601String()); + + $diff = $time->diffInMinutes($time->startOfDay()); + $this->assertSame(600, $diff); + + // Using timestamp arithmetic should correctly account for DST + $result = $time->startOfDay()->addMinutesWithTimestamp(600); + $this->assertSame('2026-04-05T09:00:00+10:00', $result->toIso8601String()); + } + + /** + * Test DST transition when clocks go FORWARD (spring forward). + * America/New_York springs forward on 2nd Sunday of March 2025 + * at 2:00 AM EST (-05) -> 3:00 AM EDT (-04) + */ + public function testAddMinutesWithTimestampAcrossDstSpringForward(): void + { + // March 9, 2025 is the 2nd Sunday of March (DST starts) + $beforeDst = Chronos::parse('2025-03-09 01:00:00', 'America/New_York'); + $this->assertSame('-05:00', $beforeDst->format('P')); + + // Add 2 hours (120 minutes) using timestamp arithmetic + // Wall clock would show 3:00 AM (skipping 2:00-3:00) + $result = $beforeDst->addMinutesWithTimestamp(120); + + // Should be 04:00 AM EDT (not 03:00 AM) + $this->assertSame('2025-03-09T04:00:00-04:00', $result->toIso8601String()); + } + + /** + * Test that addMinutes and addMinutesWithTimestamp differ during DST + */ + public function testAddMinutesVsAddMinutesWithTimestampDuringDst(): void + { + // Australia/Melbourne DST ends April 5, 2026 at 3am + $startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); + + // Wall clock addition (regular addMinutes) + $wallClock = $startOfDay->addMinutes(600); + + // Timestamp addition + $elapsed = $startOfDay->addMinutesWithTimestamp(600); + + // These should differ by 1 hour due to DST transition + $this->assertSame('2026-04-05T10:00:00+10:00', $wallClock->toIso8601String()); + $this->assertSame('2026-04-05T09:00:00+10:00', $elapsed->toIso8601String()); + } + + /** + * Test addHoursWithTimestamp across DST + */ + public function testAddHoursWithTimestampAcrossDst(): void + { + $startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); + + $result = $startOfDay->addHoursWithTimestamp(10); + + // 10 actual hours from midnight should be 09:00 (since we gain an hour at 3am) + $this->assertSame('2026-04-05T09:00:00+10:00', $result->toIso8601String()); + } + + /** + * Test addSecondsWithTimestamp across DST + */ + public function testAddSecondsWithTimestampAcrossDst(): void + { + $startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); + + // 10 hours in seconds = 36000 + $result = $startOfDay->addSecondsWithTimestamp(36000); + + $this->assertSame('2026-04-05T09:00:00+10:00', $result->toIso8601String()); + } + + /** + * Test that diffInMinutes and addMinutesWithTimestamp are inverses + */ + public function testDiffInMinutesIsInverseOfAddMinutesWithTimestamp(): void + { + $time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne'); + $startOfDay = $time->startOfDay(); + + $diff = $time->diffInMinutes($startOfDay); + + $reconstructed = $startOfDay->addMinutesWithTimestamp($diff); + + $this->assertSame($time->toIso8601String(), $reconstructed->toIso8601String()); + } +} From b91f885e26a9a9d24a95c77455eb4c5a75fc20be Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 3 Apr 2026 07:51:27 +0200 Subject: [PATCH 2/2] Rename methods to addElapsed*/subElapsed* based on feedback Renamed from *WithTimestamp to *Elapsed for clearer semantics: - addElapsedHours() / subElapsedHours() - addElapsedMinutes() / subElapsedMinutes() - addElapsedSeconds() / subElapsedSeconds() Also fixed "Daylight Saving Time" spelling (was "Savings"). --- docs/en/modifying.md | 28 +++---- src/Chronos.php | 36 ++++----- ...tampAddTest.php => ElapsedTimeAddTest.php} | 78 +++++++++---------- 3 files changed, 71 insertions(+), 71 deletions(-) rename tests/TestCase/DateTime/{TimestampAddTest.php => ElapsedTimeAddTest.php} (66%) diff --git a/docs/en/modifying.md b/docs/en/modifying.md index cb61884a..5c09976f 100644 --- a/docs/en/modifying.md +++ b/docs/en/modifying.md @@ -44,9 +44,9 @@ Available add/sub methods: For DST-safe operations that add actual elapsed time (see [DST Considerations](#dst-considerations)): -- `addHoursWithTimestamp()` / `subHoursWithTimestamp()` -- `addMinutesWithTimestamp()` / `subMinutesWithTimestamp()` -- `addSecondsWithTimestamp()` / `subSecondsWithTimestamp()` +- `addElapsedHours()` / `subElapsedHours()` +- `addElapsedMinutes()` / `subElapsedMinutes()` +- `addElapsedSeconds()` / `subElapsedSeconds()` ### Month Overflow Handling @@ -173,20 +173,20 @@ information and you need to assign the correct timezone. ## DST Considerations -When modifying dates/times across DST (Daylight Savings Time) transitions, +When modifying dates/times across DST (Daylight Saving Time) transitions, your operations may gain/lose an additional hour resulting in values that don't add up. Methods like `addHours()`, `addMinutes()`, and `addSeconds()` add "wall clock" time, which can produce unexpected results during DST transitions. -### Timestamp-Based Methods +### Elapsed Time Methods For operations that need to add actual elapsed time (not wall clock time), -use the timestamp-based variants: +use the elapsed time variants: -- `addHoursWithTimestamp()` / `subHoursWithTimestamp()` -- `addMinutesWithTimestamp()` / `subMinutesWithTimestamp()` -- `addSecondsWithTimestamp()` / `subSecondsWithTimestamp()` +- `addElapsedHours()` / `subElapsedHours()` +- `addElapsedMinutes()` / `subElapsedMinutes()` +- `addElapsedSeconds()` / `subElapsedSeconds()` These methods manipulate the Unix timestamp directly, ensuring that adding 600 minutes always means exactly 36000 seconds of elapsed time: @@ -200,13 +200,13 @@ $startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); $wallClock = $startOfDay->addMinutes(600); // Result: 2026-04-05T10:00:00+10:00 -// Timestamp addition - adds 10 hours of elapsed time -$elapsed = $startOfDay->addMinutesWithTimestamp(600); +// Elapsed time addition - adds 10 hours of elapsed time +$elapsed = $startOfDay->addElapsedMinutes(600); // Result: 2026-04-05T09:00:00+10:00 ``` -The timestamp-based methods ensure that `diffInMinutes()` and -`addMinutesWithTimestamp()` are true inverses of each other: +The elapsed time methods ensure that `diffInMinutes()` and +`addElapsedMinutes()` are true inverses of each other: ```php $time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne'); @@ -215,7 +215,7 @@ $startOfDay = $time->startOfDay(); $diff = $time->diffInMinutes($startOfDay); // 600 // Reconstructing the original time works correctly -$reconstructed = $startOfDay->addMinutesWithTimestamp($diff); +$reconstructed = $startOfDay->addElapsedMinutes($diff); // $reconstructed equals $time ``` diff --git a/src/Chronos.php b/src/Chronos.php index 98ec6712..6bed4da0 100644 --- a/src/Chronos.php +++ b/src/Chronos.php @@ -1471,7 +1471,7 @@ public function subSeconds(int $value): static } /** - * Add hours to the instance using timestamp arithmetic. + * Add hours to the instance using elapsed time. * * Unlike `addHours()` which uses wall clock time, this method * adds actual elapsed time by manipulating the Unix timestamp. @@ -1481,25 +1481,25 @@ public function subSeconds(int $value): static * @param int $value The number of hours to add. * @return static */ - public function addHoursWithTimestamp(int $value): static + public function addElapsedHours(int $value): static { return $this->setTimestamp($this->getTimestamp() + ($value * 3600)); } /** - * Remove hours from the instance using timestamp arithmetic. + * Remove hours from the instance using elapsed time. * * @param int $value The number of hours to remove. * @return static - * @see addHoursWithTimestamp() + * @see addElapsedHours() */ - public function subHoursWithTimestamp(int $value): static + public function subElapsedHours(int $value): static { - return $this->addHoursWithTimestamp(-$value); + return $this->addElapsedHours(-$value); } /** - * Add minutes to the instance using timestamp arithmetic. + * Add minutes to the instance using elapsed time. * * Unlike `addMinutes()` which uses wall clock time, this method * adds actual elapsed time by manipulating the Unix timestamp. @@ -1509,25 +1509,25 @@ public function subHoursWithTimestamp(int $value): static * @param int $value The number of minutes to add. * @return static */ - public function addMinutesWithTimestamp(int $value): static + public function addElapsedMinutes(int $value): static { return $this->setTimestamp($this->getTimestamp() + ($value * 60)); } /** - * Remove minutes from the instance using timestamp arithmetic. + * Remove minutes from the instance using elapsed time. * * @param int $value The number of minutes to remove. * @return static - * @see addMinutesWithTimestamp() + * @see addElapsedMinutes() */ - public function subMinutesWithTimestamp(int $value): static + public function subElapsedMinutes(int $value): static { - return $this->addMinutesWithTimestamp(-$value); + return $this->addElapsedMinutes(-$value); } /** - * Add seconds to the instance using timestamp arithmetic. + * Add seconds to the instance using elapsed time. * * Unlike `addSeconds()` which uses wall clock time, this method * adds actual elapsed time by manipulating the Unix timestamp. @@ -1537,21 +1537,21 @@ public function subMinutesWithTimestamp(int $value): static * @param int $value The number of seconds to add. * @return static */ - public function addSecondsWithTimestamp(int $value): static + public function addElapsedSeconds(int $value): static { return $this->setTimestamp($this->getTimestamp() + $value); } /** - * Remove seconds from the instance using timestamp arithmetic. + * Remove seconds from the instance using elapsed time. * * @param int $value The number of seconds to remove. * @return static - * @see addSecondsWithTimestamp() + * @see addElapsedSeconds() */ - public function subSecondsWithTimestamp(int $value): static + public function subElapsedSeconds(int $value): static { - return $this->addSecondsWithTimestamp(-$value); + return $this->addElapsedSeconds(-$value); } /** diff --git a/tests/TestCase/DateTime/TimestampAddTest.php b/tests/TestCase/DateTime/ElapsedTimeAddTest.php similarity index 66% rename from tests/TestCase/DateTime/TimestampAddTest.php rename to tests/TestCase/DateTime/ElapsedTimeAddTest.php index edbf1c89..627eb5f9 100644 --- a/tests/TestCase/DateTime/TimestampAddTest.php +++ b/tests/TestCase/DateTime/ElapsedTimeAddTest.php @@ -18,77 +18,77 @@ use Cake\Chronos\Chronos; use Cake\Chronos\Test\TestCase\TestCase; -class TimestampAddTest extends TestCase +class ElapsedTimeAddTest extends TestCase { - public function testAddSecondsWithTimestamp(): void + public function testAddElapsedSeconds(): void { $time = Chronos::parse('2024-01-15 12:00:00', 'UTC'); - $result = $time->addSecondsWithTimestamp(30); + $result = $time->addElapsedSeconds(30); $this->assertSame('2024-01-15 12:00:30', $result->format('Y-m-d H:i:s')); } - public function testAddSecondsWithTimestampNegative(): void + public function testAddElapsedSecondsNegative(): void { $time = Chronos::parse('2024-01-15 12:00:30', 'UTC'); - $result = $time->addSecondsWithTimestamp(-30); + $result = $time->addElapsedSeconds(-30); $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); } - public function testSubSecondsWithTimestamp(): void + public function testSubElapsedSeconds(): void { $time = Chronos::parse('2024-01-15 12:00:30', 'UTC'); - $result = $time->subSecondsWithTimestamp(30); + $result = $time->subElapsedSeconds(30); $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); } - public function testAddMinutesWithTimestamp(): void + public function testAddElapsedMinutes(): void { $time = Chronos::parse('2024-01-15 12:00:00', 'UTC'); - $result = $time->addMinutesWithTimestamp(30); + $result = $time->addElapsedMinutes(30); $this->assertSame('2024-01-15 12:30:00', $result->format('Y-m-d H:i:s')); } - public function testAddMinutesWithTimestampNegative(): void + public function testAddElapsedMinutesNegative(): void { $time = Chronos::parse('2024-01-15 12:30:00', 'UTC'); - $result = $time->addMinutesWithTimestamp(-30); + $result = $time->addElapsedMinutes(-30); $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); } - public function testSubMinutesWithTimestamp(): void + public function testSubElapsedMinutes(): void { $time = Chronos::parse('2024-01-15 12:30:00', 'UTC'); - $result = $time->subMinutesWithTimestamp(30); + $result = $time->subElapsedMinutes(30); $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); } - public function testAddHoursWithTimestamp(): void + public function testAddElapsedHours(): void { $time = Chronos::parse('2024-01-15 12:00:00', 'UTC'); - $result = $time->addHoursWithTimestamp(2); + $result = $time->addElapsedHours(2); $this->assertSame('2024-01-15 14:00:00', $result->format('Y-m-d H:i:s')); } - public function testAddHoursWithTimestampNegative(): void + public function testAddElapsedHoursNegative(): void { $time = Chronos::parse('2024-01-15 14:00:00', 'UTC'); - $result = $time->addHoursWithTimestamp(-2); + $result = $time->addElapsedHours(-2); $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); } - public function testSubHoursWithTimestamp(): void + public function testSubElapsedHours(): void { $time = Chronos::parse('2024-01-15 14:00:00', 'UTC'); - $result = $time->subHoursWithTimestamp(2); + $result = $time->subElapsedHours(2); $this->assertSame('2024-01-15 12:00:00', $result->format('Y-m-d H:i:s')); } /** * Test DST transition when clocks go BACK (fall back). - * Australia/Melbourne changes out of daylight savings on 5th April 2026 + * Australia/Melbourne changes out of daylight saving on 5th April 2026 * at 3:00 AM AEDT (+11) -> 2:00 AM AEST (+10) */ - public function testAddMinutesWithTimestampAcrossDstFallBack(): void + public function testAddElapsedMinutesAcrossDstFallBack(): void { $time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne'); @@ -98,8 +98,8 @@ public function testAddMinutesWithTimestampAcrossDstFallBack(): void $diff = $time->diffInMinutes($time->startOfDay()); $this->assertSame(600, $diff); - // Using timestamp arithmetic should correctly account for DST - $result = $time->startOfDay()->addMinutesWithTimestamp(600); + // Using elapsed time should correctly account for DST + $result = $time->startOfDay()->addElapsedMinutes(600); $this->assertSame('2026-04-05T09:00:00+10:00', $result->toIso8601String()); } @@ -108,24 +108,24 @@ public function testAddMinutesWithTimestampAcrossDstFallBack(): void * America/New_York springs forward on 2nd Sunday of March 2025 * at 2:00 AM EST (-05) -> 3:00 AM EDT (-04) */ - public function testAddMinutesWithTimestampAcrossDstSpringForward(): void + public function testAddElapsedMinutesAcrossDstSpringForward(): void { // March 9, 2025 is the 2nd Sunday of March (DST starts) $beforeDst = Chronos::parse('2025-03-09 01:00:00', 'America/New_York'); $this->assertSame('-05:00', $beforeDst->format('P')); - // Add 2 hours (120 minutes) using timestamp arithmetic + // Add 2 hours (120 minutes) using elapsed time // Wall clock would show 3:00 AM (skipping 2:00-3:00) - $result = $beforeDst->addMinutesWithTimestamp(120); + $result = $beforeDst->addElapsedMinutes(120); // Should be 04:00 AM EDT (not 03:00 AM) $this->assertSame('2025-03-09T04:00:00-04:00', $result->toIso8601String()); } /** - * Test that addMinutes and addMinutesWithTimestamp differ during DST + * Test that addMinutes and addElapsedMinutes differ during DST */ - public function testAddMinutesVsAddMinutesWithTimestampDuringDst(): void + public function testAddMinutesVsAddElapsedMinutesDuringDst(): void { // Australia/Melbourne DST ends April 5, 2026 at 3am $startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); @@ -133,8 +133,8 @@ public function testAddMinutesVsAddMinutesWithTimestampDuringDst(): void // Wall clock addition (regular addMinutes) $wallClock = $startOfDay->addMinutes(600); - // Timestamp addition - $elapsed = $startOfDay->addMinutesWithTimestamp(600); + // Elapsed time addition + $elapsed = $startOfDay->addElapsedMinutes(600); // These should differ by 1 hour due to DST transition $this->assertSame('2026-04-05T10:00:00+10:00', $wallClock->toIso8601String()); @@ -142,42 +142,42 @@ public function testAddMinutesVsAddMinutesWithTimestampDuringDst(): void } /** - * Test addHoursWithTimestamp across DST + * Test addElapsedHours across DST */ - public function testAddHoursWithTimestampAcrossDst(): void + public function testAddElapsedHoursAcrossDst(): void { $startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); - $result = $startOfDay->addHoursWithTimestamp(10); + $result = $startOfDay->addElapsedHours(10); // 10 actual hours from midnight should be 09:00 (since we gain an hour at 3am) $this->assertSame('2026-04-05T09:00:00+10:00', $result->toIso8601String()); } /** - * Test addSecondsWithTimestamp across DST + * Test addElapsedSeconds across DST */ - public function testAddSecondsWithTimestampAcrossDst(): void + public function testAddElapsedSecondsAcrossDst(): void { $startOfDay = Chronos::parse('2026-04-05 00:00:00', 'Australia/Melbourne'); // 10 hours in seconds = 36000 - $result = $startOfDay->addSecondsWithTimestamp(36000); + $result = $startOfDay->addElapsedSeconds(36000); $this->assertSame('2026-04-05T09:00:00+10:00', $result->toIso8601String()); } /** - * Test that diffInMinutes and addMinutesWithTimestamp are inverses + * Test that diffInMinutes and addElapsedMinutes are inverses */ - public function testDiffInMinutesIsInverseOfAddMinutesWithTimestamp(): void + public function testDiffInMinutesIsInverseOfAddElapsedMinutes(): void { $time = Chronos::parse('2026-04-05 09:00:00', 'Australia/Melbourne'); $startOfDay = $time->startOfDay(); $diff = $time->diffInMinutes($startOfDay); - $reconstructed = $startOfDay->addMinutesWithTimestamp($diff); + $reconstructed = $startOfDay->addElapsedMinutes($diff); $this->assertSame($time->toIso8601String(), $reconstructed->toIso8601String()); }