fix: use floor() instead of trunc() for pre-epoch fractional seconds - #743
Conversation
For dates before 1970, timeIntervalSince1970 is negative and trunc() rounds toward zero, leaving a negative fraction that -date and -RFC3339String silently drop (both only apply positive milliseconds). Use floor() so the fraction stays in [0, 1).
|
@rootkiller6788 - Are you still working on this? It was opened as a draft, so I wasn't sure if you wanted anyone to look at it yet or not. |
|
Thanks for checking in, and sorry for the quiet. The fix is complete and CI is green. |
round() on the fraction can yield 1000 when the fraction is within half a millisecond of the next second, which would render a four-digit ".1000" fraction and break the 0-999 milliseconds invariant (both before and after the epoch). Clamp to 999 so the value always matches the calendar's floored whole-second components. Also rework the pre-epoch test to be table-driven and cover whole seconds, the rounding clamp boundary, and an offset.
|
Fixes a bug where GTLRDateTime silently dropped fractional seconds for dates before the Unix epoch. In setFromDate: the fractional part was computed as asTimeInterval - trunc(asTimeInterval). For pre-1970 dates Switching to floor() keeps the fraction in [0, 1), matching what the calendar components already do. Also fixed while I was in there round(worker * 1000) returns 1000 when the fraction is within half a millisecond of the next second, which would Test Reworked testFractionalSecondsBeforeEpoch to be table-driven, matching the style of testFractionalSeconds. It now
Each case also round-trips through -date to confirm the milliseconds survive. |
|
The rounding pushing to 1000 is interesting, why should we always pull that back to 999 instead of making the fraction 000 and adding 1 more second? i.e. - Haven't really thought about it, but the code change made me think of it. |
|
Expanding on my thoughts a little - since we round in general, seems like we should still round vs. force things dow of that one case. Looks like the old code had this same issue and we hadn't realized it, so it's been a bug from that pov for a while. |
round() can give 1000ms when the fraction is within half a millisecond of the next second. Clamping that back to 999ms was forcing the value down; roll the extra second into the date components instead so it rounds the same way every other fraction does.
-0.0001 is 1969-12-31T23:59:59.9999, which rounds up to the epoch rather than clamping to .999.
|
Thank you for the review, and good point. You're right that clamping to 999 forced the value down when it should have rounded up like every other fraction. I've updated |
thomasvl
left a comment
There was a problem hiding this comment.
Thank you for the report and fix!
Problem
GTLRDateTimedrops the fractional-second component for dates before the Unix epoch. InsetFromDate:, the fraction is computed asasTimeInterval - trunc(asTimeInterval). For a pre-1970 datetimeIntervalSince1970is negative andtrunc()rounds toward zero, leaving a negative fraction. Both-dateand-RFC3339Stringonly apply a positive milliseconds value, so the fraction is silently dropped and the result is off by up to ~1 second, violating the documented 0–999 milliseconds invariant.Change
Use
floor()instead oftrunc()so the fraction always stays in[0, 1).Test
Added
testFractionalSecondsBeforeEpoch, which round-trips pre-epoch dates with fractional seconds (-0.5and-1.25seconds since the epoch) through-RFC3339Stringand-dateand asserts the milliseconds are preserved.