Skip to content

Fix diff/3 rounding for :day, :hour and :minute - #15650

Merged
josevalim merged 1 commit into
elixir-lang:mainfrom
nseaSeb:fix-calendar-diff-day-hour-minute-double-rounding
Jul 22, 2026
Merged

Fix diff/3 rounding for :day, :hour and :minute#15650
josevalim merged 1 commit into
elixir-lang:mainfrom
nseaSeb:fix-calendar-diff-day-hour-minute-double-rounding

Conversation

@nseaSeb

@nseaSeb nseaSeb commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

The :day, :hour and :minute clauses of Time.diff/3, DateTime.diff/3 and NaiveDateTime.diff/3 compute the difference in seconds first and then divide again, so the sub-second remainder is rounded twice. The intermediate conversion floors while the final div/2 truncates, so the result follows neither rule:

# elapsed is 23:59:59.999998, less than a full day
Time.diff(~T[00:00:00.000001], ~T[23:59:59.999999], :hour)
#=> -24, while the opposite direction returns 23

# elapsed is 86399.5s, and the docs promise incomplete days round to zero
NaiveDateTime.diff(~N[2000-01-01 00:00:00.7], ~N[2000-01-02 00:00:00.2], :day)
#=> -1

Compute the difference in microseconds and divide once instead. This keeps the documented truncation ("fractional results are not supported and are truncated", "it also rounds incomplete days to zero") in both directions. Positive results are unchanged, so the existing DateTime.diff/3 "almost 7 days" test now gains its negative counterpart.

Assisted-by: Claude Code:claude-opus-4-8

The `:day`, `:hour` and `:minute` clauses of `Time.diff/3`,
`DateTime.diff/3` and `NaiveDateTime.diff/3` compute the difference in
seconds first and then divide again, so the sub-second remainder is
rounded twice. The intermediate conversion floors while the final
`div/2` truncates, so the result follows neither rule:

    # elapsed is 23:59:59.999998, less than a full day
    Time.diff(~T[00:00:00.000001], ~T[23:59:59.999999], :hour)
    #=> -24, while the opposite direction returns 23

    # elapsed is 86399.5s, and the docs promise incomplete days round to zero
    NaiveDateTime.diff(~N[2000-01-01 00:00:00.7], ~N[2000-01-02 00:00:00.2], :day)
    #=> -1

Compute the difference in microseconds and divide once instead. This
keeps the documented truncation ("fractional results are not supported
and are truncated", "it also rounds incomplete days to zero") in both
directions. Positive results are unchanged, so the existing
`DateTime.diff/3` "almost 7 days" test now gains its negative
counterpart.

Assisted-by: Claude Code:claude-opus-4-8
@josevalim
josevalim merged commit 002e42d into elixir-lang:main Jul 22, 2026
15 checks passed
@josevalim

Copy link
Copy Markdown
Member

💚 💙 💜 💛 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants