diff --git a/src/discovery.jl b/src/discovery.jl index ea81a49ce..a9e01cca1 100644 --- a/src/discovery.jl +++ b/src/discovery.jl @@ -243,7 +243,7 @@ function show_next_transition(io::IO, zdt::ZonedDateTime) println(io, "Transition Date: ", Dates.format(instant, dateformat"yyyy-mm-dd")) println(io, "Local Time Change: ", time_format(instant), " → ", time_format(to), " (", direction, ")") - println(io, "Offset Change: ", repr(from.zone.offset), " → ", repr(to.zone.offset)) + println(io, "Offset Change: ", repr("text/plain", from.zone.offset), " → ", repr("text/plain", to.zone.offset)) println(io, "Transition From: ", zdt_format(from)) println(io, "Transition To: ", zdt_format(to)) diff --git a/src/io.jl b/src/io.jl index 57c1f8120..bece818be 100644 --- a/src/io.jl +++ b/src/io.jl @@ -1,20 +1,8 @@ using Dates: value -Base.print(io::IO, tz::TimeZone) = print(io, tz.name) function Base.print(io::IO, tz::FixedTimeZone) - isempty(tz.name) ? print(io, "UTC", tz.offset) : print(io, tz.name) -end -Base.print(io::IO, zdt::ZonedDateTime) = print(io, localtime(zdt), zdt.zone.offset) - -function Base.show(io::IO, t::Transition) - print(io, t.utc_datetime, " ") - show(io, t.zone.offset) - !isempty(t.zone.name) && print(io, " (", t.zone.name, ")") -end - -function Base.show(io::IO, tz::FixedTimeZone) - if get(io, :compact, false) - print(io, tz) + if get(io, :compact, true) + isempty(tz.name) ? print(io, "UTC", tz.offset) : print(io, tz.name) else offset_str = "UTC" * offset_string(tz.offset, true) # Use ISO 8601 for comparision if isempty(tz.name) @@ -27,9 +15,9 @@ function Base.show(io::IO, tz::FixedTimeZone) end end -function Base.show(io::IO, tz::VariableTimeZone) - if get(io, :compact, false) - print(io, tz) +function Base.print(io::IO, tz::VariableTimeZone) + if get(io, :compact, true) + print(io, tz.name) else trans = tz.transitions @@ -56,4 +44,86 @@ function Base.show(io::IO, tz::VariableTimeZone) end end -Base.show(io::IO,dt::ZonedDateTime) = print(io, string(dt)) +function Base.print(io::IO, t::Transition) + print(io, t.utc_datetime, " ") + show(io, MIME("text/plain"), t.zone.offset) # Long-form + !isempty(t.zone.name) && print(io, " (", t.zone.name, ")") +end + +Base.print(io::IO, zdt::ZonedDateTime) = print(io, localtime(zdt), zdt.zone.offset) + + +function Base.show(io::IO, tz::FixedTimeZone) + if istimezone(tz.name, Class(:ALL)) && isequal(tz, TimeZone(tz.name, Class(:ALL))) + print(io, "tz\"$(tz.name)\"") + else + std = Dates.value(tz.offset.std) + dst = Dates.value(tz.offset.dst) + + params = [repr(tz.name), repr(std)] + dst != 0 && push!(params, repr(dst)) + print(io, FixedTimeZone, "(", join(params, ", "), ")") + end +end + +function Base.show(io::IO, tz::VariableTimeZone) + # Compat printing when the time zone can be constructed with `@tz_str` + if istimezone(tz.name, Class(:ALL)) && isequal(tz, TimeZone(tz.name, Class(:ALL))) + print(io, "tz\"$(tz.name)\"") + + # Compact printing of a custom time zone which is non-constructable + elseif get(io, :compact, false) + print(io, VariableTimeZone, "(") + show(io, tz.name) + print(io, ", ...)") + + # Verbose printing which should print a fully constructable `VariableTimeZone`. + else + # Force `:compact => false` to make the force the transition vector printing into + # long form. + print(io, VariableTimeZone, "(") + show(io, tz.name) + print(io, ", ") + show(IOContext(io, :compact => false), tz.transitions) + print(io, ", ") + show(io, tz.cutoff) + print(io, ")") + end +end + +function Base.show(io::IO, t::Transition) + # Note: Using combo of `:typeinfo` and `:limit` as a way of detecting when a vector of + # transitions is being printed in the REPL. + if get(io, :compact, false) || get(io, :typeinfo, Union{}) == Transition && get(io, :limit, false) + print(io, t) + else + # Fallback to calling the default show instead of reimplementing it. + invoke(show, Tuple{IO, Any}, io, t) + end +end + +function Base.show(io::IO, zdt::ZonedDateTime) + if get(io, :compact, false) + print(io, zdt) + else + values = [ + yearmonthday(zdt)... + hour(zdt) + minute(zdt) + second(zdt) + millisecond(zdt) + ] + index = something(findlast(!iszero, values), 1) + params = [ + map(repr, values[1:index]); + repr(timezone(zdt); context=:compact => true) + ] + + print(io, ZonedDateTime, "(", join(params, ", "), ")") + end +end + + +Base.show(io::IO, ::MIME"text/plain", t::Transition) = print(io, t) +Base.show(io::IO, ::MIME"text/plain", tz::TimeZone) = print(IOContext(io, :compact => false), tz) +Base.show(io::IO, ::MIME"text/plain", zdt::ZonedDateTime) = print(io, zdt) diff --git a/src/utcoffset.jl b/src/utcoffset.jl index caebdd339..0d310aaef 100644 --- a/src/utcoffset.jl +++ b/src/utcoffset.jl @@ -57,8 +57,18 @@ function offset_string(offset::UTCOffset, iso8601::Bool=false) end Base.print(io::IO, o::UTCOffset) = print(io, offset_string(o, true)) + function Base.show(io::IO, o::UTCOffset) - # Show DST as a separate offset since we want to distinguish between normal hourly - # daylight saving time offsets and exotic DST offsets (e.g. midsummer time). - print(io, "UTC", offset_string(o.std), "/", offset_string(o.dst)) + if get(io, :compact, false) + # Show DST as a separate offset since we want to distinguish between normal hourly + # daylight saving time offsets and exotic DST offsets (e.g. midsummer time). + print(io, "UTC", offset_string(o.std), "/", offset_string(o.dst)) + else + # Fallback to calling the default show instead of reimplementing it. + invoke(show, Tuple{IO, Any}, io, o) + end +end + +function Base.show(io::IO, ::MIME"text/plain", o::UTCOffset) + show(IOContext(io, :compact => true), o) end diff --git a/test/helpers.jl b/test/helpers.jl index 556e59367..e3a1b75b1 100644 --- a/test/helpers.jl +++ b/test/helpers.jl @@ -28,3 +28,14 @@ function ignore_output(body::Function; stdout::Bool=true, stderr::Bool=true) return result end + +# Used in tests as a shorter form of: `sprint(show, ..., context=:compact => true)` +show_compact = (io, args...) -> show(IOContext(io, :compact => true), args...) + +# Takes the tuple from `compile` and adds the result into TimeZones cache. Typically should +# not be used and only should be required if the test tzdata version and built tzdata +# version do not match. +function cache_tz((tz, class)::Tuple{TimeZone, TimeZones.Class}) + TimeZones.TIME_ZONE_CACHE[TimeZones.name(tz)] = (tz, class) + return tz +end diff --git a/test/io.jl b/test/io.jl index 1cd47416b..12325770d 100644 --- a/test/io.jl +++ b/test/io.jl @@ -1,52 +1,76 @@ using TimeZones.TZData: parse_components +using TimeZones: Transition +dt = DateTime(1942,12,25,1,23,45) +custom_dt = DateTime(1800,1,1) + +utc = FixedTimeZone("UTC") +gmt = FixedTimeZone("GMT", 0) +foo = FixedTimeZone("FOO", 0) null = FixedTimeZone("", 10800) fixed = FixedTimeZone("UTC+01:00") est = FixedTimeZone("EST", -18000) warsaw = first(compile("Europe/Warsaw", tzdata["europe"])) -apia = first(compile("Pacific/Apia", tzdata["australasia"])) -honolulu = first(compile("Pacific/Honolulu", tzdata["northamerica"])) # Uses cutoff +apia = cache_tz(compile("Pacific/Apia", tzdata["australasia"])) +honolulu = cache_tz(compile("Pacific/Honolulu", tzdata["northamerica"])) # Uses cutoff ulyanovsk = first(compile("Europe/Ulyanovsk", tzdata["europe"])) # No named abbreviations new_york = first(compile("America/New_York", tzdata["northamerica"])) # Underscore in name -dt = DateTime(1942,12,25,1,23,45) - -# TimeZones as a string -@test string(null) == "UTC+03:00" -@test string(fixed) == "UTC+01:00" -@test string(est) == "EST" -@test string(warsaw) == "Europe/Warsaw" -@test string(apia) == "Pacific/Apia" -@test string(honolulu) == "Pacific/Honolulu" -@test string(ulyanovsk) == "Europe/Ulyanovsk" - -# Alternatively in Julia 0.7.0-DEV.4517 we could use -# `sprint(show, ..., context=:compact => true)` -show_compact = (io, args...) -> show(IOContext(io, :compact => true), args...) -@test sprint(show_compact, null) == "UTC+03:00" -@test sprint(show_compact, fixed) == "UTC+01:00" -@test sprint(show_compact, est) == "EST" -@test sprint(show_compact, warsaw) == "Europe/Warsaw" -@test sprint(show_compact, apia) == "Pacific/Apia" -@test sprint(show_compact, honolulu) == "Pacific/Honolulu" -@test sprint(show_compact, ulyanovsk) == "Europe/Ulyanovsk" - -@test sprint(show, null) == "UTC+03:00" -@test sprint(show, fixed) == "UTC+01:00" -@test sprint(show, est) == "EST (UTC-5)" -@test sprint(show, warsaw) == "Europe/Warsaw (UTC+1/UTC+2)" -@test sprint(show, apia) == "Pacific/Apia (UTC+13/UTC+14)" -@test sprint(show, honolulu) == "Pacific/Honolulu (UTC-10)" -@test sprint(show, ulyanovsk) == "Europe/Ulyanovsk (UTC+4)" - -# UTC and GMT are special cases -@test sprint(show, FixedTimeZone("UTC")) == "UTC" -@test sprint(show, FixedTimeZone("GMT", 0)) == "GMT" -@test sprint(show, FixedTimeZone("FOO", 0)) == "FOO (UTC+0)" +custom = VariableTimeZone("Test/Custom", [Transition(custom_dt, utc)]) # Non-cached variable time zone + +@test sprint(print, utc) == "UTC" +@test sprint(print, gmt) == "GMT" +@test sprint(print, foo) == "FOO" +@test sprint(print, null) == "UTC+03:00" +@test sprint(print, fixed) == "UTC+01:00" +@test sprint(print, est) == "EST" +@test sprint(print, warsaw) == "Europe/Warsaw" +@test sprint(print, apia) == "Pacific/Apia" +@test sprint(print, honolulu) == "Pacific/Honolulu" +@test sprint(print, ulyanovsk) == "Europe/Ulyanovsk" +@test sprint(print, custom) == "Test/Custom" + +@test sprint(show_compact, utc) == "tz\"UTC\"" +@test sprint(show_compact, gmt) == "tz\"GMT\"" +@test sprint(show_compact, foo) == "FixedTimeZone(\"FOO\", 0)" +@test sprint(show_compact, null) == "FixedTimeZone(\"\", 10800)" +@test sprint(show_compact, fixed) == "tz\"UTC+01:00\"" +@test sprint(show_compact, est) == "tz\"EST\"" +@test sprint(show_compact, warsaw) == "tz\"Europe/Warsaw\"" +@test sprint(show_compact, apia) == "tz\"Pacific/Apia\"" +@test sprint(show_compact, honolulu) == "tz\"Pacific/Honolulu\"" +@test sprint(show_compact, ulyanovsk) == "tz\"Europe/Ulyanovsk\"" +@test sprint(show_compact, custom) == "VariableTimeZone(\"Test/Custom\", ...)" + +@test sprint(show, utc) == "tz\"UTC\"" +@test sprint(show, gmt) == "tz\"GMT\"" +@test sprint(show, foo) == "FixedTimeZone(\"FOO\", 0)" +@test sprint(show, null) == "FixedTimeZone(\"\", 10800)" +@test sprint(show, fixed) == "tz\"UTC+01:00\"" +@test sprint(show, est) == "tz\"EST\"" +@test sprint(show, warsaw) == "tz\"Europe/Warsaw\"" +@test sprint(show, apia) == "tz\"Pacific/Apia\"" +@test sprint(show, honolulu) == "tz\"Pacific/Honolulu\"" +@test sprint(show, ulyanovsk) == "tz\"Europe/Ulyanovsk\"" +@test sprint(show, custom) == "VariableTimeZone(\"Test/Custom\", Transition[Transition($(repr(custom_dt)), tz\"UTC\")], nothing)" + +@test sprint(show, MIME("text/plain"), utc) == "UTC" +@test sprint(show, MIME("text/plain"), gmt) == "GMT" +@test sprint(show, MIME("text/plain"), foo) == "FOO (UTC+0)" +@test sprint(show, MIME("text/plain"), null) == "UTC+03:00" +@test sprint(show, MIME("text/plain"), fixed) == "UTC+01:00" +@test sprint(show, MIME("text/plain"), est) == "EST (UTC-5)" +@test sprint(show, MIME("text/plain"), warsaw) == "Europe/Warsaw (UTC+1/UTC+2)" +@test sprint(show, MIME("text/plain"), apia) == "Pacific/Apia (UTC+13/UTC+14)" +@test sprint(show, MIME("text/plain"), honolulu) == "Pacific/Honolulu (UTC-10)" +@test sprint(show, MIME("text/plain"), ulyanovsk) == "Europe/Ulyanovsk (UTC+4)" +@test sprint(show, MIME("text/plain"), custom) == "Test/Custom (UTC+0)" # ZonedDateTime as a string zdt = ZonedDateTime(dt, warsaw) @test string(zdt) == "1942-12-25T01:23:45+01:00" -@test sprint(show, zdt) == "1942-12-25T01:23:45+01:00" +@test sprint(show_compact, zdt) == "1942-12-25T01:23:45+01:00" +@test sprint(show, zdt) == "ZonedDateTime(1942, 12, 25, 1, 23, 45, tz\"Europe/Warsaw\")" +@test sprint(show, MIME("text/plain"), zdt) == "1942-12-25T01:23:45+01:00" # TimeZone parsing @@ -105,3 +129,38 @@ f = "yyyy/m/d H:M:S zzz" df = Dates.DateFormat("yyyy-mm-ddTHH:MM:SS ZZZ") zdt = ZonedDateTime(dt, warsaw) @test_throws ArgumentError parse(ZonedDateTime, Dates.format(zdt, df), df) + + +# Displaying VariableTimeZone's vector of transitions on the REPL has a special printing +@testset "Transitions I/O" begin + transitions = honolulu.transitions # Only contains a few transitions + t = transitions[2] # 1896-01-13T22:31:26 UTC-10:30/+0 (HST) + + @testset "basic" begin + @test sprint(print, t) == "1896-01-13T22:31:26 UTC-10:30/+0 (HST)" + @test sprint(show_compact, t) == "1896-01-13T22:31:26 UTC-10:30/+0 (HST)" + @test sprint(show, t) == "Transition($(repr(t.utc_datetime)), FixedTimeZone(\"HST\", -37800))" + @test sprint(show, MIME("text/plain"), t) == "1896-01-13T22:31:26 UTC-10:30/+0 (HST)" + end + + @testset "REPL vector" begin + expected_full = string( + TimeZones.Transition, + "[", + join(map(t -> sprint(show, t, context=:compact => false), transitions), ", "), + "]", + ) + + # Note: The output here is different from the interactive REPL but is representative + # of the output. + expected_repl = string( + TimeZones.Transition, + "[", + join(map(t -> sprint(show, t, context=:compact => true), transitions), ", "), + "]", + ) + + @test sprint(show, transitions, context=:compact => false) == expected_full + @test sprint(show, transitions; context=:limit => true) == expected_repl + end +end diff --git a/test/utcoffset.jl b/test/utcoffset.jl index 7945be5e5..8de39fece 100644 --- a/test/utcoffset.jl +++ b/test/utcoffset.jl @@ -42,5 +42,17 @@ let a = UTCOffset(7200, 3600), b = UTCOffset(3600, 7200) @test isequal(a, b) end -@test sprint(show, UTCOffset(0, 0)) == "UTC+0/+0" -@test sprint(show, UTCOffset(3600, 7200)) == "UTC+1/+2" +@test sprint(show_compact, UTCOffset(0, 0)) == "UTC+0/+0" +@test sprint(show_compact, UTCOffset(3600, 7200)) == "UTC+1/+2" + +# https://github.com/JuliaLang/julia/pull/30817 +if VERSION >= v"1.2.0-DEV.223" + @test sprint(show, UTCOffset(0, 0)) == "UTCOffset(Second(0), Second(0))" + @test sprint(show, UTCOffset(3600, 7200)) == "UTCOffset(Second(3600), Second(7200))" +else + @test sprint(show, UTCOffset(0, 0)) == "UTCOffset(0 seconds, 0 seconds)" + @test sprint(show, UTCOffset(3600, 7200)) == "UTCOffset(3600 seconds, 7200 seconds)" +end + +@test sprint(show, MIME("text/plain"), UTCOffset(0, 0)) == "UTC+0/+0" +@test sprint(show, MIME("text/plain"), UTCOffset(3600, 7200)) == "UTC+1/+2"