Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DateTime + Nanosecond loses information #50785

Open
jariji opened this issue Aug 3, 2023 · 12 comments · Fixed by #50816
Open

DateTime + Nanosecond loses information #50785

jariji opened this issue Aug 3, 2023 · 12 comments · Fixed by #50816
Labels
dates Dates, times, and the Dates stdlib module

Comments

@jariji
Copy link
Contributor

jariji commented Aug 3, 2023

This issue came up on Slack.

julia> dt = now()
2023-08-03T10:01:51.027

julia> dt == dt + Nanosecond(10^6 - 1)
true


julia> dump(now())
DateTime
  instant: Dates.UTInstant{Millisecond}
    periods: Millisecond
      value: Int64 63826748639606

DateTimes are integral so I would expect this to promote or error, not fail silently. Floating point can lose data, but I don't think integral types do this in Julia.

@Moelf
Copy link
Contributor

Moelf commented Aug 3, 2023

That's because DateTime only tracks things in millisecond and

julia> Dates.toms(Nanosecond(10^6))
1

julia> Dates.toms(Nanosecond(10^6 - 1))
0

you can see we always add things in ms:

(+)(x::DateTime, y::Period) = return DateTime(UTM(value(x) + toms(y)))
(-)(x::DateTime, y::Period) = return DateTime(UTM(value(x) - toms(y)))

@jariji
Copy link
Contributor Author

jariji commented Aug 3, 2023

toms is a private implementation detail. Integral types don't lose information like that. They promote or error.

julia> 1 + eps(0.0)
1.0

julia> 1 + eps(0.0) === 1
false

@Moelf
Copy link
Contributor

Moelf commented Aug 3, 2023

That's not a good analogy, DateTime can't use Nanosecond internally without using Big number, so it's actually pretty fundamental we go to ms

@brenhinkeller brenhinkeller added the dates Dates, times, and the Dates stdlib module label Aug 3, 2023
@jariji
Copy link
Contributor Author

jariji commented Aug 4, 2023

Converting to milliseconds may be essential if being quantized in milliseconds is part of the API of DateTime. But the current behavior of Dates.toms is not part of that API - that function is undocumented and its use is not part of the API of the DateTime constructor or Base.+.

If I'm converting into T a value that's not exactly representable in T, Julia typically promotes, as in the float case -- or if it can't, it gives an InexactError. Julia doesn't generally throw away otherwise accurate values.

DateTime can't use Nanosecond internally without using Big number

If DateTime doesn't support what the user is asking for, it should say so, and the user can install NanoDates.jl.

@barucden
Copy link
Contributor

barucden commented Aug 4, 2023

The documentation could be more explicit about DateTime's resolution. Right now it only says "DateTime wraps a UTInstant{Millisecond} [...]".

Also, when constructing from a Date and a Time, DateTime does throw an InexactError:

julia> DateTime( Date(now()), Time(Nanosecond(1)) )
ERROR: InexactError: DateTime(00:00:00.000000001)

This behavior is explicitly implemented as well as documented.

Maybe toms should throw an InexactError when its argument cannot be expressed as milliseconds (i.e., Nanosecond and Microsecond not divisible by 10^6 and 10^3, respectively)? Although, that would probably be breaking.

@PallHaraldsson
Copy link
Contributor

That's because DateTime only tracks things in millisecond

Even if, should:

julia> x === x + Nanosecond(10^6 - 1)
true

@Moelf
Copy link
Contributor

Moelf commented Aug 4, 2023

Yes? Unless you want to error on DateTime + Nanosecond.

If you're routinely adding less than 10^6 nanosecond, you're probably not using Julia DateTime built-in stuff anyway

@barucden
Copy link
Contributor

barucden commented Aug 7, 2023

A test was added in #30912 to check that toms truncates nanoseconds. A similar test was added in #32037 for microseconds. So changing the corresponding methods of toms to

toms(c::Nanosecond)  = divexact(value(c), 1000000)
toms(c::Microsecond) = divexact(value(c), 1000)

breaks even our test suite.

@jariji
Copy link
Contributor Author

jariji commented Aug 7, 2023

Again, toms is a private, internal function, not part of Julia's API, so its behavior under any particular version is not restricted. Nor does + need to call toms.


Here's an example of Julia correctly refusing to lose information:

julia> today()
2023-08-07

julia> today() + Hour(1)
ERROR: MethodError: no method matching +(::Date, ::Hour)

For now, deprecating without removing DateTime + Nanosecond would be a good solution, IMO.

@Seelengrab
Copy link
Contributor

Seelengrab commented Aug 22, 2023

This has not been fixed by #50816, I don't think. We're now inventing information out of thin air instead of losing it, by sometimes rounding up.

EDIT: and we're also still losing it, by rounding down. This means there are now worse precision problems than before.

@Seelengrab Seelengrab reopened this Aug 22, 2023
@timholy
Copy link
Member

timholy commented Aug 22, 2023

(I agree this hasn't been closed by #50816, that's the part the thumbs-up is for.)

@jariji
Copy link
Contributor Author

jariji commented Sep 30, 2023

Related: #35446

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dates Dates, times, and the Dates stdlib module
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants