Skip to content

[OP-19969] Round instead of truncate when formatting a duration from hours - #24776

Open
jtauschl wants to merge 2 commits into
opf:devfrom
jtauschl:fix/duration-formatter-rounds-instead-of-truncates
Open

[OP-19969] Round instead of truncate when formatting a duration from hours#24776
jtauschl wants to merge 2 commits into
opf:devfrom
jtauschl:fix/duration-formatter-rounds-instead-of-truncates

Conversation

@jtauschl

@jtauschl jtauschl commented Aug 14, 2026

Copy link
Copy Markdown

Ticket

https://community.openproject.org/wp/OP-19969

Summary

format_duration_from_hours passed its computed seconds value straight into Duration.new(seconds: ...), whose hash-based constructor (in the ruby-duration gem) truncates via Float#to_i rather than rounding -- e.g. 5.501 hours (19803.6 seconds) silently serialized as "PT5H30M3S" instead of the correctly rounded "PT5H30M4S", losing up to just under a second of precision on any value with a fractional-second remainder. This affects every caller of this shared formatter (work packages, meetings, time entries), not just time entries.

Change

Round the seconds value explicitly before constructing the Duration.

Test plan

  • Updated the existing spec assertion that encoded the old truncating behavior as expected
  • Verified live against a real 17.7.1 instance, both server-side and through the full REST API: before the fix, format_duration_from_hours(5.501)"PT5H30M3S"; after the fix → "PT5H30M4S", and POST /api/v3/time_entries with hours: "PT5H30M3.6S" correctly returns "hours":"PT5H30M4S"

format_duration_from_hours passed its computed seconds value straight
into Duration.new(seconds: ...), whose hash-based constructor (in the
ruby-duration gem) truncates via Float#to_i rather than rounding --
e.g. 5.501 hours (19803.6 seconds) silently serialized as "PT5H30M3S"
instead of the correctly rounded "PT5H30M4S", losing up to just under
a second of precision on any value with a fractional-second remainder.
This affects every caller of this shared formatter (work packages,
meetings, time entries), not just time entries.

Round the seconds value explicitly before constructing the Duration.
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@jtauschl

Copy link
Copy Markdown
Author

recheck

@myabc myabc changed the title Round instead of truncate when formatting a duration from hours [OP-19969] Round instead of truncate when formatting a duration from hours Aug 15, 2026
@myabc
myabc requested a lite review from Copilot August 15, 2026 18:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a precision bug in the API duration formatter by rounding (instead of truncating) computed seconds when formatting durations from hours, aligning output with expected ISO8601 serialization across affected API representers.

Changes:

  • Round (hours * 3600) to whole seconds before constructing Duration in format_duration_from_hours.
  • Update the existing spec to assert the corrected rounding behavior (e.g., 5.501h -> PT5H30M4S).
  • Add inline documentation explaining why explicit rounding is required (ruby-duration truncation behavior).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
lib/api/v3/utilities/date_time_formatter.rb Rounds computed seconds for hour-based duration formatting before Duration.new(...).iso8601.
spec/lib/api/v3/utilities/date_time_formatter_spec.rb Updates expectation to validate rounded (not truncated) seconds behavior for hour-based formatting.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +88 to +92
# Duration.new truncates its :seconds argument via Float#to_i
# (ruby-duration gem) rather than rounding -- round explicitly
# here first so e.g. 3600.9 seconds (1h 0.9s) serializes as
# PT1H1S, not silently dropped to PT1H.
Duration.new(seconds: (hours * 3600).round).iso8601
…rom_hours

The sibling format_duration_from_hours fix in this same PR rounds seconds
before constructing Duration.new to avoid its truncation behavior;
format_duration_from_days had the identical bug and was left unfixed --
a fractional-day input's seconds still silently lost up to ~1s of
precision.
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