Cannot use a Dates.Hour and Dates.Month as keys in the same Dict


#1

It appears that I cannot have a Dict with Dates.Hour(1) and Dates.Month(1) as keys. I can have absolutely any other type/value as a key in this Dict, but I cannot put these two keys together in the same Dict.

It does not matter what order they are added in, the second one will throw an exception about the missing == method, but I don’t understand that because both of these are subtypes of Base.Period and we have ==(::Base.Dates.Period, ::Base.Dates.Period) defined, so why wouldn’t the compiler use that?

What’s even weirder, is I can use Dates.Hour(2) and Dates.Hour(0), but not Dates.Hour(1), and the same for Dates.Month

julia> DATEPART_DISPLAY = Dict{Dates.Period, AbstractString}()
Dict{Base.Dates.Period,AbstractString} with 0 entries

julia> DATEPART_DISPLAY[Dates.Month(1)] = "%b %Y"
"%b %Y"

julia> DATEPART_DISPLAY[Dates.Week(1)] = "%b %d %Y"
"%b %d %Y"

julia> DATEPART_DISPLAY[Dates.Hour(1)] = "%b %d %Y, %H:%M"
ERROR: MethodError: no method matching ==(::Base.Dates.Hour, ::Base.Dates.Month)
Closest candidates are:
  ==(::Union{Base.Dates.Day, Base.Dates.Hour, Base.Dates.Microsecond, Base.Dates.Millisecond, Base.Dates.Minute, Base.Dates.Nanosecond, Base.Dates.Second, Base.Dates.Week}, ::Union{Base.Dates.Month, Base.Dates.Year}) at dates/periods.jl:443
  ==(::Base.Dates.Period, ::Base.Dates.Period) at dates/periods.jl:63
  ==(::Any, ::Any) at operators.jl:52
  ...
Stacktrace:
 [1] ==(::Base.Dates.Hour, ::Base.Dates.Month) at ./dates/periods.jl:443
 [2] ht_keyindex2(::Dict{Base.Dates.Period,AbstractString}, ::Base.Dates.Hour) at ./dict.jl:366
 [3] setindex!(::Dict{Base.Dates.Period,AbstractString}, ::String, ::Base.Dates.Hour) at ./dict.jl:421

julia> DATEPART_DISPLAY[Dates.Minute(1)] = "%b %d %Y, %H:%M"
"%b %d %Y, %H:%M"

julia> DATEPART_DISPLAY[Dates.Second(1)] = "%b %d %Y, %H:%M:%S"
"%b %d %Y, %H:%M:%S"

julia> DATEPART_DISPLAY[Dates.Year(1)] = "%Y"
"%Y"
julia> versioninfo()
Julia Version 0.6.3-pre.2
Commit c011bb7 (2018-05-08 17:03 UTC)
DEBUG build
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, haswell)


#2

I tested this on 0.7 quick. I don’t get an error when assigning elements to the dict, however I do get

julia> Hour(1) == Month(1)
ERROR: MethodError: no method matching ==(::Hour, ::Month)
Closest candidates are:
  ==(::Union{Day, Hour, Microsecond, Millisecond, Minute, Nanosecond, Second, Week}, ::Union{Month, Year}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v0.7/Dates/src/periods.jl:446
  ==(::Period, ::Period) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v0.7/Dates/src/periods.jl:63
  ==(::Any, ::Any) at operators.jl:81

This seems like a bug to me, although I don’t understand why in the world it’s happening. It should match all 3 of the methods listed. Perhaps this is some sort of weird method ambiguity bug?


#3

yeah, for the == check I get the error even with Hour(2), but that works in the Dict


#4

Ah, I think I’ve found what’s going on. If you look here you will see that this method has been explicitly defined to throw a MethodError. Why? Well, it’s because Month and Year aren’t real time intervals.

That’s right, Month and Year don’t represent any fixed period of time. Yes, DateTime is one of the most macabre, horrific things ever created by the low animal minds of confused humans who couldn’t handle the fact that the planet that they live on and its satellite have rotational and revolutionary periods that are not in resonance with eachother. This isn’t a Julia bug, this is a bug in human society. I loathe that I ever have to use these godawful things.

Anyway, enough pontificating. This isn’t a bug, however, it would be nice if they chose a more informative error to throw here. It might even be worth filing an issue over. I’m not quite sure why the Dict is checking for equality, it should just be hashing the arguments. I guess it still does this for collision checking, but like I said, it doesn’t seem to happen in 0.7, for whatever reason.


#5

I can live with the comparison being indeterminate. I don’t need to compare these intervals. All I need is to put them in a Dict. Well, its Minute(60) for now.


#6

Please open an issue: this is definitely a bug.


#7

Is it though? It might be a bug in Dict, which is fixed on 0.7, but == fails on these types for a very good reason.

I just compared the Dict setindex! code in 0.6 and 0.7 and they look the same, so I’m still not sure what’s going on.


#8

Opened https://github.com/JuliaLang/julia/issues/27076


#9

You only trigger the bad comparison if you have a (truncated) hash collision. Hence, hard to reproduce on different julia versions.

julia> using Dates #skip this on 0.6

julia> DATEPART_DISPLAY = Dict{Dates.Period, AbstractString}()
Dict{Period,AbstractString} with 0 entries

julia> for i=1:1000 DATEPART_DISPLAY[Dates.Hour(rand(Int))] = "" end

julia> for i=1:1000 DATEPART_DISPLAY[Dates.Month(rand(Int))] = "" end
ERROR: MethodError: no method matching ==(::Month, ::Hour)

#10
julia> Dates.Hour <: Dates.Period
true

julia> Dates.Week <: Dates.Period
true

julia> Dates.Month <: Dates.Period
true

julia> Dates.Hour(1)  == Dates.Week(1)
false

julia> Dates.Hour(1)  == Dates.Month(1)
ERROR: MethodError: no method matching ==(::Base.Dates.Hour, ::Base.Dates.Month)
Closest candidates are:

If you look at the code in periods.jl its clear the intent is to be able to compare any subtypes of Period. I find this is broken on v0.6.2 and also on a few week old build of v0.7