Skip to content
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
ShortStrings = "63221d1c-8677-4ff0-9126-0ff0817b4975"
Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[compat]
EzXML = "0.9.1, 1"
Mocking = "0.7"
RecipesBase = "0.7, 0.8, 1"
ShortStrings = "0.3.5"
julia = "1"

[extras]
Expand Down
2 changes: 2 additions & 0 deletions src/TimeZones.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module TimeZones

using Dates
using Printf
using ShortStrings
using Serialization
using RecipesBase: RecipesBase, @recipe
using Unicode
Expand Down Expand Up @@ -55,6 +56,7 @@ include("indexable_generator.jl")

include("class.jl")
include("utcoffset.jl")
include(joinpath("types", "name.jl"))
include(joinpath("types", "timezone.jl"))
include(joinpath("types", "fixedtimezone.jl"))
include(joinpath("types", "variabletimezone.jl"))
Expand Down
2 changes: 1 addition & 1 deletion src/types/fixedtimezone.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const FIXED_TIME_ZONE_REGEX = r"""
A `TimeZone` with a constant offset for all of time.
"""
struct FixedTimeZone <: TimeZone
name::String
name::Name
offset::UTCOffset
end

Expand Down
56 changes: 56 additions & 0 deletions src/types/name.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
struct SName
region::ShortString15
locality1::ShortString15
locality2::ShortString15
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe should be a subtype of AbstractString?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering it.
But it is a lot of work to subtype AbstractString. Especially because we need to match equality and thus hash with String.
And this is a internal type that is used for an internal field.
And the main operation that matters is comparing for equality with other SNames

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use a larger ShortString and skip this custom type all together? If ShortString63 is larger than you want you could always tweak the size by defining a primitive type and use ShortString{T}. This gets you the string functionality without you having to write it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think that is viable than sure.
Based on your earlier comments that i reinterated here #271 (comment)
I thought it wasn't
In particular due to the BitInterger seralization one (which i now realized has been fixed).

I also though that they would be slower, as i have found that they are much slower than expected once you get large, but it seems that that doesn't really kick in for ShortString63.
It is a bit slower for some operations but a bit faster for others.

Benchmarks

== false

julia> @btime x==y setup=(x=convert(TimeZones.SName, "America/Winnipeg"); y=convert(TimeZones.SName, "America/Argentina/ComodRivadavia"))
  1.504 ns (0 allocations: 0 bytes)
false

julia> @btime x==y setup=(x=ShortString63("America/Winnipeg"); y=ShortString63("America/Argentina/ComodRivadavia"))
  2.117 ns (0 allocations: 0 bytes)
false

== true

(basically exactly the same as the false case)

julia> @btime x==y setup=(x=convert(TimeZones.SName, "America/Winnipeg"); y=convert(TimeZones.SName, "America/Winnipeg"))
  1.505 ns (0 allocations: 0 bytes)
true

julia> @btime x==y setup=(x=ShortString63("America/Winnipeg"); y=ShortString63("America/Winnipeg"))
  2.115 ns (0 allocations: 0 bytes)
true

hash

julia> @btime hash(x)==hash(y) setup=(x=convert(TimeZones.SName, "America/Winnipeg"); y=convert(TimeZones.SName, "America/Argentina/ComodRivadavia"))
  34.256 ns (0 allocations: 0 bytes);
  
julia> @btime hash(x)==hash(y) setup=(x=ShortString63("America/Winnipeg"); y=ShortString63("America/Argentina/ComodRivadavia"));
  27.598 ns (0 allocations: 0 bytes)

So I will make that change, since it is simpler


function Base.print(io::IO, name::SName)
print(io, name.region)
if !isempty(name.locality1)
print(io,"/", name.locality1)
if !isempty(name.locality2)
print(io,"/", name.locality2)
end
end
end

Base.convert(::Type{String}, name::SName) = string(name)
function Base.convert(::Type{SName}, str::AbstractString)
name = try_convert(SName, str)
name isa Nothing && DomainError(str, "All timezone name parts must have length < 16")
return name
end

