Skip to content

Fix LocalDateTime::plusDuration() when the nano carry crosses midnight - #142

Merged
BenMorel merged 1 commit into
brick:mainfrom
gnutix:fix/local-date-time-plus-duration-nano-carry
Oct 10, 2026
Merged

BenMorel merged 1 commit into
brick:mainfrom
gnutix:fix/local-date-time-plus-duration-nano-carry

Conversation

@gnutix

@gnutix gnutix commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Item 3 of #137.

Problem

LocalDateTime::plusDuration() / minusDuration() landed on the wrong day when adding the nanos carried a second across midnight:

LocalDateTime::of(2000, 1, 1, 23, 59, 59, 500_000_000)->plusDuration(Duration::ofMillis(600));
// 2000-01-01T00:00:00.1, expected 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, expected 2000-01-01T00:00:00.1

The day shift was computed from the seconds only, while LocalTime::plusDuration() applies the nano carry. The second case comes from how a negative fractional duration is stored: -0.4 s is (-1 s, +600,000,000 ns). This only happens when both the date-time and the duration have a fractional second.

Change

The day shift now includes the carry, intdiv(nano + duration nanos, NANOS_PER_SECOND). It is never negative, because both nano values are in [0, 1e9).

There are two new cases in providerDuration, which both testPlusDuration and testMinusDuration use: one crossing midnight forwards and one backwards.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Fm9bvgKFasEGzpK48bQrxA

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fm9bvgKFasEGzpK48bQrxA
@codecov

codecov Bot commented Oct 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.24%. Comparing base (44f8c01) to head (74e4c95).

Additional details and impacted files
@@            Coverage Diff            @@
##               main     #142   +/-   ##
=========================================
  Coverage     99.24%   99.24%           
  Complexity     1141     1141           
=========================================
  Files            48       48           
  Lines          2501     2503    +2     
=========================================
+ Hits           2482     2484    +2     
  Misses           19       19           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@BenMorel
BenMorel merged commit c6340ff into brick:main Oct 10, 2026
10 checks passed
@BenMorel

Copy link
Copy Markdown
Member

Thank you, @gnutix!

BenMorel added a commit that referenced this pull request Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants