From 74e4c958273a451e6bca1e9b9b3b532af3ca2469 Mon Sep 17 00:00:00 2001 From: Dorian Villet Date: Sat, 10 Oct 2026 10:24:51 +0200 Subject: [PATCH] Fix LocalDateTime::plusDuration() when the nano carry crosses midnight The day shift was computed from the seconds only, while the time part (LocalTime::plusDuration()) also applies the carry from the nanos. When that carry crossed midnight, the time wrapped but the date did not move: LocalDateTime::of(2000, 1, 1, 23, 59, 59, 500_000_000)->plusDuration(Duration::ofMillis(600)); // 2000-01-01T00:00:00.1 instead of 2000-01-02T00:00:00.1 LocalDateTime::of(2000, 1, 1, 0, 0, 0, 500_000_000)->minusDuration(Duration::ofMillis(400)); // 1999-12-31T00:00:00.1 instead of 2000-01-01T00:00:00.1 The second case comes from negative fractional durations, which are stored as negative seconds and positive nanos. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Fm9bvgKFasEGzpK48bQrxA --- src/LocalDateTime.php | 5 ++++- tests/LocalDateTimeTest.php | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/LocalDateTime.php b/src/LocalDateTime.php index a9ad6b99..927910b1 100644 --- a/src/LocalDateTime.php +++ b/src/LocalDateTime.php @@ -426,7 +426,10 @@ public function plusDuration(Duration $duration): LocalDateTime return $this; } - $days = Math::floorDiv($this->time->toSecondOfDay() + $duration->getSeconds(), LocalTime::SECONDS_PER_DAY); + $seconds = $this->time->toSecondOfDay() + $duration->getSeconds() + + intdiv($this->time->getNano() + $duration->getNanos(), LocalTime::NANOS_PER_SECOND); + + $days = Math::floorDiv($seconds, LocalTime::SECONDS_PER_DAY); return new LocalDateTime($this->date->plusDays($days), $this->time->plusDuration($duration)); } diff --git a/tests/LocalDateTimeTest.php b/tests/LocalDateTimeTest.php index 8eedf007..4c1c4716 100644 --- a/tests/LocalDateTimeTest.php +++ b/tests/LocalDateTimeTest.php @@ -701,6 +701,8 @@ public static function providerDuration(): array [7654321, 1999999999, 2001, 5, 2, 18, 17, 9, 123456788], [-654321, -987654321, 2001, 1, 26, 14, 19, 44, 135802468], [-7654321, 2013456789, 2000, 11, 6, 13, 53, 7, 136913578], + [71693, 900000000, 2001, 2, 4, 0, 0, 0, 23456789], + [-14707, 900000000, 2001, 2, 3, 0, 0, 0, 23456789], ]; }