try_convert(::Type{SName}, name::SName) = name
try_convert(::Type{String}, name::String) = name
function try_convert(::Type{SName}, str::AbstractString)
parts = split(str, "/"; limit=3)
all(length(parts) < 16) ||return nothing
return if length(parts) == 3
SName(parts[1], parts[2], parts[3])
elseif length(parts) == 2
SName(parts[1], parts[2], ss15"")
else
SName(parts[1], ss15"", ss15"")
end
end


Base.isempty(name::SName) = isempty(name.region) # region being empty implies all empty

name_parts(str::AbstractString) = split(str, "/")
function name_parts(name::SName)
# TODO this could be faster by returning an iterator but not really performance critial
parts = [name.region]
if !isempty(name.locality1)
push!(parts, name.locality1)
if !isempty(name.locality2)
push!(parts, name.locality2)
end
end
return parts
end

# Short strings are broken on 32bit:
# TODO: https://github.com/JuliaString/MurmurHash3.jl/issues/12
const Name = Int === Int32 ? String : SName
22 changes: 14 additions & 8 deletions src/types/timezone.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const TIME_ZONE_CACHE = Dict{String,Tuple{TimeZone,Class}}()
const TIME_ZONE_CACHE = Dict{Name,Tuple{TimeZone,Class}}()

"""
TimeZone(str::AbstractString) -> TimeZone
Expand Down Expand Up @@ -41,11 +41,15 @@ US/Pacific (UTC-8/UTC-7)
TimeZone(::AbstractString, ::Class)

function TimeZone(str::AbstractString, mask::Class=Class(:DEFAULT))
return TimeZone(convert(Name, str), mask)
end

function TimeZone(name::Name, mask::Class=Class(:DEFAULT))
str = string(name)
# Note: If the class `mask` does not match the time zone we'll still load the
# information into the cache to ensure the result is consistent.
tz, class = get!(TIME_ZONE_CACHE, str) do
tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...)

tz, class = get!(TIME_ZONE_CACHE, name) do
tz_path = joinpath(TZData.COMPILED_DIR, name_parts(name)...)
if isfile(tz_path)
open(deserialize, tz_path, "r")
elseif occursin(FIXED_TIME_ZONE_REGEX, str)
Expand Down Expand Up @@ -91,19 +95,21 @@ end

Check whether a string is a valid for constructing a `TimeZone` with the provided `mask`.
"""
function istimezone(str::AbstractString, mask::Class=Class(:DEFAULT))
function istimezone(str::Union{AbstractString, Name}, mask::Class=Class(:DEFAULT))
# Start by performing quick FIXED class test
if mask & Class(:FIXED) != Class(:NONE) && occursin(FIXED_TIME_ZONE_REGEX, str)
if mask & Class(:FIXED) != Class(:NONE) && occursin(FIXED_TIME_ZONE_REGEX, string(str))
return true
end
name = try_convert(Name, str)
name isa Nothing && return false

# Perform more expensive checks against pre-compiled time zones
tz, class = get(TIME_ZONE_CACHE, str) do
tz_path = joinpath(TZData.COMPILED_DIR, split(str, "/")...)
tz_path = joinpath(TZData.COMPILED_DIR, name_parts(name)...)

