From 2270ee83c1df13571b8039caaba4da726442e5c8 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 15 Apr 2021 16:33:25 +0100 Subject: [PATCH 1/8] Introduce a bitstype just for IANA Variable TimeZones --- src/TimeZones.jl | 2 + src/arithmetic.jl | 4 +- src/conversions.jl | 2 + src/discovery.jl | 11 +-- src/interpret.jl | 4 + src/types/ianatimezone.jl | 136 ++++++++++++++++++++++++++++++++++ src/types/timezone.jl | 5 +- src/types/variabletimezone.jl | 25 +++++-- src/types/zoneddatetime.jl | 10 ++- 9 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 src/types/ianatimezone.jl 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..9e1895ab --- /dev/null +++ b/src/types/ianatimezone.jl @@ -0,0 +1,136 @@ + +# This is extremely redundant but still only about 8MB for each of our 2 tables +# and it avoids any need to write a smarter but more CPU time expensive perfect hash +# This needs to be big enough to avoid any collisions +# also extra size is useful because it means we are probably safe if new timezones are added +const IANA_TABLE_SIZE = 2^20 + +const IANA_TIMEZONES = Vector{VariableTimeZone}(undef, IANA_TABLE_SIZE) + +# TODO: maybe fill this during build(), probably by generating a julia file. +# That way we can avoid actually instantitating every timezone til it is needed. +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() + # TODO: we should workout how to filter out FixedTimeZones here + mod_id = iana_mod_id(name) + # Important: Make sure our hash is perfect (even module the table size) + isassigned(IANA_NAMES, mod_id) && error("hash collision for $tz, at $mod_id") + IANA_NAMES[mod_id] = name + end + return IANA_NAMES +end + +# have checked that this is perfect +perfect_hash(tz::VariableTimeZone, h=zero(UInt)) = perfect_hash(tz.name, h) +function perfect_hash(name::AbstractString, h=zero(UInt)) + h = hash(:timezone, h) + h = hash(name, h) + return h +end + +iana_mod_id(str_or_var_tz) = iana_mod_id(perfect_hash(str_or_var_tz)) +iana_mod_id(id::UInt) = mod1(id, IANA_TABLE_SIZE) + +function is_standard_iana(str::AbstractString) + mod_id = iana_mod_id(str) + return isassigned(IANA_NAMES, mod_id) && IANA_NAMES[mod_id] == str +end + +function get_iana_timezone!(str::AbstractString) + mod_id = iana_mod_id(str) + if isassigned(IANA_TIMEZONES, mod_id) + IANA_TIMEZONES[mod_id] + else + tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...) + tz, class = deserialize(tz_path) + # TODO: maybe here is where we check if it is a FixedTimeZone, and if so don't remember it? + if tz isa VariableTimeZone + IANA_TIMEZONES[mod_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) + mod_id = iana_mod_id(id) + if isassigned(IANA_NAMES, mod_id) + name = IANA_NAMES[mod_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 hash of the corresponding Variable/FixedTimeZone + # and it is only possible if `hash` on all timezones in tzdata happens to be perfect + # This is the real hash, not the hash modulo IANA_TABLE_SIZE + # because that way we can in the future change IANA_TABLE_SIZE and not invalidate old + # serialized data. + id::UInt +end + +function IANATimeZone(name::AbstractString) + return IANATimeZone(perfect_hash(name)) +end + +backing_timezone(itz::IANATimeZone) = get_iana_timezone!(itz.id) + +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 + +# TODO: we have the hash, it seems like we should be able to use that to get seeded hash +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 +function Base.hasproperty(tz::IANATimeZone, s::Symbol) + return s === :name || s === :transitions || hasfield(IANATimeZone, s) +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..e0b77bd6 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) @@ -38,8 +55,4 @@ function Base.isequal(a::VariableTimeZone, b::VariableTimeZone) ) end -function Base.hash(tz::VariableTimeZone, h::UInt) - h = hash(:timezone, h) - h = hash(tz.name, h) - return h -end +Base.hash(tz::VariableTimeZone, h::UInt) = perfect_hash(tz, h) 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 From 886d2be37a8fd3348ce2ec089d0140d8e2c24070 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 15 Apr 2021 16:53:43 +0100 Subject: [PATCH 2/8] remove outdated comment --- src/types/ianatimezone.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl index 9e1895ab..8a0d6b18 100644 --- a/src/types/ianatimezone.jl +++ b/src/types/ianatimezone.jl @@ -44,7 +44,6 @@ function get_iana_timezone!(str::AbstractString) else tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...) tz, class = deserialize(tz_path) - # TODO: maybe here is where we check if it is a FixedTimeZone, and if so don't remember it? if tz isa VariableTimeZone IANA_TIMEZONES[mod_id] = tz return IANATimeZone(perfect_hash(str)) From 091493bd04409733c3104db2e7e68505de791379 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 15 Apr 2021 19:27:13 +0100 Subject: [PATCH 3/8] Use a proper perfect_hash for IANATimeZone --- src/types/ianatimezone.jl | 92 +++-- src/types/variabletimezone.jl | 6 +- test/runtests.jl | 1 + test/types/ianatimezone.jl | 601 +++++++++++++++++++++++++++++++++ test/types/variabletimezone.jl | 2 +- 5 files changed, 666 insertions(+), 36 deletions(-) create mode 100644 test/types/ianatimezone.jl diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl index 8a0d6b18..1bd1c113 100644 --- a/src/types/ianatimezone.jl +++ b/src/types/ianatimezone.jl @@ -1,9 +1,49 @@ +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 = ( + 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, + ) + + units = codeunits(str) + len = length(units) + hval = len + + 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 + return hval +end -# This is extremely redundant but still only about 8MB for each of our 2 tables -# and it avoids any need to write a smarter but more CPU time expensive perfect hash -# This needs to be big enough to avoid any collisions -# also extra size is useful because it means we are probably safe if new timezones are added -const IANA_TABLE_SIZE = 2^20 const IANA_TIMEZONES = Vector{VariableTimeZone}(undef, IANA_TABLE_SIZE) @@ -13,39 +53,28 @@ 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() # TODO: we should workout how to filter out FixedTimeZones here - mod_id = iana_mod_id(name) + id = perfect_hash(name) # Important: Make sure our hash is perfect (even module the table size) - isassigned(IANA_NAMES, mod_id) && error("hash collision for $tz, at $mod_id") - IANA_NAMES[mod_id] = name + isassigned(IANA_NAMES, id) && error("hash collision for $tz, at $id") + IANA_NAMES[id] = name end return IANA_NAMES end -# have checked that this is perfect -perfect_hash(tz::VariableTimeZone, h=zero(UInt)) = perfect_hash(tz.name, h) -function perfect_hash(name::AbstractString, h=zero(UInt)) - h = hash(:timezone, h) - h = hash(name, h) - return h -end - -iana_mod_id(str_or_var_tz) = iana_mod_id(perfect_hash(str_or_var_tz)) -iana_mod_id(id::UInt) = mod1(id, IANA_TABLE_SIZE) - function is_standard_iana(str::AbstractString) - mod_id = iana_mod_id(str) - return isassigned(IANA_NAMES, mod_id) && IANA_NAMES[mod_id] == str + id = perfect_hash(str) + return isassigned(IANA_NAMES, id) && IANA_NAMES[id] == str end function get_iana_timezone!(str::AbstractString) - mod_id = iana_mod_id(str) - if isassigned(IANA_TIMEZONES, mod_id) - IANA_TIMEZONES[mod_id] + 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[mod_id] = tz + IANA_TIMEZONES[id] = tz return IANATimeZone(perfect_hash(str)) else # it is a FixedTimeZone, we are not going to use a IANATimeZone @@ -55,9 +84,8 @@ function get_iana_timezone!(str::AbstractString) end function get_iana_timezone!(id::UInt) - mod_id = iana_mod_id(id) - if isassigned(IANA_NAMES, mod_id) - name = IANA_NAMES[mod_id] + if isassigned(IANA_NAMES, id) + name = IANA_NAMES[id] return get_iana_timezone!(name) else error( @@ -75,11 +103,7 @@ 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 hash of the corresponding Variable/FixedTimeZone - # and it is only possible if `hash` on all timezones in tzdata happens to be perfect - # This is the real hash, not the hash modulo IANA_TABLE_SIZE - # because that way we can in the future change IANA_TABLE_SIZE and not invalidate old - # serialized data. + # id must be a prefect_hash of the corresponding timezone name id::UInt end @@ -87,7 +111,7 @@ function IANATimeZone(name::AbstractString) return IANATimeZone(perfect_hash(name)) end -backing_timezone(itz::IANATimeZone) = get_iana_timezone!(itz.id) +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 diff --git a/src/types/variabletimezone.jl b/src/types/variabletimezone.jl index e0b77bd6..f3947a0c 100644 --- a/src/types/variabletimezone.jl +++ b/src/types/variabletimezone.jl @@ -55,4 +55,8 @@ function Base.isequal(a::VariableTimeZone, b::VariableTimeZone) ) end -Base.hash(tz::VariableTimeZone, h::UInt) = perfect_hash(tz, h) +function Base.hash(tz::VariableTimeZone, h::UInt) + h = hash(:timezone, h) + h = hash(tz.name, h) + return h +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..4a603707 --- /dev/null +++ b/test/types/ianatimezone.jl @@ -0,0 +1,601 @@ +@testset "IANATimeZone" begin + @testset "perfect_hash" begin + perfect_hash = TimeZones.perfect_hash + # 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 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) From fc2535b4dfcfb02b3cf4b054fb119dee8eb4b5e4 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 15 Apr 2021 19:54:56 +0100 Subject: [PATCH 4/8] update comment --- src/types/ianatimezone.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl index 1bd1c113..74cdb8f5 100644 --- a/src/types/ianatimezone.jl +++ b/src/types/ianatimezone.jl @@ -117,7 +117,7 @@ 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 -# TODO: we have the hash, it seems like we should be able to use that to get seeded hash +# 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)) From eb841d028ad3c3ce4383c05f5cc89b0d713585ec Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 15 Apr 2021 20:06:37 +0100 Subject: [PATCH 5/8] remove outdated comment --- src/types/ianatimezone.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl index 74cdb8f5..27f0f878 100644 --- a/src/types/ianatimezone.jl +++ b/src/types/ianatimezone.jl @@ -48,13 +48,11 @@ end const IANA_TIMEZONES = Vector{VariableTimeZone}(undef, IANA_TABLE_SIZE) # TODO: maybe fill this during build(), probably by generating a julia file. -# That way we can avoid actually instantitating every timezone til it is needed. 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() - # TODO: we should workout how to filter out FixedTimeZones here id = perfect_hash(name) - # Important: Make sure our hash is perfect (even module the table size) + # Important: this line make's sure our hash is indeed perfect isassigned(IANA_NAMES, id) && error("hash collision for $tz, at $id") IANA_NAMES[id] = name end From 10b6d747c75336ac6e3b70999854bb75d84bc12c Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 15 Apr 2021 20:07:44 +0100 Subject: [PATCH 6/8] type in comment --- src/types/ianatimezone.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl index 27f0f878..a6ffe0c7 100644 --- a/src/types/ianatimezone.jl +++ b/src/types/ianatimezone.jl @@ -52,7 +52,7 @@ 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 make's sure our hash is indeed perfect + # 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 From ae3724839a03249a0677716c624318ea7e35da37 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 15 Apr 2021 20:11:58 +0100 Subject: [PATCH 7/8] julia 1.0 doesn't have hasproperty --- src/types/ianatimezone.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl index a6ffe0c7..9e485700 100644 --- a/src/types/ianatimezone.jl +++ b/src/types/ianatimezone.jl @@ -131,8 +131,10 @@ function Base.getproperty(tz::IANATimeZone, s::Symbol) return getfield(tz, s) end end -function Base.hasproperty(tz::IANATimeZone, s::Symbol) - return s === :name || s === :transitions || hasfield(IANATimeZone, s) +if isdefined(Base, :hasproperty) + function Base.hasproperty(tz::IANATimeZone, s::Symbol) + return s === :name || s === :transitions || hasfield(IANATimeZone, s) + end end From 96b0ee830e4c9e4e8f281e84d1bb1fa7b5e84467 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Fri, 16 Apr 2021 13:32:34 +0100 Subject: [PATCH 8/8] bounds checking and performance tweeks for perfect hash --- src/types/ianatimezone.jl | 36 +- test/types/ianatimezone.jl | 1217 ++++++++++++++++++------------------ 2 files changed, 644 insertions(+), 609 deletions(-) diff --git a/src/types/ianatimezone.jl b/src/types/ianatimezone.jl index 9e485700..b87f6ee3 100644 --- a/src/types/ianatimezone.jl +++ b/src/types/ianatimezone.jl @@ -12,6 +12,8 @@ function perfect_hash(str::AbstractString) # 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, @@ -24,23 +26,33 @@ function perfect_hash(str::AbstractString) 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, + 22, 320, 245, 1, 1681, 1681, 1681, 1681, ) units = codeunits(str) len = length(units) - hval = len - 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 + 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 diff --git a/test/types/ianatimezone.jl b/test/types/ianatimezone.jl index 4a603707..4270f5db 100644 --- a/test/types/ianatimezone.jl +++ b/test/types/ianatimezone.jl @@ -1,601 +1,624 @@ -@testset "IANATimeZone" begin +@btime @testset "IANATimeZone" begin @testset "perfect_hash" begin perfect_hash = TimeZones.perfect_hash - # 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 + + @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