Skip to content

Commit

Permalink
Switch from truncation to rounding in sub-millisecond DateTime arithm…
Browse files Browse the repository at this point in the history
…etic (JuliaLang#50816)
  • Loading branch information
barucden authored Aug 22, 2023
1 parent e18ffec commit a4309ca
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 4 deletions.
4 changes: 2 additions & 2 deletions stdlib/Dates/src/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,8 @@ Base.isless(x::CompoundPeriod, y::Period) = x < CompoundPeriod(y)
Base.isless(x::CompoundPeriod, y::CompoundPeriod) = tons(x) < tons(y)
# truncating conversions to milliseconds, nanoseconds and days:
# overflow can happen for periods longer than ~300,000 years
toms(c::Nanosecond) = div(value(c), 1000000)
toms(c::Microsecond) = div(value(c), 1000)
toms(c::Nanosecond) = div(value(c), 1000000, RoundNearest)
toms(c::Microsecond) = div(value(c), 1000, RoundNearest)
toms(c::Millisecond) = value(c)
toms(c::Second) = 1000 * value(c)
toms(c::Minute) = 60000 * value(c)
Expand Down
24 changes: 22 additions & 2 deletions stdlib/Dates/src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,28 @@ abstract type AbstractDateTime <: TimeType end
"""
DateTime
`DateTime` wraps a `UTInstant{Millisecond}` and interprets it according to the proleptic
Gregorian calendar.
`DateTime` represents a point in time according to the proleptic Gregorian calendar.
The finest resolution of the time is millisecond (i.e., microseconds or
nanoseconds cannot be represented by this type). The type supports fixed-point
arithmetic, and thus is prone to underflowing (and overflowing). A notable
consequence is rounding when adding a `Microsecond` or a `Nanosecond`:
```jldoctest
julia> dt = DateTime(2023, 8, 19, 17, 45, 32, 900)
2023-08-19T17:45:32.900
julia> dt + Millisecond(1)
2023-08-19T17:45:32.901
julia> dt + Microsecond(1000) # 1000us == 1ms
2023-08-19T17:45:32.901
julia> dt + Microsecond(999) # 999us rounded to 1000us
2023-08-19T17:45:32.901
julia> dt + Microsecond(1499) # 1499 rounded to 1000us
2023-08-19T17:45:32.901
```
"""
struct DateTime <: AbstractDateTime
instant::UTInstant{Millisecond}
Expand Down
18 changes: 18 additions & 0 deletions stdlib/Dates/test/arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,24 @@ end
@test dt - Dates.Millisecond(1) == Dates.DateTime(1972, 6, 30, 23, 59, 58, 999)
@test dt + Dates.Millisecond(-1) == Dates.DateTime(1972, 6, 30, 23, 59, 58, 999)
end
@testset "DateTime-Microsecond arithmetic" begin
dt = Dates.DateTime(1999, 12, 27)
@test dt + Dates.Microsecond(1) == dt
@test dt + Dates.Microsecond(501) == Dates.DateTime(1999, 12, 27, 0, 0, 0, 1)
@test dt + Dates.Microsecond(1499) == Dates.DateTime(1999, 12, 27, 0, 0, 0, 1)
@test dt - Dates.Microsecond(1) == dt
@test dt - Dates.Microsecond(501) == Dates.DateTime(1999, 12, 26, 23, 59, 59, 999)
@test dt - Dates.Microsecond(1499) == Dates.DateTime(1999, 12, 26, 23, 59, 59, 999)
end
@testset "DateTime-Nanosecond arithmetic" begin
dt = Dates.DateTime(1999, 12, 27)
@test dt + Dates.Nanosecond(1) == dt
@test dt + Dates.Nanosecond(500_001) == Dates.DateTime(1999, 12, 27, 0, 0, 0, 1)
@test dt + Dates.Nanosecond(1_499_999) == Dates.DateTime(1999, 12, 27, 0, 0, 0, 1)
@test dt - Dates.Nanosecond(1) == dt
@test dt - Dates.Nanosecond(500_001) == Dates.DateTime(1999, 12, 26, 23, 59, 59, 999)
@test dt - Dates.Nanosecond(1_499_999) == Dates.DateTime(1999, 12, 26, 23, 59, 59, 999)
end
end
@testset "Date arithmetic" begin
@testset "Date-Year arithmetic" begin
Expand Down
8 changes: 8 additions & 0 deletions stdlib/Dates/test/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,14 @@ end
@test Dates.default(Dates.Nanosecond) == zero(Dates.Nanosecond)
end
@testset "Conversions" begin
@test Dates.toms(1499 * us) == 1
@test Dates.toms(501 * us) == 1
@test Dates.toms(us) == 0

@test Dates.toms(1_499_999 * ns) == 1
@test Dates.toms(500_001 * ns) == 1
@test Dates.toms(ns) == 0

@test Dates.toms(ms) == Dates.value(Dates.Millisecond(ms)) == 1
@test Dates.toms(s) == Dates.value(Dates.Millisecond(s)) == 1000
@test Dates.toms(mi) == Dates.value(Dates.Millisecond(mi)) == 60000
Expand Down
6 changes: 6 additions & 0 deletions stdlib/Dates/test/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ ms = Dates.Millisecond(1)
Dates.Hour(4), Dates.Second(10)) == Dates.DateTime(1, 2, 1, 4, 0, 10)
end

@testset "DateTime construction from Date and Time" begin
@test Dates.DateTime(Dates.Date(2023, 08, 07), Dates.Time(12)) == Dates.DateTime(2023, 08, 07, 12, 0, 0, 0)
@test_throws InexactError Dates.DateTime(Dates.Date(2023, 08, 07), Dates.Time(12, 0, 0, 0, 42))
@test_throws InexactError Dates.DateTime(Dates.Date(2023, 08, 07), Dates.Time(12, 0, 0, 0, 0, 42))
end

@testset "Date construction by parts" begin
test = Dates.Date(Dates.UTD(734869))
@test Dates.Date(2013) == test
Expand Down

0 comments on commit a4309ca

Please sign in to comment.