if isfile(tz_path)
# Cache the data since we're already performing the deserialization
TIME_ZONE_CACHE[str] = open(deserialize, tz_path, "r")
TIME_ZONE_CACHE[name] = open(deserialize, tz_path, "r")
else
nothing, Class(:NONE)
end
Expand Down
9 changes: 4 additions & 5 deletions src/types/variabletimezone.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ Base.isless(a::Transition, b::Transition) = isless(a.utc_datetime, b.utc_datetim
A `TimeZone` with an offset that changes over time.
"""
struct VariableTimeZone <: TimeZone
name::String
name::Name
transitions::Vector{Transition}
cutoff::Union{DateTime,Nothing}

function VariableTimeZone(name::AbstractString, transitions::Vector{Transition}, cutoff::Union{DateTime,Nothing}=nothing)
new(name, transitions, cutoff)
end
end
function VariableTimeZone(name::AbstractString, transitions::Vector{Transition})
VariableTimeZone(name, transitions, nothing)
end

name(tz::VariableTimeZone) = tz.name
Expand Down
20 changes: 9 additions & 11 deletions src/types/zoneddatetime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@ using Dates: AbstractDateTime, argerror, validargs
# A `DateTime` that includes `TimeZone` information.
# """

struct ZonedDateTime <: AbstractDateTime
struct ZonedDateTime{T<:TimeZone} <: AbstractDateTime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learned from Intervals.jl changing the type parameters like this should be considered a breaking change. I'd probably punt this as part of this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't achieve the goal of making a ZonedDateTime with a FixedTimeZone isbits without this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But i could remove it from this PR.
though we would lose many of the benifits.
(we would get most of those benifits from just #327)

though it would make it easier to make a PR that pushed the timezone as a value in the type-parameter.
Which might be the preferred breaking change.

utc_datetime::DateTime
timezone::TimeZone
timezone::T
zone::FixedTimeZone # The current zone for the utc_datetime.
end

function ZonedDateTime(utc_datetime::DateTime, timezone::TimeZone, zone::FixedTimeZone)
return new(utc_datetime, timezone, zone)
function ZonedDateTime(
utc_datetime::DateTime, timezone::VariableTimeZone, zone::FixedTimeZone
)
if timezone.cutoff !== nothing && utc_datetime >= timezone.cutoff
throw(UnhandledTimeError(timezone))
end

function ZonedDateTime(utc_datetime::DateTime, timezone::VariableTimeZone, zone::FixedTimeZone)
if timezone.cutoff !== nothing && utc_datetime >= timezone.cutoff
throw(UnhandledTimeError(timezone))
end

return new(utc_datetime, timezone, zone)
end
return ZonedDateTime{VariableTimeZone}(utc_datetime, timezone, zone)
end

"""
Expand Down
4 changes: 2 additions & 2 deletions src/tzdata/compile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using Dates: parse_components

using ...TimeZones: TIME_ZONE_CACHE
using ...TimeZones: TimeZones, TimeZone, FixedTimeZone, VariableTimeZone, Transition, Class
using ...TimeZones: rename
using ...TimeZones: name_parts, rename, try_convert
using ..TZData: TimeOffset, ZERO, MIN_GMT_OFFSET, MAX_GMT_OFFSET, MIN_SAVE, MAX_SAVE,
ABS_DIFF_OFFSET

Expand Down Expand Up @@ -697,7 +697,7 @@ function compile(tz_source::TZSource, dest_dir::AbstractString; kwargs...)
empty!(TIME_ZONE_CACHE)

for (tz, class) in results
parts = split(TimeZones.name(tz), '/')
parts = name_parts(TimeZones.name(tz))
tz_path = joinpath(dest_dir, parts...)
tz_dir = dirname(tz_path)

Expand Down
4 changes: 2 additions & 2 deletions test/arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ spring_zdt = ZonedDateTime(spring, warsaw)
)
@test results == expected
@test length(results) == 2
@test results isa StepRange{ZonedDateTime}
@test results isa StepRange{<:ZonedDateTime}
end

@testset "date-period" begin
Expand All @@ -89,7 +89,7 @@ spring_zdt = ZonedDateTime(spring, warsaw)
)
@test results == expected
@test length(results) == 2
@test results isa StepRange{ZonedDateTime}
@test results isa StepRange{<:ZonedDateTime}
end

@testset "ambiguous" begin
Expand Down
2 changes: 1 addition & 1 deletion test/interpret.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ long = VariableTimeZone("Test/LongGap", [
])

# A time zone with an unnecessary transition that typically is hidden to the user
hidden = VariableTimeZone("Test/HiddenTransition", [
hidden = VariableTimeZone("Test/Hidden", [
Transition(DateTime(1800,1,1,0), zone["T+1"])
Transition(DateTime(1900,1,1,0), zone["T+0"])
Transition(DateTime(1935,4,1,2), zone["T+1"]) # The hidden transition
Expand Down
80 changes: 48 additions & 32 deletions test/types/zoneddatetime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ using Dates: Hour, Second, UTM, @dateformat_str
utc_dt = DateTime(1916, 1, 31, 23)

# Disambiguating parameters ignored when there is no ambiguity.
@test ZonedDateTime(local_dt, warsaw).zone.name == "CET"
@test ZonedDateTime(local_dt, warsaw, 0).zone.name == "CET"
@test ZonedDateTime(local_dt, warsaw, 1).zone.name == "CET"
@test ZonedDateTime(local_dt, warsaw, 2).zone.name == "CET"
@test ZonedDateTime(local_dt, warsaw, true).zone.name == "CET"
@test ZonedDateTime(local_dt, warsaw, false).zone.name == "CET"
@test ZonedDateTime(utc_dt, warsaw, from_utc=true).zone.name == "CET"
@test string(ZonedDateTime(local_dt, warsaw).zone.name) == "CET"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementing promotion rules should avoid all these changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Happy to hear if i am wrong though.
Subtyping AbstractString would (but that is more work see #324 (comment))
overloading equality would (but that can cause probelms since it would also require making hash agree and that is expensive to do)
AFIACT there is no promotion rule that gets invoked for == between Strign and a random type.

@test string(ZonedDateTime(local_dt, warsaw, 0).zone.name) == "CET"
@test string(ZonedDateTime(local_dt, warsaw, 1).zone.name) == "CET"
@test string(ZonedDateTime(local_dt, warsaw, 2).zone.name) == "CET"
@test string(ZonedDateTime(local_dt, warsaw, true).zone.name) == "CET"
@test string(ZonedDateTime(local_dt, warsaw, false).zone.name) == "CET"
@test string(ZonedDateTime(utc_dt, warsaw, from_utc=true).zone.name) == "CET"

@test ZonedDateTime(local_dt, warsaw).utc_datetime == utc_dt
@test ZonedDateTime(local_dt, warsaw, 0).utc_datetime == utc_dt
Expand All @@ -99,13 +99,13 @@ using Dates: Hour, Second, UTM, @dateformat_str
utc_dt = DateTime(1916, 5, 31, 22)

# Disambiguating parameters ignored when there is no ambiguity.
@test ZonedDateTime(local_dt, warsaw).zone.name == "CEST"
@test ZonedDateTime(local_dt, warsaw, 0).zone.name == "CEST"
@test ZonedDateTime(local_dt, warsaw, 1).zone.name == "CEST"
@test ZonedDateTime(local_dt, warsaw, 2).zone.name == "CEST"
@test ZonedDateTime(local_dt, warsaw, true).zone.name == "CEST"
@test ZonedDateTime(local_dt, warsaw, false).zone.name == "CEST"
@test ZonedDateTime(utc_dt, warsaw, from_utc=true).zone.name == "CEST"
@test string(ZonedDateTime(local_dt, warsaw).zone.name) == "CEST"
@test string(ZonedDateTime(local_dt, warsaw, 0).zone.name) == "CEST"
@test string(ZonedDateTime(local_dt, warsaw, 1).zone.name) == "CEST"
@test string(ZonedDateTime(local_dt, warsaw, 2).zone.name) == "CEST"
@test string(ZonedDateTime(local_dt, warsaw, true).zone.name) == "CEST"
@test string(ZonedDateTime(local_dt, warsaw, false).zone.name) == "CEST"
@test string(ZonedDateTime(utc_dt, warsaw, from_utc=true).zone.name) == "CEST"

@test ZonedDateTime(local_dt, warsaw).utc_datetime == utc_dt
@test ZonedDateTime(local_dt, warsaw, 0).utc_datetime == utc_dt
Expand Down Expand Up @@ -133,10 +133,10 @@ using Dates: Hour, Second, UTM, @dateformat_str
@test_throws NonExistentTimeError ZonedDateTime(local_dts[2], warsaw, true)
@test_throws NonExistentTimeError ZonedDateTime(local_dts[2], warsaw, false)

@test ZonedDateTime(local_dts[1], warsaw).zone.name == "CET"
@test ZonedDateTime(local_dts[3], warsaw).zone.name == "CEST"
@test ZonedDateTime(utc_dts[1], warsaw, from_utc=true).zone.name == "CET"
@test ZonedDateTime(utc_dts[2], warsaw, from_utc=true).zone.name == "CEST"
@test string(ZonedDateTime(local_dts[1], warsaw).zone.name) == "CET"
@test string(ZonedDateTime(local_dts[3], warsaw).zone.name) == "CEST"
@test string(ZonedDateTime(utc_dts[1], warsaw, from_utc=true).zone.name) == "CET"
@test string(ZonedDateTime(utc_dts[2], warsaw, from_utc=true).zone.name) == "CEST"

@test ZonedDateTime(local_dts[1], warsaw).utc_datetime == utc_dts[1]
@test ZonedDateTime(local_dts[3], warsaw).utc_datetime == utc_dts[2]
Expand All @@ -151,12 +151,12 @@ using Dates: Hour, Second, UTM, @dateformat_str
@test_throws AmbiguousTimeError ZonedDateTime(local_dt, warsaw)
@test_throws AmbiguousTimeError ZonedDateTime(local_dt, warsaw, 0)

@test ZonedDateTime(local_dt, warsaw, 1).zone.name == "CEST"
@test ZonedDateTime(local_dt, warsaw, 2).zone.name == "CET"
@test ZonedDateTime(local_dt, warsaw, true).zone.name == "CEST"
@test ZonedDateTime(local_dt, warsaw, false).zone.name == "CET"
@test ZonedDateTime(utc_dts[1], warsaw, from_utc=true).zone.name == "CEST"
@test ZonedDateTime(utc_dts[2], warsaw, from_utc=true).zone.name == "CET"
@test string(ZonedDateTime(local_dt, warsaw, 1).zone.name) == "CEST"
@test string(ZonedDateTime(local_dt, warsaw, 2).zone.name) == "CET"
@test string(ZonedDateTime(local_dt, warsaw, true).zone.name) == "CEST"
@test string(ZonedDateTime(local_dt, warsaw, false).zone.name) == "CET"
@test string(ZonedDateTime(utc_dts[1], warsaw, from_utc=true).zone.name) == "CEST"
@test string(ZonedDateTime(utc_dts[2], warsaw, from_utc=true).zone.name) == "CET"

@test ZonedDateTime(local_dt, warsaw, 1).utc_datetime == utc_dts[1]
@test ZonedDateTime(local_dt, warsaw, 2).utc_datetime == utc_dts[2]
Expand All @@ -172,12 +172,12 @@ using Dates: Hour, Second, UTM, @dateformat_str
utc_dts = (DateTime(1922, 5, 31, 21), DateTime(1922, 5, 31, 22))
@test_throws AmbiguousTimeError ZonedDateTime(local_dt, warsaw)

@test ZonedDateTime(local_dt, warsaw, 1).zone.name == "EET"
@test ZonedDateTime(local_dt, warsaw, 2).zone.name == "CET"
@test string(ZonedDateTime(local_dt, warsaw, 1).zone.name) == "EET"
@test string(ZonedDateTime(local_dt, warsaw, 2).zone.name) == "CET"
@test_throws AmbiguousTimeError ZonedDateTime(local_dt, warsaw, true)
@test_throws AmbiguousTimeError ZonedDateTime(local_dt, warsaw, false)
@test ZonedDateTime(utc_dts[1], warsaw, from_utc=true).zone.name == "EET"
@test ZonedDateTime(utc_dts[2], warsaw, from_utc=true).zone.name == "CET"
@test string(ZonedDateTime(utc_dts[1], warsaw, from_utc=true).zone.name) == "EET"
@test string(ZonedDateTime(utc_dts[2], warsaw, from_utc=true).zone.name) == "CET"

@test ZonedDateTime(local_dt, warsaw, 1).utc_datetime == utc_dts[1]
@test ZonedDateTime(local_dt, warsaw, 2).utc_datetime == utc_dts[2]
Expand Down Expand Up @@ -283,14 +283,14 @@ using Dates: Hour, Second, UTM, @dateformat_str

# Make sure that the duplicated hour only doesn't contain an additional entry.
@test_throws AmbiguousTimeError ZonedDateTime(DateTime(1935,9,1), dup)
@test ZonedDateTime(DateTime(1935,9,1), dup, 1).zone.name == "DTDT-2"
@test ZonedDateTime(DateTime(1935,9,1), dup, 2).zone.name == "DTST"
@test string(ZonedDateTime(DateTime(1935,9,1), dup, 1).zone.name) == "DTDT-2"
@test string(ZonedDateTime(DateTime(1935,9,1), dup, 2).zone.name) == "DTST"
@test_throws BoundsError ZonedDateTime(DateTime(1935,9,1), dup, 3)

# Ensure that DTDT-1 is completely ignored.
@test_throws NonExistentTimeError ZonedDateTime(DateTime(1935,4,1), dup)
@test ZonedDateTime(DateTime(1935,4,1,1), dup).zone.name == "DTDT-2"
@test ZonedDateTime(DateTime(1935,8,31,23), dup).zone.name == "DTDT-2"
@test string(ZonedDateTime(DateTime(1935,4,1,1), dup).zone.name) == "DTDT-2"
@test string(ZonedDateTime(DateTime(1935,8,31,23), dup).zone.name) == "DTDT-2"
end

@testset "equality" begin
Expand Down Expand Up @@ -430,4 +430,20 @@ using Dates: Hour, Second, UTM, @dateformat_str
@test typemin(ZonedDateTime) <= ZonedDateTime(typemin(DateTime), utc)
@test typemax(ZonedDateTime) >= ZonedDateTime(typemax(DateTime), utc)
end

# TODO: isbits is not working on 32 bit because of not using SName type, because of
# https://github.com/JuliaString/MurmurHash3.jl/issues/12
Int==Int64 && @testset "isbits" begin
utc_zdt = ZonedDateTime(1, 2, 3, 4, 5, 6, 7, utc)
@test isbits(utc)

var_zdt = ZonedDateTime(Date(2011, 6, 1), tz"America/Winnipeg")
@test !isbits(var_zdt) # we might like this, but we don't have it.
@test isbits(var_zdt.utc_datetime)
@test isbits(var_zdt.zone)
@test isbits(var_zdt.utc_datetime)
@test isbits(var_zdt.timezone.cutoff)
@test isbits(var_zdt.timezone.name)
@test isbitstype(eltype(var_zdt.timezone.transitions))
end
end
8 changes: 4 additions & 4 deletions test/tzdata/compile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ dates, ordered = order_rules([rule_post, rule_endless, rule_overlap, rule_pre],

# Europe/Warsaw time zone has a combination of factors that requires computing
# the abbreviation to be done in a specific way.
@test tz.transitions[1].zone.name == "LMT"
@test tz.transitions[2].zone.name == "WMT"
@test tz.transitions[3].zone.name == "CET" # Standard time
@test tz.transitions[4].zone.name == "CEST" # Daylight saving time
@test string(tz.transitions[1].zone.name) == "LMT"
@test string(tz.transitions[2].zone.name) == "WMT"
@test string(tz.transitions[3].zone.name) == "CET" # Standard time
@test string(tz.transitions[4].zone.name) == "CEST" # Daylight saving time
@test issorted(tz.transitions)

zone = Dict{AbstractString,FixedTimeZone}()
Expand Down