Skip to content

Commit

Permalink
Overhaul printing of types (#194)
Browse files Browse the repository at this point in the history
* Overhaul printing of types

Related to these Dates stdlib changes:

- JuliaLang/julia#30200
- JuliaLang/julia#30817

* Review comments
  • Loading branch information
omus authored Mar 29, 2019
1 parent 870702b commit c57e8e3
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 61 deletions.
2 changes: 1 addition & 1 deletion src/discovery.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
106 changes: 88 additions & 18 deletions src/io.jl
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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

Expand All @@ -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)
16 changes: 13 additions & 3 deletions src/utcoffset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 11 additions & 0 deletions test/helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
133 changes: 96 additions & 37 deletions test/io.jl
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
16 changes: 14 additions & 2 deletions test/utcoffset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"

0 comments on commit c57e8e3

Please sign in to comment.