diff --git a/Project.toml b/Project.toml index aabb70883..715d978b9 100644 --- a/Project.toml +++ b/Project.toml @@ -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.7" julia = "1" [extras] diff --git a/src/TimeZones.jl b/src/TimeZones.jl index 754cecf58..57c60e20f 100644 --- a/src/TimeZones.jl +++ b/src/TimeZones.jl @@ -4,6 +4,7 @@ using Dates using Printf using Serialization using RecipesBase: RecipesBase, @recipe +using ShortStrings: ShortString15, ShortString63 using Unicode import Dates: TimeZone, UTC diff --git a/src/arithmetic.jl b/src/arithmetic.jl index e1991b711..9438ca19d 100644 --- a/src/arithmetic.jl +++ b/src/arithmetic.jl @@ -17,7 +17,7 @@ function Base.:(-)(zdt::ZonedDateTime, p::TimePeriod) return ZonedDateTime(DateTime(zdt, UTC) - p, timezone(zdt); from_utc=true) end -function broadcasted(::typeof(+), r::StepRange{ZonedDateTime}, p::DatePeriod) +function broadcasted(::typeof(+), r::StepRange{<:ZonedDateTime}, p::DatePeriod) start, step, stop = first(r), Base.step(r), last(r) # Since the local time + period can result in an invalid local datetime when working with @@ -41,10 +41,10 @@ function broadcasted(::typeof(+), r::StepRange{ZonedDateTime}, p::DatePeriod) return StepRange(start, step, stop) end -function broadcasted(::typeof(+), r::StepRange{ZonedDateTime}, p::TimePeriod) +function broadcasted(::typeof(+), r::StepRange{<:ZonedDateTime}, p::TimePeriod) return StepRange(r.start + p, r.step, r.stop + p) end -broadcasted(::typeof(+), p::Period, r::StepRange{ZonedDateTime}) = broadcasted(+, r, p) -broadcasted(::typeof(-), r::StepRange{ZonedDateTime}, p::Period) = broadcasted(+, r, -p) -broadcasted(::typeof(-), p::Period, r::StepRange{ZonedDateTime}) = broadcasted(-, r, p) +broadcasted(::typeof(+), p::Period, r::StepRange{<:ZonedDateTime}) = broadcasted(+, r, p) +broadcasted(::typeof(-), r::StepRange{<:ZonedDateTime}, p::Period) = broadcasted(+, r, -p) +broadcasted(::typeof(-), p::Period, r::StepRange{<:ZonedDateTime}) = broadcasted(-, r, p) diff --git a/src/types/fixedtimezone.jl b/src/types/fixedtimezone.jl index 1d349ca07..a6bd2a181 100644 --- a/src/types/fixedtimezone.jl +++ b/src/types/fixedtimezone.jl @@ -1,3 +1,7 @@ +# Ideally would always use ShortString15, but it's `hash` is broken on 32-bit systems. +# https://github.com/JuliaString/MurmurHash3.jl/issues/12 +const FixedTimeZoneName = Int === Int64 ? ShortString15 : String + const FIXED_TIME_ZONE_REGEX = r""" ^(?| Z @@ -30,7 +34,7 @@ const FIXED_TIME_ZONE_REGEX = r""" A `TimeZone` with a constant offset for all of time. """ struct FixedTimeZone <: TimeZone - name::String + name::FixedTimeZoneName offset::UTCOffset end @@ -72,7 +76,7 @@ UTC+15:45:21 function FixedTimeZone(s::AbstractString) s == "Z" && return UTC_ZERO - m = match(FIXED_TIME_ZONE_REGEX, s) + m = match(FIXED_TIME_ZONE_REGEX, String(s)) m === nothing && throw(ArgumentError("Unrecognized time zone: $s")) coefficient = m[:sign] == "-" ? -1 : 1 diff --git a/src/types/variabletimezone.jl b/src/types/variabletimezone.jl index 35b2b89b6..eedf8230e 100644 --- a/src/types/variabletimezone.jl +++ b/src/types/variabletimezone.jl @@ -5,13 +5,17 @@ end Base.isless(a::Transition, b::Transition) = isless(a.utc_datetime, b.utc_datetime) +# Ideally would always use ShortString63, but it's `hash` is broken on 32-bit systems. +# https://github.com/JuliaString/MurmurHash3.jl/issues/12 +const VariableZoneName = Int === Int64 ? ShortString63 : String + """ VariableTimeZone A `TimeZone` with an offset that changes over time. """ struct VariableTimeZone <: TimeZone - name::String + name::VariableZoneName transitions::Vector{Transition} cutoff::Union{DateTime,Nothing} diff --git a/src/types/zoneddatetime.jl b/src/types/zoneddatetime.jl index 2c832e8d9..378dd8cd6 100644 --- a/src/types/zoneddatetime.jl +++ b/src/types/zoneddatetime.jl @@ -6,22 +6,20 @@ using Dates: AbstractDateTime, argerror, validargs # A `DateTime` that includes `TimeZone` information. # """ -struct ZonedDateTime <: AbstractDateTime +struct ZonedDateTime{T<:TimeZone} <: AbstractDateTime 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 """ @@ -181,11 +179,11 @@ function Base.hash(zdt::ZonedDateTime, h::UInt) return h end -Base.typemin(::Type{ZonedDateTime}) = ZonedDateTime(typemin(DateTime), utc_tz; from_utc=true) -Base.typemax(::Type{ZonedDateTime}) = ZonedDateTime(typemax(DateTime), utc_tz; from_utc=true) +Base.typemin(::Type{<:ZonedDateTime}) = ZonedDateTime(typemin(DateTime), utc_tz; from_utc=true) +Base.typemax(::Type{<:ZonedDateTime}) = ZonedDateTime(typemax(DateTime), utc_tz; from_utc=true) # Note: The `validargs` function is as part of the Dates parsing interface. -function Dates.validargs(::Type{ZonedDateTime}, y::Int64, m::Union{Int64, Int32}, d::Int64, h::Int64, mi::Int64, s::Int64, ms::Int64, tz::AbstractString) +function Dates.validargs(::Type{<:ZonedDateTime}, y::Int64, m::Union{Int64, Int32}, d::Int64, h::Int64, mi::Int64, s::Int64, ms::Int64, tz::AbstractString) err = validargs(DateTime, y, Int64(m), d, h, mi, s, ms) err === nothing || return err istimezone(tz) || return argerror("TimeZone: \"$tz\" is not a recognized time zone") diff --git a/test/arithmetic.jl b/test/arithmetic.jl index 2c8f66aa7..13de497ae 100644 --- a/test/arithmetic.jl +++ b/test/arithmetic.jl @@ -55,7 +55,7 @@ spring_zdt = ZonedDateTime(spring, warsaw) # Arithmetic with a StepRange should always work even when the start/stop lands on # ambiguous or non-existent DateTimes. -@testset "StepRange{ZonedDateTime}" begin +@testset "StepRange{<:ZonedDateTime}" begin @testset "time-period" begin dt = DateTime(2015, 6, 1) @@ -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 @@ -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 diff --git a/test/io.jl b/test/io.jl index 7f8cda4b8..ef6d4a7a2 100644 --- a/test/io.jl +++ b/test/io.jl @@ -76,8 +76,7 @@ zdt = ZonedDateTime(dt, warsaw) zdt_vector = [zdt] @test sprint(show, MIME("text/plain"), zdt_vector) == summary(zdt_vector) * ":\n 1942-12-25T01:23:45+01:00" -prefix = VERSION >= v"1.5.0-DEV.224" ? "" : "ZonedDateTime" -@test sprint(show, zdt_vector; context=:compact => true) == "$prefix[ZonedDateTime(1942, 12, 25, 1, 23, 45, tz\"Europe/Warsaw\")]" +@test sprint(show, zdt_vector; context=:compact => true) == "ZonedDateTime{VariableTimeZone}[ZonedDateTime(1942, 12, 25, 1, 23, 45, tz\"Europe/Warsaw\")]" # TimeZone parsing diff --git a/test/types/fixedtimezone.jl b/test/types/fixedtimezone.jl index 94cae0ab4..774f67283 100644 --- a/test/types/fixedtimezone.jl +++ b/test/types/fixedtimezone.jl @@ -41,4 +41,14 @@ fixed_tz = FixedTimeZone("UTC") @test size(fixed_tz .== fixed_tz) == () end + + @testset "isbits" begin + # We are not using ShortStrings on 32-bit due to hash being broken on 32-bit. + # See https://github.com/JuliaString/MurmurHash3.jl/issues/12 + if Int === Int64 + @test isbits(FixedTimeZone("0123")) + else + @test_broken isbits(FixedTimeZone("0123")) + end + end end diff --git a/test/types/zoneddatetime.jl b/test/types/zoneddatetime.jl index e776e5bb9..24c9fbb01 100644 --- a/test/types/zoneddatetime.jl +++ b/test/types/zoneddatetime.jl @@ -430,4 +430,18 @@ using Dates: Hour, Second, UTM, @dateformat_str @test typemin(ZonedDateTime) <= ZonedDateTime(typemin(DateTime), utc) @test typemax(ZonedDateTime) >= ZonedDateTime(typemax(DateTime), utc) end + + @testset "isbits(::ZonedDateTime)" begin + # https://github.com/JuliaTime/TimeZones.jl/issues/271 + + @testset "typeof($zone)" for zone in (tz"America/Winnipeg", FixedTimeZone("0123")) + # We are not isbits until we fix the timezone to also be isbits + zdt = ZonedDateTime(Date(2000), zone) + if isbits(zone) + @test isbits(zdt) + else + @test_broken isbits(zdt) + end + end + end end