diff --git a/src/TimeZones.jl b/src/TimeZones.jl index 754cecf5..61fdb4a7 100644 --- a/src/TimeZones.jl +++ b/src/TimeZones.jl @@ -47,6 +47,7 @@ function __init__() ) global ISOZonedDateTimeFormat = DateFormat("yyyy-mm-ddTHH:MM:SS.ssszzz") + init_IANA_NAMES!() end include("compat.jl") @@ -58,6 +59,7 @@ include("utcoffset.jl") include(joinpath("types", "timezone.jl")) include(joinpath("types", "fixedtimezone.jl")) include(joinpath("types", "variabletimezone.jl")) +include(joinpath("types", "ianatimezone.jl")) include(joinpath("types", "zoneddatetime.jl")) include("exceptions.jl") include(joinpath("tzdata", "TZData.jl")) diff --git a/src/arithmetic.jl b/src/arithmetic.jl index e1991b71..5ffa0897 100644 --- a/src/arithmetic.jl +++ b/src/arithmetic.jl @@ -25,14 +25,14 @@ function broadcasted(::typeof(+), r::StepRange{ZonedDateTime}, p::DatePeriod) # non-existent and ambiguous dates. tz = timezone(start) - if isa(tz, VariableTimeZone) + if isa(tz, AbstractVariableTimeZone) start = first_valid(DateTime(start) + p, tz, step) else start = start + p end tz = timezone(stop) - if isa(tz, VariableTimeZone) + if isa(tz, AbstractVariableTimeZone) stop = last_valid(DateTime(stop) + p, tz, step) else stop = stop + p diff --git a/src/conversions.jl b/src/conversions.jl index f60e0f1b..99f08f95 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -175,6 +175,8 @@ Converts a `ZonedDateTime` from its current `TimeZone` into the specified `TimeZ """ function astimezone end +astimezone(zdt::ZonedDateTime, tz::IANATimeZone) = _do_and_rewrap(astimezone, zdt, tz) + function astimezone(zdt::ZonedDateTime, tz::VariableTimeZone) i = searchsortedlast( tz.transitions, zdt.utc_datetime, diff --git a/src/discovery.jl b/src/discovery.jl index b4d1ada8..02d12442 100644 --- a/src/discovery.jl +++ b/src/discovery.jl @@ -154,15 +154,16 @@ next_transition_instant function next_transition_instant(zdt::ZonedDateTime) tz = zdt.timezone - tz isa VariableTimeZone || return nothing + tz isa AbstractVariableTimeZone || return nothing + transits = transitions(tz) # Determine the index of the transition which occurs after the UTC datetime specified index = searchsortedfirst( - tz.transitions, DateTime(zdt, UTC), + transits, DateTime(zdt, UTC), by=el -> isa(el, TimeZones.Transition) ? el.utc_datetime : el, ) - index <= length(tz.transitions) || return nothing + index <= length(transits) || return nothing # Use the UTC datetime of the transition and the offset information prior to the # transition to create a `ZonedDateTime` which cannot be constructed with the high-level @@ -170,8 +171,8 @@ function next_transition_instant(zdt::ZonedDateTime) # transition but visually appears to be before the transition. For example in a # transition where the clock changes from 01:59 → 03:00 we would return 02:00 where # the UTC datetime of 02:00 == 03:00. - utc_datetime = tz.transitions[index].utc_datetime - prev_zone = tz.transitions[index - 1].zone + utc_datetime = transits[index].utc_datetime + prev_zone = transits[index - 1].zone ZonedDateTime(utc_datetime, tz, prev_zone) end diff --git a/src/interpret.jl b/src/interpret.jl index 4597c819..8e2edc73 100644 --- a/src/interpret.jl +++ b/src/interpret.jl @@ -163,3 +163,7 @@ function last_valid(local_dt::DateTime, tz::VariableTimeZone) possible = interpret(local_dt, tz, Local) return isempty(possible) ? first(shift_gap(local_dt, tz)) : last(possible) end + + +first_valid(dt::DateTime, tz::IANATimeZone, args...) = _do_and_rewrap(first_valid, dt, tz, args...) +last_valid(dt::DateTime, tz::IANATimeZone, args...) = _do_and_rewrap(last_valid, dt, tz, args...) diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl new file mode 100644 index 00000000..b87f6ee3 --- /dev/null +++ b/src/types/ianatimezone.jl @@ -0,0 +1,171 @@ +const IANA_TABLE_SIZE = 1680 + +perfect_hash(tz::VariableTimeZone) = perfect_hash(tz.name) +function perfect_hash(str::AbstractString) + # This was generated via `gperf` and translated by hand. + # in case of collisions from new keys being added you must *not* regenerate it or + # change the assoc table, as that will change existing hashes and break any serialized + # data. Instead add special cases from the exact string via adding special cases. e.g. + #`name=="New/Timezone" && return 1681` (after adjusting `IANA_TABLE_SIZE`) + # The code below will (for all timezones in the 2021a release of tzdata) generate a + # value between 29 and 1680 inclusive. So any new keys that need to be added + # manually to resolve collisions can freely use anything outside that range. + + asso_values = ( + # this contains exactly 127 (ie. 0x7F) values. this is important for speed and for + # the inbounds checking after + 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, + 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, + 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, + 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, + 1681, 1681, 1681, 4, 1681, 1, 3, 3, 241, 48, + 1, 22, 10, 9, 61, 18, 74, 9, 30, 1681, + 1681, 1681, 1681, 1681, 1681, 4, 2, 18, 167, 48, + 547, 691, 532, 446, 574, 466, 211, 452, 8, 387, + 110, 475, 601, 262, 83, 124, 580, 536, 60, 1, + 719, 2, 1681, 1681, 1681, 291, 1, 1, 27, 424, + 13, 5, 43, 3, 355, 12, 168, 148, 90, 179, + 4, 1, 315, 3, 3, 3, 2, 37, 5, 65, + 22, 320, 245, 1, 1681, 1681, 1681, 1681, + ) + + units = codeunits(str) + len = length(units) + + for unit in units + # Check every unit is inbounds, if not then we know it is not in the table + # so return a value that is large to be in the table. + # It is faster to precheck if these are inbounds in advance and then `@inbounds` the + # next section. + unit + 0x01 < UInt(length(asso_values)) || return IANA_TABLE_SIZE + 1 + end + + hval = len + @inbounds begin + len >= 19 && (hval += asso_values[units[19]]) + len >= 12 && (hval += asso_values[units[12]]) + len >= 11 && (hval += asso_values[units[11]]) + len >= 9 && (hval += asso_values[units[9] + 1]) + len >= 8 && (hval += asso_values[units[8]]) + len >= 6 && (hval += asso_values[units[6] + 1]) + len >= 4 && (hval += asso_values[units[4]]) + len >= 2 && (hval += asso_values[units[2] + 1]) + len >= 1 && (hval += asso_values[units[1]]) + len > 0 && (hval += asso_values[units[end]]) # add the last + end + return hval +end + + +const IANA_TIMEZONES = Vector{VariableTimeZone}(undef, IANA_TABLE_SIZE) + +# TODO: maybe fill this during build(), probably by generating a julia file. +const IANA_NAMES = Vector{String}(undef, IANA_TABLE_SIZE) +function init_IANA_NAMES!() # this is run by __init__ (at least for now) + for name in timezone_names() + id = perfect_hash(name) + # Important: this line makes sure our hash is indeed perfect + isassigned(IANA_NAMES, id) && error("hash collision for $tz, at $id") + IANA_NAMES[id] = name + end + return IANA_NAMES +end + +function is_standard_iana(str::AbstractString) + id = perfect_hash(str) + return isassigned(IANA_NAMES, id) && IANA_NAMES[id] == str +end + +function get_iana_timezone!(str::AbstractString) + id = perfect_hash(str) + if isassigned(IANA_TIMEZONES, id) + IANA_TIMEZONES[id] + else + tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...) + tz, class = deserialize(tz_path) + if tz isa VariableTimeZone + IANA_TIMEZONES[id] = tz + return IANATimeZone(perfect_hash(str)) + else + # it is a FixedTimeZone, we are not going to use a IANATimeZone + return tz + end + end +end + +function get_iana_timezone!(id::UInt) + if isassigned(IANA_NAMES, id) + name = IANA_NAMES[id] + return get_iana_timezone!(name) + else + error( + "$id does not correspond to any known IANA timezone. " * + "Check you are using the right version of the IANA database.", + ) + end +end + + +""" + IANATimeZone(::AbstractString) <: AbstractVariableTimeZone + +A type for representing a standard variable IANA TimeZome from the tzdata. +Under-the-hood it stores only a unique integer identifier. +""" +struct IANATimeZone <: TimeZone + # id must be a prefect_hash of the corresponding timezone name + id::UInt +end + +function IANATimeZone(name::AbstractString) + return IANATimeZone(perfect_hash(name)) +end + +backing_timezone(itz::IANATimeZone) = get_iana_timezone!(itz.id)::VariableTimeZone + +Base.:(==)(a::IANATimeZone, b::IANATimeZone) = a.id == b.id +Base.:(==)(a::IANATimeZone, b::TimeZone) = backing_timezone(a) == b +Base.:(==)(b::TimeZone, a::IANATimeZone) = backing_timezone(a) == b + +# We can't use our perfect hash is as won't agree with the hash of the backing_timezone +Base.hash(a::IANATimeZone, seed::UInt) = hash(backing_timezone(a), seed) + +name(a::IANATimeZone) = name(backing_timezone(a)) +transitions(tz::IANATimeZone) = transitions(backing_timezone(tz)) + +# TODO: should i just make this check the fields of VariableTimeZone and just delegate all? +function Base.getproperty(tz::IANATimeZone, s::Symbol) + if s === :name + return name(tz) + elseif s == :transitions + return transitions(tz) + else + return getfield(tz, s) + end +end +if isdefined(Base, :hasproperty) + function Base.hasproperty(tz::IANATimeZone, s::Symbol) + return s === :name || s === :transitions || hasfield(IANATimeZone, s) + end +end + + + + +"""" + _do_and_rewrap(f, arg1, tz::IANATimeZone, args...; kwargs...) + +Run the function `f(arg1, backing_timezone(tz), args...; kwargs...)` +which must return a `ZonedDateTime`, with the backing timezone. +Replace the timezone field with `tz` (which should be equivalent). +""" +function _do_and_rewrap(f, arg1, tz::IANATimeZone, args...; kwargs...) + backed_tz = backing_timezone(tz) + backed_zdt::ZonedDateTime = f(arg1, backing_timezone(tz), args...; kwargs...) + # make it store tz rather than the equiv backing timezone, other fields the same + return ZonedDateTime(backed_zdt.utc_datetime, tz, backed_zdt.zone) +end + + +Base.show(io::IO, tz::IANATimeZone) = show(io, backing_timezone(tz)) +Base.print(io::IO, tz::IANATimeZone) = print(io, backing_timezone(tz)) diff --git a/src/types/timezone.jl b/src/types/timezone.jl index 7e8289b1..70ddb840 100644 --- a/src/types/timezone.jl +++ b/src/types/timezone.jl @@ -46,7 +46,10 @@ function TimeZone(str::AbstractString, mask::Class=Class(:DEFAULT)) tz, class = get!(TIME_ZONE_CACHE, str) do tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...) - if isfile(tz_path) + if mask == Class(:DEFAULT) && is_standard_iana(str) + # TODO: idk if this as sensible way to handle class and mask + get_iana_timezone!(str), Class(:DEFAULT) + elseif isfile(tz_path) open(deserialize, tz_path, "r") elseif occursin(FIXED_TIME_ZONE_REGEX, str) FixedTimeZone(str), Class(:FIXED) diff --git a/src/types/variabletimezone.jl b/src/types/variabletimezone.jl index 35b2b89b..f3947a0c 100644 --- a/src/types/variabletimezone.jl +++ b/src/types/variabletimezone.jl @@ -5,12 +5,27 @@ end Base.isless(a::Transition, b::Transition) = isless(a.utc_datetime, b.utc_datetime) +# TODO: define and document an actual API for this +# Seems to need: (list may be incomplete) +# - transitions(tz) +# - name(tz) +# - first_valid(tz) +# - last_valid(tz) +# - some constructors for ZonedDateTime +# +# As well as ones that are common to TimeZone +# - astimezone(zdt, tz) +# - show(io, tz) +# - print(io, tz) +# - `==` and `hash` +abstract type AbstractVariableTimeZone <: TimeZone end + """ VariableTimeZone A `TimeZone` with an offset that changes over time. """ -struct VariableTimeZone <: TimeZone +struct VariableTimeZone <: AbstractVariableTimeZone name::String transitions::Vector{Transition} cutoff::Union{DateTime,Nothing} @@ -20,6 +35,8 @@ struct VariableTimeZone <: TimeZone end end +transitions(tz::VariableTimeZone) = tz.transitions + name(tz::VariableTimeZone) = tz.name function rename(tz::VariableTimeZone, name::AbstractString) diff --git a/src/types/zoneddatetime.jl b/src/types/zoneddatetime.jl index 2c832e8d..8bc9b62f 100644 --- a/src/types/zoneddatetime.jl +++ b/src/types/zoneddatetime.jl @@ -104,6 +104,14 @@ function ZonedDateTime(dt::DateTime, tz::VariableTimeZone, is_dst::Bool) end end +function ZonedDateTime(dt::DateTime, tz::IANATimeZone, occ_or_dst::Integer) + return _do_and_rewrap(ZonedDateTime, dt, tz, occ_or_dst) +end + +function ZonedDateTime(dt::DateTime, tz::IANATimeZone; kwargs...) + return _do_and_rewrap(ZonedDateTime, dt, tz; kwargs...) +end + # Convenience constructors @doc """ ZonedDateTime(y, [m, d, h, mi, s, ms], tz, [amb]) -> DateTime @@ -113,7 +121,7 @@ Construct a `ZonedDateTime` type by parts. Arguments `y, m, ..., ms` must be con `TimeZone` then `amb` can be supplied to resolve ambiguity. """ ZonedDateTime -@optional function ZonedDateTime(y::Integer, m::Integer=1, d::Integer=1, h::Integer=0, mi::Integer=0, s::Integer=0, ms::Integer=0, tz::VariableTimeZone, amb::Union{Integer,Bool}) +@optional function ZonedDateTime(y::Integer, m::Integer=1, d::Integer=1, h::Integer=0, mi::Integer=0, s::Integer=0, ms::Integer=0, tz::AbstractVariableTimeZone, amb::Union{Integer,Bool}) ZonedDateTime(DateTime(y,m,d,h,mi,s,ms), tz, amb) end diff --git a/test/runtests.jl b/test/runtests.jl index b631e70f..5abedd14 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -56,6 +56,7 @@ include("helpers.jl") include(joinpath("types", "fixedtimezone.jl")) include(joinpath("types", "variabletimezone.jl")) include(joinpath("types", "zoneddatetime.jl")) + include(joinpath("types", "ianatimezone.jl")) include("exceptions.jl") include("interpret.jl") include("accessors.jl") diff --git a/test/types/ianatimezone.jl b/test/types/ianatimezone.jl new file mode 100644 index 00000000..4270f5db --- /dev/null +++ b/test/types/ianatimezone.jl @@ -0,0 +1,624 @@ +@btime @testset "IANATimeZone" begin + @testset "perfect_hash" begin + perfect_hash = TimeZones.perfect_hash + + @testset "non-matching" begin + # In general we can make no promised for what inputs that are from outside + # the set used to generated the hash will do. They have a decent chance of being + # out of bounds, but not certain. And it is even possible for them to colide + # But there are some exceptions that we do make sure are out of bounds: + + IANA_TABLE_SIZE = TimeZones.IANA_TABLE_SIZE + @testset "non-ANSI cases are out out bounds" begin + # all non-ANSI are garenteed to be out of bounds + @test perfect_hash("🌔/Tsiolkovsky") > IANA_TABLE_SIZE + @test perfect_hash("Asia/🗼") > IANA_TABLE_SIZE + end + + @testset "Empty string should be out of bounds" begin + # We don't actually require it to be zero, being > IANA_TABLE_SIZE would be + # fine also, but zero is what it is right now, and seems good + @test perfect_hash("") == 0 + end + end + + @testset "cases used for generating hash haven't changed" begin + # testcases generated by running original code generated by gperf + # It is a full exhaustive set of 2021a's tzdata names, since it + # is fast enough to run, it includes some FixedTimeZones + @test perfect_hash("Africa/Abidjan") == 673 + @test perfect_hash("Africa/Accra") == 80 + @test perfect_hash("Africa/Addis_Ababa") == 89 + @test perfect_hash("Africa/Algiers") == 263 + @test perfect_hash("Africa/Asmara") == 70 + @test perfect_hash("Africa/Asmera") == 74 + @test perfect_hash("Africa/Bamako") == 238 + @test perfect_hash("Africa/Bangui") == 140 + @test perfect_hash("Africa/Banjul") == 383 + @test perfect_hash("Africa/Bissau") == 270 + @test perfect_hash("Africa/Blantyre") == 253 + @test perfect_hash("Africa/Brazzaville") == 564 + @test perfect_hash("Africa/Bujumbura") == 286 + @test perfect_hash("Africa/Cairo") == 108 + @test perfect_hash("Africa/Casablanca") == 137 + @test perfect_hash("Africa/Ceuta") == 123 + @test perfect_hash("Africa/Conakry") == 862 + @test perfect_hash("Africa/Dakar") == 259 + @test perfect_hash("Africa/Dar_es_Salaam") == 736 + @test perfect_hash("Africa/Djibouti") == 416 + @test perfect_hash("Africa/Douala") == 633 + @test perfect_hash("Africa/El_Aaiun") == 297 + @test perfect_hash("Africa/Freetown") == 622 + @test perfect_hash("Africa/Gaborone") == 788 + @test perfect_hash("Africa/Harare") == 627 + @test perfect_hash("Africa/Johannesburg") == 965 + @test perfect_hash("Africa/Juba") == 638 + @test perfect_hash("Africa/Kampala") == 870 + @test perfect_hash("Africa/Khartoum") == 723 + @test perfect_hash("Africa/Kigali") == 796 + @test perfect_hash("Africa/Kinshasa") == 1054 + @test perfect_hash("Africa/Lagos") == 303 + @test perfect_hash("Africa/Libreville") == 455 + @test perfect_hash("Africa/Lome") == 593 + @test perfect_hash("Africa/Luanda") == 293 + @test perfect_hash("Africa/Lubumbashi") == 507 + @test perfect_hash("Africa/Lusaka") == 425 + @test perfect_hash("Africa/Malabo") == 567 + @test perfect_hash("Africa/Maputo") == 578 + @test perfect_hash("Africa/Maseru") == 583 + @test perfect_hash("Africa/Mbabane") == 969 + @test perfect_hash("Africa/Mogadishu") == 880 + @test perfect_hash("Africa/Monrovia") == 833 + @test perfect_hash("Africa/Nairobi") == 111 + @test perfect_hash("Africa/Ndjamena") == 255 + @test perfect_hash("Africa/Niamey") == 739 + @test perfect_hash("Africa/Nouakchott") == 537 + @test perfect_hash("Africa/Ouagadougou") == 497 + @test perfect_hash("Africa/Porto-Novo") == 492 + @test perfect_hash("Africa/Sao_Tome") == 729 + @test perfect_hash("Africa/Timbuktu") == 413 + @test perfect_hash("Africa/Tripoli") == 474 + @test perfect_hash("Africa/Tunis") == 164 + @test perfect_hash("Africa/Windhoek") == 1281 + @test perfect_hash("America/Adak") == 338 + @test perfect_hash("America/Anchorage") == 830 + @test perfect_hash("America/Anguilla") == 86 + @test perfect_hash("America/Antigua") == 59 + @test perfect_hash("America/Araguaina") == 51 + @test perfect_hash("America/Argentina/Buenos_Aires") == 72 + @test perfect_hash("America/Argentina/Catamarca") == 83 + @test perfect_hash("America/Argentina/ComodRivadavia") == 88 + @test perfect_hash("America/Argentina/Cordoba") == 81 + @test perfect_hash("America/Argentina/Jujuy") == 954 + @test perfect_hash("America/Argentina/La_Rioja") == 275 + @test perfect_hash("America/Argentina/Mendoza") == 515 + @test perfect_hash("America/Argentina/Rio_Gallegos") == 671 + @test perfect_hash("America/Argentina/Salta") == 323 + @test perfect_hash("America/Argentina/San_Juan") == 329 + @test perfect_hash("America/Argentina/San_Luis") == 328 + @test perfect_hash("America/Argentina/Tucuman") == 149 + @test perfect_hash("America/Argentina/Ushuaia") == 187 + @test perfect_hash("America/Aruba") == 107 + @test perfect_hash("America/Asuncion") == 90 + @test perfect_hash("America/Atikokan") == 209 + @test perfect_hash("America/Atka") == 191 + @test perfect_hash("America/Bahia") == 426 + @test perfect_hash("America/Bahia_Banderas") == 442 + @test perfect_hash("America/Barbados") == 94 + @test perfect_hash("America/Belem") == 332 + @test perfect_hash("America/Belize") == 166 + @test perfect_hash("America/Blanc-Sablon") == 75 + @test perfect_hash("America/Boa_Vista") == 355 + @test perfect_hash("America/Bogota") == 64 + @test perfect_hash("America/Boise") == 78 + @test perfect_hash("America/Buenos_Aires") == 82 + @test perfect_hash("America/Cambridge_Bay") == 743 + @test perfect_hash("America/Campo_Grande") == 726 + @test perfect_hash("America/Cancun") == 640 + @test perfect_hash("America/Caracas") == 216 + @test perfect_hash("America/Catamarca") == 215 + @test perfect_hash("America/Cayenne") == 539 + @test perfect_hash("America/Cayman") == 711 + @test perfect_hash("America/Chicago") == 646 + @test perfect_hash("America/Chihuahua") == 579 + @test perfect_hash("America/Coral_Harbour") == 223 + @test perfect_hash("America/Cordoba") == 226 + @test perfect_hash("America/Costa_Rica") == 218 + @test perfect_hash("America/Creston") == 221 + @test perfect_hash("America/Cuiaba") == 222 + @test perfect_hash("America/Curacao") == 214 + @test perfect_hash("America/Danmarkshavn") == 287 + @test perfect_hash("America/Dawson") == 161 + @test perfect_hash("America/Dawson_Creek") == 316 + @test perfect_hash("America/Denver") == 101 + @test perfect_hash("America/Detroit") == 97 + @test perfect_hash("America/Dominica") == 283 + @test perfect_hash("America/Edmonton") == 774 + @test perfect_hash("America/Eirunepe") == 635 + @test perfect_hash("America/El_Salvador") == 1152 + @test perfect_hash("America/Ensenada") == 599 + @test perfect_hash("America/Fort_Nelson") == 750 + @test perfect_hash("America/Fort_Wayne") == 746 + @test perfect_hash("America/Fortaleza") == 741 + @test perfect_hash("America/Glace_Bay") == 1321 + @test perfect_hash("America/Godthab") == 616 + @test perfect_hash("America/Goose_Bay") == 900 + @test perfect_hash("America/Grand_Turk") == 730 + @test perfect_hash("America/Grenada") == 584 + @test perfect_hash("America/Guadeloupe") == 596 + @test perfect_hash("America/Guatemala") == 580 + @test perfect_hash("America/Guayaquil") == 987 + @test perfect_hash("America/Guyana") == 895 + @test perfect_hash("America/Halifax") == 612 + @test perfect_hash("America/Havana") == 494 + @test perfect_hash("America/Hermosillo") == 674 + @test perfect_hash("America/Indiana/Indianapolis") == 670 + @test perfect_hash("America/Indiana/Knox") == 669 + @test perfect_hash("America/Indiana/Marengo") == 653 + @test perfect_hash("America/Indiana/Petersburg") == 657 + @test perfect_hash("America/Indiana/Tell_City") == 1061 + @test perfect_hash("America/Indiana/Vevay") == 972 + @test perfect_hash("America/Indiana/Vincennes") == 658 + @test perfect_hash("America/Indiana/Winamac") == 1077 + @test perfect_hash("America/Indianapolis") == 661 + @test perfect_hash("America/Inuvik") == 805 + @test perfect_hash("America/Iqaluit") == 709 + @test perfect_hash("America/Jamaica") == 689 + @test perfect_hash("America/Jujuy") == 1031 + @test perfect_hash("America/Juneau") == 553 + @test perfect_hash("America/Kentucky/Louisville") == 277 + @test perfect_hash("America/Kentucky/Monticello") == 273 + @test perfect_hash("America/Knox_IN") == 284 + @test perfect_hash("America/Kralendijk") == 495 + @test perfect_hash("America/La_Paz") == 1139 + @test perfect_hash("America/Lima") == 672 + @test perfect_hash("America/Los_Angeles") == 798 + @test perfect_hash("America/Louisville") == 551 + @test perfect_hash("America/Lower_Princes") == 997 + @test perfect_hash("America/Maceio") == 479 + @test perfect_hash("America/Managua") == 56 + @test perfect_hash("America/Manaus") == 57 + @test perfect_hash("America/Marigot") == 67 + @test perfect_hash("America/Martinique") == 63 + @test perfect_hash("America/Matamoros") == 58 + @test perfect_hash("America/Mazatlan") == 301 + @test perfect_hash("America/Mendoza") == 68 + @test perfect_hash("America/Menominee") == 62 + @test perfect_hash("America/Merida") == 65 + @test perfect_hash("America/Metlakatla") == 146 + @test perfect_hash("America/Mexico_City") == 728 + @test perfect_hash("America/Miquelon") == 95 + @test perfect_hash("America/Moncton") == 482 + @test perfect_hash("America/Monterrey") == 378 + @test perfect_hash("America/Montevideo") == 60 + @test perfect_hash("America/Montreal") == 147 + @test perfect_hash("America/Montserrat") == 61 + @test perfect_hash("America/Nassau") == 471 + @test perfect_hash("America/New_York") == 934 + @test perfect_hash("America/Nipigon") == 760 + @test perfect_hash("America/Nome") == 615 + @test perfect_hash("America/Noronha") == 434 + @test perfect_hash("America/North_Dakota/Beulah") == 803 + @test perfect_hash("America/North_Dakota/Center") == 451 + @test perfect_hash("America/North_Dakota/New_Salem") == 630 + @test perfect_hash("America/Nuuk") == 759 + @test perfect_hash("America/Ojinaga") == 169 + @test perfect_hash("America/Panama") == 522 + @test perfect_hash("America/Pangnirtung") == 534 + @test perfect_hash("America/Paramaribo") == 525 + @test perfect_hash("America/Phoenix") == 545 + @test perfect_hash("America/Port-au-Prince") == 546 + @test perfect_hash("America/Port_of_Spain") == 533 + @test perfect_hash("America/Porto_Acre") == 530 + @test perfect_hash("America/Porto_Velho") == 528 + @test perfect_hash("America/Puerto_Rico") == 531 + @test perfect_hash("America/Punta_Arenas") == 532 + @test perfect_hash("America/Rainy_River") == 330 + @test perfect_hash("America/Rankin_Inlet") == 468 + @test perfect_hash("America/Recife") == 744 + @test perfect_hash("America/Regina") == 319 + @test perfect_hash("America/Resolute") == 314 + @test perfect_hash("America/Rio_Branco") == 600 + @test perfect_hash("America/Rosario") == 309 + @test perfect_hash("America/Santa_Isabel") == 231 + @test perfect_hash("America/Santarem") == 311 + @test perfect_hash("America/Santiago") == 133 + @test perfect_hash("America/Santo_Domingo") == 142 + @test perfect_hash("America/Sao_Paulo") == 420 + @test perfect_hash("America/Scoresbysund") == 151 + @test perfect_hash("America/Shiprock") == 601 + @test perfect_hash("America/Sitka") == 274 + @test perfect_hash("America/St_Barthelemy") == 749 + @test perfect_hash("America/St_Johns") == 994 + @test perfect_hash("America/St_Kitts") == 886 + @test perfect_hash("America/St_Lucia") == 629 + @test perfect_hash("America/St_Thomas") == 504 + @test perfect_hash("America/St_Vincent") == 1001 + @test perfect_hash("America/Swift_Current") == 193 + @test perfect_hash("America/Tegucigalpa") == 212 + @test perfect_hash("America/Thule") == 296 + @test perfect_hash("America/Thunder_Bay") == 851 + @test perfect_hash("America/Tijuana") == 372 + @test perfect_hash("America/Toronto") == 171 + @test perfect_hash("America/Tortola") == 172 + @test perfect_hash("America/Vancouver") == 1011 + @test perfect_hash("America/Virgin") == 587 + @test perfect_hash("America/Whitehorse") == 124 + @test perfect_hash("America/Winnipeg") == 114 + @test perfect_hash("America/Yakutat") == 948 + @test perfect_hash("America/Yellowknife") == 955 + @test perfect_hash("Antarctica/Casey") == 401 + @test perfect_hash("Antarctica/Davis") == 233 + @test perfect_hash("Antarctica/DumontDUrville") == 368 + @test perfect_hash("Antarctica/Macquarie") == 536 + @test perfect_hash("Antarctica/Mawson") == 520 + @test perfect_hash("Antarctica/McMurdo") == 518 + @test perfect_hash("Antarctica/Palmer") == 177 + @test perfect_hash("Antarctica/Rothera") == 667 + @test perfect_hash("Antarctica/South_Pole") == 336 + @test perfect_hash("Antarctica/Syowa") == 326 + @test perfect_hash("Antarctica/Troll") == 236 + @test perfect_hash("Antarctica/Vostok") == 792 + @test perfect_hash("Arctic/Longyearbyen") == 898 + @test perfect_hash("Asia/Aden") == 28 + @test perfect_hash("Asia/Almaty") == 866 + @test perfect_hash("Asia/Amman") == 229 + @test perfect_hash("Asia/Anadyr") == 32 + @test perfect_hash("Asia/Aqtau") == 85 + @test perfect_hash("Asia/Aqtobe") == 347 + @test perfect_hash("Asia/Ashgabat") == 762 + @test perfect_hash("Asia/Ashkhabad") == 509 + @test perfect_hash("Asia/Atyrau") == 417 + @test perfect_hash("Asia/Baghdad") == 79 + @test perfect_hash("Asia/Bahrain") == 415 + @test perfect_hash("Asia/Baku") == 224 + @test perfect_hash("Asia/Bangkok") == 693 + @test perfect_hash("Asia/Barnaul") == 258 + @test perfect_hash("Asia/Beirut") == 55 + @test perfect_hash("Asia/Bishkek") == 353 + @test perfect_hash("Asia/Brunei") == 98 + @test perfect_hash("Asia/Calcutta") == 295 + @test perfect_hash("Asia/Chita") == 234 + @test perfect_hash("Asia/Choibalsan") == 453 + @test perfect_hash("Asia/Chongqing") == 208 + @test perfect_hash("Asia/Chungking") == 389 + @test perfect_hash("Asia/Colombo") == 620 + @test perfect_hash("Asia/Dacca") == 503 + @test perfect_hash("Asia/Damascus") == 738 + @test perfect_hash("Asia/Dhaka") == 157 + @test perfect_hash("Asia/Dili") == 334 + @test perfect_hash("Asia/Dubai") == 131 + @test perfect_hash("Asia/Dushanbe") == 119 + @test perfect_hash("Asia/Famagusta") == 959 + @test perfect_hash("Asia/Gaza") == 821 + @test perfect_hash("Asia/Harbin") == 899 + @test perfect_hash("Asia/Hebron") == 502 + @test perfect_hash("Asia/Ho_Chi_Minh") == 1585 + @test perfect_hash("Asia/Hong_Kong") == 1296 + @test perfect_hash("Asia/Hovd") == 485 + @test perfect_hash("Asia/Irkutsk") == 1045 + @test perfect_hash("Asia/Istanbul") == 777 + @test perfect_hash("Asia/Jakarta") == 664 + @test perfect_hash("Asia/Jayapura") == 874 + @test perfect_hash("Asia/Jerusalem") == 765 + @test perfect_hash("Asia/Kabul") == 350 + @test perfect_hash("Asia/Kamchatka") == 428 + @test perfect_hash("Asia/Karachi") == 639 + @test perfect_hash("Asia/Kashgar") == 252 + @test perfect_hash("Asia/Kathmandu") == 288 + @test perfect_hash("Asia/Katmandu") == 291 + @test perfect_hash("Asia/Khandyga") == 557 + @test perfect_hash("Asia/Kolkata") == 414 + @test perfect_hash("Asia/Krasnoyarsk") == 706 + @test perfect_hash("Asia/Kuala_Lumpur") == 920 + @test perfect_hash("Asia/Kuching") == 676 + @test perfect_hash("Asia/Kuwait") == 325 + @test perfect_hash("Asia/Macao") == 477 + @test perfect_hash("Asia/Macau") == 513 + @test perfect_hash("Asia/Magadan") == 66 + @test perfect_hash("Asia/Makassar") == 210 + @test perfect_hash("Asia/Manila") == 200 + @test perfect_hash("Asia/Muscat") == 46 + @test perfect_hash("Asia/Nicosia") == 1159 + @test perfect_hash("Asia/Novokuznetsk") == 1161 + @test perfect_hash("Asia/Novosibirsk") == 917 + @test perfect_hash("Asia/Omsk") == 367 + @test perfect_hash("Asia/Oral") == 396 + @test perfect_hash("Asia/Phnom_Penh") == 1572 + @test perfect_hash("Asia/Pontianak") == 690 + @test perfect_hash("Asia/Pyongyang") == 822 + @test perfect_hash("Asia/Qatar") == 650 + @test perfect_hash("Asia/Qostanay") == 986 + @test perfect_hash("Asia/Qyzylorda") == 1117 + @test perfect_hash("Asia/Rangoon") == 649 + @test perfect_hash("Asia/Riyadh") == 1337 + @test perfect_hash("Asia/Saigon") == 476 + @test perfect_hash("Asia/Sakhalin") == 369 + @test perfect_hash("Asia/Samarkand") == 472 + @test perfect_hash("Asia/Seoul") == 196 + @test perfect_hash("Asia/Shanghai") == 473 + @test perfect_hash("Asia/Singapore") == 784 + @test perfect_hash("Asia/Srednekolymsk") == 419 + @test perfect_hash("Asia/Taipei") == 181 + @test perfect_hash("Asia/Tashkent") == 170 + @test perfect_hash("Asia/Tbilisi") == 361 + @test perfect_hash("Asia/Tehran") == 508 + @test perfect_hash("Asia/Tel_Aviv") == 257 + @test perfect_hash("Asia/Thimbu") == 232 + @test perfect_hash("Asia/Thimphu") == 588 + @test perfect_hash("Asia/Tokyo") == 535 + @test perfect_hash("Asia/Tomsk") == 470 + @test perfect_hash("Asia/Ujung_Pandang") == 1047 + @test perfect_hash("Asia/Ulaanbaatar") == 662 + @test perfect_hash("Asia/Ulan_Bator") == 610 + @test perfect_hash("Asia/Urumqi") == 663 + @test perfect_hash("Asia/Ust-Nera") == 614 + @test perfect_hash("Asia/Vientiane") == 581 + @test perfect_hash("Asia/Vladivostok") == 719 + @test perfect_hash("Asia/Yakutsk") == 1190 + @test perfect_hash("Asia/Yangon") == 1104 + @test perfect_hash("Asia/Yekaterinburg") == 930 + @test perfect_hash("Asia/Yerevan") == 793 + @test perfect_hash("Atlantic/Azores") == 1008 + @test perfect_hash("Atlantic/Bermuda") == 769 + @test perfect_hash("Atlantic/Canary") == 1084 + @test perfect_hash("Atlantic/Cape_Verde") == 1089 + @test perfect_hash("Atlantic/Faeroe") == 770 + @test perfect_hash("Atlantic/Faroe") == 767 + @test perfect_hash("Atlantic/Jan_Mayen") == 771 + @test perfect_hash("Atlantic/Madeira") == 775 + @test perfect_hash("Atlantic/Reykjavik") == 1235 + @test perfect_hash("Atlantic/South_Georgia") == 808 + @test perfect_hash("Atlantic/St_Helena") == 1056 + @test perfect_hash("Atlantic/Stanley") == 1083 + @test perfect_hash("Australia/ACT") == 195 + @test perfect_hash("Australia/Adelaide") == 117 + @test perfect_hash("Australia/Brisbane") == 105 + @test perfect_hash("Australia/Broken_Hill") == 205 + @test perfect_hash("Australia/Canberra") == 115 + @test perfect_hash("Australia/Currie") == 153 + @test perfect_hash("Australia/Darwin") == 265 + @test perfect_hash("Australia/Eucla") == 178 + @test perfect_hash("Australia/Hobart") == 628 + @test perfect_hash("Australia/LHI") == 1279 + @test perfect_hash("Australia/Lindeman") == 322 + @test perfect_hash("Australia/Lord_Howe") == 318 + @test perfect_hash("Australia/Melbourne") == 563 + @test perfect_hash("Australia/NSW") == 896 + @test perfect_hash("Australia/North") == 456 + @test perfect_hash("Australia/Perth") == 562 + @test perfect_hash("Australia/Queensland") == 626 + @test perfect_hash("Australia/South") == 710 + @test perfect_hash("Australia/Sydney") == 995 + @test perfect_hash("Australia/Tasmania") == 180 + @test perfect_hash("Australia/Victoria") == 688 + @test perfect_hash("Australia/West") == 634 + @test perfect_hash("Australia/Yancowinna") == 104 + @test perfect_hash("Brazil/Acre") == 467 + @test perfect_hash("Brazil/DeNoronha") == 660 + @test perfect_hash("Brazil/East") == 519 + @test perfect_hash("Brazil/West") == 1023 + @test perfect_hash("CET") == 651 + @test perfect_hash("CST6CDT") == 300 + @test perfect_hash("Canada/Atlantic") == 558 + @test perfect_hash("Canada/Central") == 243 + @test perfect_hash("Canada/Eastern") == 173 + @test perfect_hash("Canada/Mountain") == 865 + @test perfect_hash("Canada/Newfoundland") == 213 + @test perfect_hash("Canada/Pacific") == 703 + @test perfect_hash("Canada/Saskatchewan") == 538 + @test perfect_hash("Canada/Yukon") == 100 + @test perfect_hash("Chile/Continental") == 486 + @test perfect_hash("Chile/EasterIsland") == 403 + @test perfect_hash("Cuba") == 29 + @test perfect_hash("EET") == 681 + @test perfect_hash("EST") == 217 + @test perfect_hash("EST5EDT") == 278 + @test perfect_hash("Egypt") == 725 + @test perfect_hash("Eire") == 230 + @test perfect_hash("Etc/GMT") == 186 + @test perfect_hash("Etc/GMT+0") == 398 + @test perfect_hash("Etc/GMT+1") == 158 + @test perfect_hash("Etc/GMT+10") == 352 + @test perfect_hash("Etc/GMT+11") == 159 + @test perfect_hash("Etc/GMT+12") == 112 + @test perfect_hash("Etc/GMT+2") == 132 + @test perfect_hash("Etc/GMT+3") == 141 + @test perfect_hash("Etc/GMT+4") == 128 + @test perfect_hash("Etc/GMT+5") == 179 + @test perfect_hash("Etc/GMT+6") == 188 + @test perfect_hash("Etc/GMT+7") == 201 + @test perfect_hash("Etc/GMT+8") == 192 + @test perfect_hash("Etc/GMT+9") == 148 + @test perfect_hash("Etc/GMT-0") == 395 + @test perfect_hash("Etc/GMT-1") == 155 + @test perfect_hash("Etc/GMT-10") == 349 + @test perfect_hash("Etc/GMT-11") == 156 + @test perfect_hash("Etc/GMT-12") == 109 + @test perfect_hash("Etc/GMT-13") == 130 + @test perfect_hash("Etc/GMT-14") == 118 + @test perfect_hash("Etc/GMT-2") == 129 + @test perfect_hash("Etc/GMT-3") == 138 + @test perfect_hash("Etc/GMT-4") == 125 + @test perfect_hash("Etc/GMT-5") == 176 + @test perfect_hash("Etc/GMT-6") == 185 + @test perfect_hash("Etc/GMT-7") == 198 + @test perfect_hash("Etc/GMT-8") == 189 + @test perfect_hash("Etc/GMT-9") == 145 + @test perfect_hash("Etc/GMT0") == 586 + @test perfect_hash("Etc/Greenwich") == 901 + @test perfect_hash("Etc/UCT") == 345 + @test perfect_hash("Etc/UTC") == 237 + @test perfect_hash("Etc/Universal") == 244 + @test perfect_hash("Etc/Zulu") == 175 + @test perfect_hash("Europe/Amsterdam") == 307 + @test perfect_hash("Europe/Andorra") == 121 + @test perfect_hash("Europe/Astrakhan") == 127 + @test perfect_hash("Europe/Athens") == 163 + @test perfect_hash("Europe/Belfast") == 202 + @test perfect_hash("Europe/Belgrade") == 168 + @test perfect_hash("Europe/Berlin") == 261 + @test perfect_hash("Europe/Bratislava") == 134 + @test perfect_hash("Europe/Brussels") == 126 + @test perfect_hash("Europe/Bucharest") == 478 + @test perfect_hash("Europe/Budapest") == 437 + @test perfect_hash("Europe/Busingen") == 139 + @test perfect_hash("Europe/Chisinau") == 194 + @test perfect_hash("Europe/Copenhagen") == 460 + @test perfect_hash("Europe/Dublin") == 388 + @test perfect_hash("Europe/Gibraltar") == 979 + @test perfect_hash("Europe/Guernsey") == 1135 + @test perfect_hash("Europe/Helsinki") == 714 + @test perfect_hash("Europe/Isle_of_Man") == 863 + @test perfect_hash("Europe/Istanbul") == 655 + @test perfect_hash("Europe/Jersey") == 1055 + @test perfect_hash("Europe/Kaliningrad") == 637 + @test perfect_hash("Europe/Kiev") == 752 + @test perfect_hash("Europe/Kirov") == 754 + @test perfect_hash("Europe/Lisbon") == 521 + @test perfect_hash("Europe/Ljubljana") == 590 + @test perfect_hash("Europe/London") == 654 + @test perfect_hash("Europe/Luxembourg") == 517 + @test perfect_hash("Europe/Madrid") == 617 + @test perfect_hash("Europe/Malta") == 592 + @test perfect_hash("Europe/Mariehamn") == 613 + @test perfect_hash("Europe/Minsk") == 1028 + @test perfect_hash("Europe/Monaco") == 1303 + @test perfect_hash("Europe/Moscow") == 1367 + @test perfect_hash("Europe/Nicosia") == 292 + @test perfect_hash("Europe/Oslo") == 499 + @test perfect_hash("Europe/Paris") == 264 + @test perfect_hash("Europe/Podgorica") == 543 + @test perfect_hash("Europe/Prague") == 268 + @test perfect_hash("Europe/Riga") == 879 + @test perfect_hash("Europe/Rome") == 1034 + @test perfect_hash("Europe/Samara") == 404 + @test perfect_hash("Europe/San_Marino") == 1147 + @test perfect_hash("Europe/Sarajevo") == 571 + @test perfect_hash("Europe/Saratov") == 408 + @test perfect_hash("Europe/Simferopol") == 682 + @test perfect_hash("Europe/Skopje") == 950 + @test perfect_hash("Europe/Sofia") == 700 + @test perfect_hash("Europe/Stockholm") == 1163 + @test perfect_hash("Europe/Tallinn") == 327 + @test perfect_hash("Europe/Tirane") == 371 + @test perfect_hash("Europe/Tiraspol") == 457 + @test perfect_hash("Europe/Ulyanovsk") == 569 + @test perfect_hash("Europe/Uzhgorod") == 254 + @test perfect_hash("Europe/Vaduz") == 1243 + @test perfect_hash("Europe/Vatican") == 1158 + @test perfect_hash("Europe/Vienna") == 867 + @test perfect_hash("Europe/Vilnius") == 878 + @test perfect_hash("Europe/Volgograd") == 1025 + @test perfect_hash("Europe/Warsaw") == 742 + @test perfect_hash("Europe/Zagreb") == 891 + @test perfect_hash("Europe/Zaporozhye") == 869 + @test perfect_hash("Europe/Zurich") == 1625 + @test perfect_hash("GB") == 713 + @test perfect_hash("GB-Eire") == 772 + @test perfect_hash("GMT") == 785 + @test perfect_hash("GMT+0") == 949 + @test perfect_hash("GMT-0") == 946 + @test perfect_hash("GMT0") == 1185 + @test perfect_hash("Greenwich") == 1521 + @test perfect_hash("HST") == 701 + @test perfect_hash("Hongkong") == 1179 + @test perfect_hash("Iceland") == 570 + @test perfect_hash("Indian/Antananarivo") == 491 + @test perfect_hash("Indian/Chagos") == 510 + @test perfect_hash("Indian/Christmas") == 524 + @test perfect_hash("Indian/Cocos") == 812 + @test perfect_hash("Indian/Comoro") == 811 + @test perfect_hash("Indian/Kerguelen") == 1029 + @test perfect_hash("Indian/Mahe") == 960 + @test perfect_hash("Indian/Maldives") == 982 + @test perfect_hash("Indian/Mauritius") == 973 + @test perfect_hash("Indian/Mayotte") == 961 + @test perfect_hash("Indian/Reunion") == 1138 + @test perfect_hash("Iran") == 461 + @test perfect_hash("Israel") == 724 + @test perfect_hash("Jamaica") == 623 + @test perfect_hash("Japan") == 611 + @test perfect_hash("Kwajalein") == 861 + @test perfect_hash("Libya") == 705 + @test perfect_hash("MET") == 1085 + @test perfect_hash("MST") == 621 + @test perfect_hash("MST7MDT") == 691 + @test perfect_hash("Mexico/BajaNorte") == 881 + @test perfect_hash("Mexico/BajaSur") == 1131 + @test perfect_hash("Mexico/General") == 1668 + @test perfect_hash("NZ") == 731 + @test perfect_hash("NZ-CHAT") == 120 + @test perfect_hash("Navajo") == 358 + @test perfect_hash("PRC") == 393 + @test perfect_hash("PST8PDT") == 405 + @test perfect_hash("Pacific/Apia") == 348 + @test perfect_hash("Pacific/Auckland") == 923 + @test perfect_hash("Pacific/Bougainville") == 493 + @test perfect_hash("Pacific/Chatham") == 684 + @test perfect_hash("Pacific/Chuuk") == 722 + @test perfect_hash("Pacific/Easter") == 889 + @test perfect_hash("Pacific/Efate") == 888 + @test perfect_hash("Pacific/Enderbury") == 1222 + @test perfect_hash("Pacific/Fakaofo") == 1176 + @test perfect_hash("Pacific/Fiji") == 1215 + @test perfect_hash("Pacific/Funafuti") == 1044 + @test perfect_hash("Pacific/Galapagos") == 963 + @test perfect_hash("Pacific/Gambier") == 1076 + @test perfect_hash("Pacific/Guadalcanal") == 1065 + @test perfect_hash("Pacific/Guam") == 1223 + @test perfect_hash("Pacific/Honolulu") == 824 + @test perfect_hash("Pacific/Johnston") == 1165 + @test perfect_hash("Pacific/Kiritimati") == 576 + @test perfect_hash("Pacific/Kosrae") == 556 + @test perfect_hash("Pacific/Kwajalein") == 721 + @test perfect_hash("Pacific/Majuro") == 548 + @test perfect_hash("Pacific/Marquesas") == 354 + @test perfect_hash("Pacific/Midway") == 740 + @test perfect_hash("Pacific/Nauru") == 797 + @test perfect_hash("Pacific/Niue") == 766 + @test perfect_hash("Pacific/Norfolk") == 916 + @test perfect_hash("Pacific/Noumea") == 938 + @test perfect_hash("Pacific/Pago_Pago") == 817 + @test perfect_hash("Pacific/Palau") == 936 + @test perfect_hash("Pacific/Pitcairn") == 1241 + @test perfect_hash("Pacific/Pohnpei") == 1181 + @test perfect_hash("Pacific/Ponape") == 819 + @test perfect_hash("Pacific/Port_Moresby") == 1167 + @test perfect_hash("Pacific/Rarotonga") == 604 + @test perfect_hash("Pacific/Saipan") == 748 + @test perfect_hash("Pacific/Samoa") == 597 + @test perfect_hash("Pacific/Tahiti") == 837 + @test perfect_hash("Pacific/Tarawa") == 463 + @test perfect_hash("Pacific/Tongatapu") == 505 + @test perfect_hash("Pacific/Truk") == 789 + @test perfect_hash("Pacific/Wake") == 550 + @test perfect_hash("Pacific/Wallis") == 577 + @test perfect_hash("Pacific/Yap") == 1680 + @test perfect_hash("Poland") == 450 + @test perfect_hash("Portugal") == 970 + @test perfect_hash("ROC") == 732 + @test perfect_hash("ROK") == 1180 + @test perfect_hash("Singapore") == 496 + @test perfect_hash("Turkey") == 807 + @test perfect_hash("UCT") == 377 + @test perfect_hash("US/Alaska") == 423 + @test perfect_hash("US/Aleutian") == 443 + @test perfect_hash("US/Arizona") == 392 + @test perfect_hash("US/Central") == 356 + @test perfect_hash("US/East-Indiana") == 873 + @test perfect_hash("US/Eastern") == 279 + @test perfect_hash("US/Hawaii") == 962 + @test perfect_hash("US/Indiana-Starke") == 945 + @test perfect_hash("US/Michigan") == 1058 + @test perfect_hash("US/Mountain") == 712 + @test perfect_hash("US/Pacific") == 975 + @test perfect_hash("US/Samoa") == 483 + @test perfect_hash("UTC") == 269 + @test perfect_hash("Universal") == 412 + @test perfect_hash("W-SU") == 791 + @test perfect_hash("WET") == 1169 + @test perfect_hash("Zulu") == 802 + end + end +end diff --git a/test/types/variabletimezone.jl b/test/types/variabletimezone.jl index e78247cd..1e6004e8 100644 --- a/test/types/variabletimezone.jl +++ b/test/types/variabletimezone.jl @@ -46,7 +46,7 @@ @test !isequal(a, b) @test hash(a) == hash(b) end - + @testset "hash using name" begin a = first(compile("Europe/Warsaw", tzdata["europe"])) b = VariableTimeZone("Europe/Warsaw", a.transitions[1:1], nothing)