Skip to content

Commit

Permalink
Merge pull request JuliaLang#37486 from sostock/zeroperiods
Browse files Browse the repository at this point in the history
Make zero-periods compare equal
  • Loading branch information
quinnj authored Sep 16, 2020
2 parents 8bdf569 + 921c7e7 commit bfa7261
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 9 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ Standard library changes

#### Dates
* `Quarter` period is defined ([#35519]).
* Zero-valued `FixedPeriod`s and `OtherPeriod`s now compare equal, e.g.,
`Year(0) == Day(0)`. The behavior of non-zero `Period`s is not changed. ([#37486])

#### Statistics

Expand Down
19 changes: 10 additions & 9 deletions stdlib/Dates/src/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -456,19 +456,20 @@ Base.convert(::Type{Quarter}, x::Month) = Quarter(divexact(value(x), 3))
Base.promote_rule(::Type{Quarter}, ::Type{Month}) = Month


# fixed is not comparable to other periods, as per discussion in issue #21378
(==)(x::FixedPeriod, y::OtherPeriod) = false
(==)(x::OtherPeriod, y::FixedPeriod) = false
# fixed is not comparable to other periods, except when both are zero (#37459)
(==)(x::FixedPeriod, y::OtherPeriod) = iszero(x) & iszero(y)
(==)(x::OtherPeriod, y::FixedPeriod) = y == x

const fixedperiod_seed = UInt === UInt64 ? 0x5b7fc751bba97516 : 0xeae0fdcb
const otherperiod_seed = UInt === UInt64 ? 0xe1837356ff2d2ac9 : 0x170d1b00
const zero_or_fixedperiod_seed = UInt === UInt64 ? 0x5b7fc751bba97516 : 0xeae0fdcb
const nonzero_otherperiod_seed = UInt === UInt64 ? 0xe1837356ff2d2ac9 : 0x170d1b00
otherperiod_seed(x::OtherPeriod) = iszero(value(x)) ? zero_or_fixedperiod_seed : nonzero_otherperiod_seed
# tons() will overflow for periods longer than ~300,000 years, implying a hash collision
# which is relatively harmless given how infrequent such periods should appear
Base.hash(x::FixedPeriod, h::UInt) = hash(tons(x), h + fixedperiod_seed)
Base.hash(x::FixedPeriod, h::UInt) = hash(tons(x), h + zero_or_fixedperiod_seed)
# Overflow can also happen here for really long periods (~8e17 years)
Base.hash(x::Year, h::UInt) = hash(12 * value(x), h + otherperiod_seed)
Base.hash(x::Quarter, h::UInt) = hash(3 * value(x), h + otherperiod_seed)
Base.hash(x::Month, h::UInt) = hash(value(x), h + otherperiod_seed)
Base.hash(x::Year, h::UInt) = hash(12 * value(x), h + otherperiod_seed(x))
Base.hash(x::Quarter, h::UInt) = hash(3 * value(x), h + otherperiod_seed(x))
Base.hash(x::Month, h::UInt) = hash(value(x), h + otherperiod_seed(x))

Base.isless(x::FixedPeriod, y::OtherPeriod) = throw(MethodError(isless, (x, y)))
Base.isless(x::OtherPeriod, y::FixedPeriod) = throw(MethodError(isless, (x, y)))
Expand Down
15 changes: 15 additions & 0 deletions stdlib/Dates/test/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,21 @@ end
@test hash(y) == hash(z)
end
end
@testset "Equality and hashing between FixedPeriod/OtherPeriod/CompoundPeriod (#37459)" begin
function test_hash_equality(x, y)
@test x == y
@test y == x
@test isequal(x, y)
@test isequal(y, x)
@test hash(x) == hash(y)
end
for FP = (Dates.Week, Dates.Day, Dates.Hour, Dates.Minute,
Dates.Second, Dates.Millisecond, Dates.Microsecond, Dates.Nanosecond)
for OP = (Dates.Year, Dates.Quarter, Dates.Month)
test_hash_equality(FP(0), OP(0))
end
end
end

@testset "#30832" begin
@test Dates.toms(Dates.Second(1) + Dates.Nanosecond(1)) == 1e3
Expand Down

0 comments on commit bfa7261

Please sign in to comment.