Skip to content

Commit

Permalink
Address corner cases in next_transition_instant (JuliaTime#192)
Browse files Browse the repository at this point in the history
No longer fails with `FixedTimeZone`s or instants where no future
transition exists.
  • Loading branch information
omus authored and kpamnany committed May 5, 2023
1 parent 8d3203f commit 1ccf384
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 3 deletions.
21 changes: 18 additions & 3 deletions src/discovery.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,12 @@ end


"""
next_transition_instant(zdt::ZonedDateTime) -> ZonedDateTime
next_transition_instant(tz::TimeZone=localzone()) -> ZonedDateTime
next_transition_instant(zdt::ZonedDateTime) -> Union{ZonedDateTime, Nothing}
next_transition_instant(tz::TimeZone=localzone()) -> Union{ZonedDateTime, Nothing}
Determine the next instant at which a time zone transition occurs (typically
due to daylight-savings time).
due to daylight-savings time). If no there exists no future transition then `nothing` will
be returned.
Note that the provided `ZonedDateTime` isn't normally constructable:
Expand All @@ -153,13 +154,16 @@ next_transition_instant

function next_transition_instant(zdt::ZonedDateTime)
tz = zdt.timezone
tz isa VariableTimeZone || return nothing

# Determine the index of the transition which occurs after the UTC datetime specified
index = searchsortedfirst(
tz.transitions, TimeZones.utc(zdt),
by=el -> isa(el, TimeZones.Transition) ? el.utc_datetime : el,
)

index <= length(tz.transitions) || 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
# constructors. The instant constructed is equivalent to the first instant after the
Expand Down Expand Up @@ -208,7 +212,18 @@ Transition To: 2011-12-31T00:00:00.000+14:00
show_next_transition

function show_next_transition(io::IO, zdt::ZonedDateTime)
if timezone(zdt) isa FixedTimeZone
@warn "No transitions exist in time zone $(timezone(zdt))"
return
end

instant = next_transition_instant(zdt)

if instant === nothing
@warn "No transition exists in $(timezone(zdt)) after: $zdt"
return
end

epsilon = eps(instant)
from, to = instant - epsilon, instant + zero(epsilon)
direction = value(to.zone.offset - from.zone.offset) < 0 ? "Backward" : "Forward"
Expand Down
31 changes: 31 additions & 0 deletions test/discovery.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ paris = first(compile("Europe/Paris", tzdata["europe"]))
end
end
end

@testset "fixed time zone" begin
@test next_transition_instant(ZonedDateTime(2018, 1, 1, tz"UTC")) === nothing
end

@testset "no future transition" begin
# Determine the last transition instant for the time zone
t = wpg.transitions[end]
last_trans_instant = ZonedDateTime(t.utc_datetime, wpg, t.zone)

@test next_transition_instant(last_trans_instant) !== nothing
@test next_transition_instant(last_trans_instant + Millisecond(1)) === nothing
end
end

@testset "show_next_transition" begin
Expand Down Expand Up @@ -123,4 +136,22 @@ end
end
end
end

@testset "fixed time zone" begin
msg = "No transitions exist in time zone UTC"
instant = ZonedDateTime(2019 ,1, 1, tz"UTC")

io = IOBuffer()
@test_logs (:warn, msg) show_next_transition(io, instant)
@test isempty(read(seekstart(io), String))
end

@testset "no future transition" begin
msg = "No transition exists in America/Winnipeg after: 2038-03-14T01:59:59.999-06:00"
instant = ZonedDateTime(wpg.cutoff - Millisecond(1), wpg; from_utc=true)

io = IOBuffer()
@test_logs (:warn, msg) show_next_transition(io, instant)
@test isempty(read(seekstart(io), String))
end
end

0 comments on commit 1ccf384

Please sign in to comment.