Skip to content

Commit c57e8e3

Browse files
authored
Overhaul printing of types (#194)
* Overhaul printing of types Related to these Dates stdlib changes: - JuliaLang/julia#30200 - JuliaLang/julia#30817 * Review comments
1 parent 870702b commit c57e8e3

File tree

6 files changed

+223
-61
lines changed

6 files changed

+223
-61
lines changed

src/discovery.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ function show_next_transition(io::IO, zdt::ZonedDateTime)
243243

244244
println(io, "Transition Date: ", Dates.format(instant, dateformat"yyyy-mm-dd"))
245245
println(io, "Local Time Change: ", time_format(instant), "", time_format(to), " (", direction, ")")
246-
println(io, "Offset Change: ", repr(from.zone.offset), "", repr(to.zone.offset))
246+
println(io, "Offset Change: ", repr("text/plain", from.zone.offset), "", repr("text/plain", to.zone.offset))
247247
println(io, "Transition From: ", zdt_format(from))
248248
println(io, "Transition To: ", zdt_format(to))
249249

src/io.jl

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
using Dates: value
22

3-
Base.print(io::IO, tz::TimeZone) = print(io, tz.name)
43
function Base.print(io::IO, tz::FixedTimeZone)
5-
isempty(tz.name) ? print(io, "UTC", tz.offset) : print(io, tz.name)
6-
end
7-
Base.print(io::IO, zdt::ZonedDateTime) = print(io, localtime(zdt), zdt.zone.offset)
8-
9-
function Base.show(io::IO, t::Transition)
10-
print(io, t.utc_datetime, " ")
11-
show(io, t.zone.offset)
12-
!isempty(t.zone.name) && print(io, " (", t.zone.name, ")")
13-
end
14-
15-
function Base.show(io::IO, tz::FixedTimeZone)
16-
if get(io, :compact, false)
17-
print(io, tz)
4+
if get(io, :compact, true)
5+
isempty(tz.name) ? print(io, "UTC", tz.offset) : print(io, tz.name)
186
else
197
offset_str = "UTC" * offset_string(tz.offset, true) # Use ISO 8601 for comparision
208
if isempty(tz.name)
@@ -27,9 +15,9 @@ function Base.show(io::IO, tz::FixedTimeZone)
2715
end
2816
end
2917

30-
function Base.show(io::IO, tz::VariableTimeZone)
31-
if get(io, :compact, false)
32-
print(io, tz)
18+
function Base.print(io::IO, tz::VariableTimeZone)
19+
if get(io, :compact, true)
20+
print(io, tz.name)
3321
else
3422
trans = tz.transitions
3523

@@ -56,4 +44,86 @@ function Base.show(io::IO, tz::VariableTimeZone)
5644
end
5745
end
5846

59-
Base.show(io::IO,dt::ZonedDateTime) = print(io, string(dt))
47+
function Base.print(io::IO, t::Transition)
48+
print(io, t.utc_datetime, " ")
49+
show(io, MIME("text/plain"), t.zone.offset) # Long-form
50+
!isempty(t.zone.name) && print(io, " (", t.zone.name, ")")
51+
end
52+
53+
Base.print(io::IO, zdt::ZonedDateTime) = print(io, localtime(zdt), zdt.zone.offset)
54+
55+
56+
function Base.show(io::IO, tz::FixedTimeZone)
57+
if istimezone(tz.name, Class(:ALL)) && isequal(tz, TimeZone(tz.name, Class(:ALL)))
58+
print(io, "tz\"$(tz.name)\"")
59+
else
60+
std = Dates.value(tz.offset.std)
61+
dst = Dates.value(tz.offset.dst)
62+
63+
params = [repr(tz.name), repr(std)]
64+
dst != 0 && push!(params, repr(dst))
65+
print(io, FixedTimeZone, "(", join(params, ", "), ")")
66+
end
67+
end
68+
69+
function Base.show(io::IO, tz::VariableTimeZone)
70+
# Compat printing when the time zone can be constructed with `@tz_str`
71+
if istimezone(tz.name, Class(:ALL)) && isequal(tz, TimeZone(tz.name, Class(:ALL)))
72+
print(io, "tz\"$(tz.name)\"")
73+
74+
# Compact printing of a custom time zone which is non-constructable
75+
elseif get(io, :compact, false)
76+
print(io, VariableTimeZone, "(")
77+
show(io, tz.name)
78+
print(io, ", ...)")
79+
80+
# Verbose printing which should print a fully constructable `VariableTimeZone`.
81+
else
82+
# Force `:compact => false` to make the force the transition vector printing into
83+
# long form.
84+
print(io, VariableTimeZone, "(")
85+
show(io, tz.name)
86+
print(io, ", ")
87+
show(IOContext(io, :compact => false), tz.transitions)
88+
print(io, ", ")
89+
show(io, tz.cutoff)
90+
print(io, ")")
91+
end
92+
end
93+
94+
function Base.show(io::IO, t::Transition)
95+
# Note: Using combo of `:typeinfo` and `:limit` as a way of detecting when a vector of
96+
# transitions is being printed in the REPL.
97+
if get(io, :compact, false) || get(io, :typeinfo, Union{}) == Transition && get(io, :limit, false)
98+
print(io, t)
99+
else
100+
# Fallback to calling the default show instead of reimplementing it.
101+
invoke(show, Tuple{IO, Any}, io, t)
102+
end
103+
end
104+
105+
function Base.show(io::IO, zdt::ZonedDateTime)
106+
if get(io, :compact, false)
107+
print(io, zdt)
108+
else
109+
values = [
110+
yearmonthday(zdt)...
111+
hour(zdt)
112+
minute(zdt)
113+
second(zdt)
114+
millisecond(zdt)
115+
]
116+
index = something(findlast(!iszero, values), 1)
117+
params = [
118+
map(repr, values[1:index]);
119+
repr(timezone(zdt); context=:compact => true)
120+
]
121+
122+
print(io, ZonedDateTime, "(", join(params, ", "), ")")
123+
end
124+
end
125+
126+
127+
Base.show(io::IO, ::MIME"text/plain", t::Transition) = print(io, t)
128+
Base.show(io::IO, ::MIME"text/plain", tz::TimeZone) = print(IOContext(io, :compact => false), tz)
129+
Base.show(io::IO, ::MIME"text/plain", zdt::ZonedDateTime) = print(io, zdt)

src/utcoffset.jl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,18 @@ function offset_string(offset::UTCOffset, iso8601::Bool=false)
5757
end
5858

5959
Base.print(io::IO, o::UTCOffset) = print(io, offset_string(o, true))
60+
6061
function Base.show(io::IO, o::UTCOffset)
61-
# Show DST as a separate offset since we want to distinguish between normal hourly
62-
# daylight saving time offsets and exotic DST offsets (e.g. midsummer time).
63-
print(io, "UTC", offset_string(o.std), "/", offset_string(o.dst))
62+
if get(io, :compact, false)
63+
# Show DST as a separate offset since we want to distinguish between normal hourly
64+
# daylight saving time offsets and exotic DST offsets (e.g. midsummer time).
65+
print(io, "UTC", offset_string(o.std), "/", offset_string(o.dst))
66+
else
67+
# Fallback to calling the default show instead of reimplementing it.
68+
invoke(show, Tuple{IO, Any}, io, o)
69+
end
70+
end
71+
72+
function Base.show(io::IO, ::MIME"text/plain", o::UTCOffset)
73+
show(IOContext(io, :compact => true), o)
6474
end

test/helpers.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,14 @@ function ignore_output(body::Function; stdout::Bool=true, stderr::Bool=true)
2828

2929
return result
3030
end
31+
32+
# Used in tests as a shorter form of: `sprint(show, ..., context=:compact => true)`
33+
show_compact = (io, args...) -> show(IOContext(io, :compact => true), args...)
34+
35+
# Takes the tuple from `compile` and adds the result into TimeZones cache. Typically should
36+
# not be used and only should be required if the test tzdata version and built tzdata
37+
# version do not match.
38+
function cache_tz((tz, class)::Tuple{TimeZone, TimeZones.Class})
39+
TimeZones.TIME_ZONE_CACHE[TimeZones.name(tz)] = (tz, class)
40+
return tz
41+
end

test/io.jl

Lines changed: 96 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,76 @@
11
using TimeZones.TZData: parse_components
2+
using TimeZones: Transition
23

4+
dt = DateTime(1942,12,25,1,23,45)
5+
custom_dt = DateTime(1800,1,1)
6+
7+
utc = FixedTimeZone("UTC")
8+
gmt = FixedTimeZone("GMT", 0)
9+
foo = FixedTimeZone("FOO", 0)
310
null = FixedTimeZone("", 10800)
411
fixed = FixedTimeZone("UTC+01:00")
512
est = FixedTimeZone("EST", -18000)
613
warsaw = first(compile("Europe/Warsaw", tzdata["europe"]))
7-
apia = first(compile("Pacific/Apia", tzdata["australasia"]))
8-
honolulu = first(compile("Pacific/Honolulu", tzdata["northamerica"])) # Uses cutoff
14+
apia = cache_tz(compile("Pacific/Apia", tzdata["australasia"]))
15+
honolulu = cache_tz(compile("Pacific/Honolulu", tzdata["northamerica"])) # Uses cutoff
916
ulyanovsk = first(compile("Europe/Ulyanovsk", tzdata["europe"])) # No named abbreviations
1017
new_york = first(compile("America/New_York", tzdata["northamerica"])) # Underscore in name
11-
dt = DateTime(1942,12,25,1,23,45)
12-
13-
# TimeZones as a string
14-
@test string(null) == "UTC+03:00"
15-
@test string(fixed) == "UTC+01:00"
16-
@test string(est) == "EST"
17-
@test string(warsaw) == "Europe/Warsaw"
18-
@test string(apia) == "Pacific/Apia"
19-
@test string(honolulu) == "Pacific/Honolulu"
20-
@test string(ulyanovsk) == "Europe/Ulyanovsk"
21-
22-
# Alternatively in Julia 0.7.0-DEV.4517 we could use
23-
# `sprint(show, ..., context=:compact => true)`
24-
show_compact = (io, args...) -> show(IOContext(io, :compact => true), args...)
25-
@test sprint(show_compact, null) == "UTC+03:00"
26-
@test sprint(show_compact, fixed) == "UTC+01:00"
27-
@test sprint(show_compact, est) == "EST"
28-
@test sprint(show_compact, warsaw) == "Europe/Warsaw"
29-
@test sprint(show_compact, apia) == "Pacific/Apia"
30-
@test sprint(show_compact, honolulu) == "Pacific/Honolulu"
31-
@test sprint(show_compact, ulyanovsk) == "Europe/Ulyanovsk"
32-
33-
@test sprint(show, null) == "UTC+03:00"
34-
@test sprint(show, fixed) == "UTC+01:00"
35-
@test sprint(show, est) == "EST (UTC-5)"
36-
@test sprint(show, warsaw) == "Europe/Warsaw (UTC+1/UTC+2)"
37-
@test sprint(show, apia) == "Pacific/Apia (UTC+13/UTC+14)"
38-
@test sprint(show, honolulu) == "Pacific/Honolulu (UTC-10)"
39-
@test sprint(show, ulyanovsk) == "Europe/Ulyanovsk (UTC+4)"
40-
41-
# UTC and GMT are special cases
42-
@test sprint(show, FixedTimeZone("UTC")) == "UTC"
43-
@test sprint(show, FixedTimeZone("GMT", 0)) == "GMT"
44-
@test sprint(show, FixedTimeZone("FOO", 0)) == "FOO (UTC+0)"
18+
custom = VariableTimeZone("Test/Custom", [Transition(custom_dt, utc)]) # Non-cached variable time zone
19+
20+
@test sprint(print, utc) == "UTC"
21+
@test sprint(print, gmt) == "GMT"
22+
@test sprint(print, foo) == "FOO"
23+
@test sprint(print, null) == "UTC+03:00"
24+
@test sprint(print, fixed) == "UTC+01:00"
25+
@test sprint(print, est) == "EST"
26+
@test sprint(print, warsaw) == "Europe/Warsaw"
27+
@test sprint(print, apia) == "Pacific/Apia"
28+
@test sprint(print, honolulu) == "Pacific/Honolulu"
29+
@test sprint(print, ulyanovsk) == "Europe/Ulyanovsk"
30+
@test sprint(print, custom) == "Test/Custom"
31+
32+
@test sprint(show_compact, utc) == "tz\"UTC\""
33+
@test sprint(show_compact, gmt) == "tz\"GMT\""
34+
@test sprint(show_compact, foo) == "FixedTimeZone(\"FOO\", 0)"
35+
@test sprint(show_compact, null) == "FixedTimeZone(\"\", 10800)"
36+
@test sprint(show_compact, fixed) == "tz\"UTC+01:00\""
37+
@test sprint(show_compact, est) == "tz\"EST\""
38+
@test sprint(show_compact, warsaw) == "tz\"Europe/Warsaw\""
39+
@test sprint(show_compact, apia) == "tz\"Pacific/Apia\""
40+
@test sprint(show_compact, honolulu) == "tz\"Pacific/Honolulu\""
41+
@test sprint(show_compact, ulyanovsk) == "tz\"Europe/Ulyanovsk\""
42+
@test sprint(show_compact, custom) == "VariableTimeZone(\"Test/Custom\", ...)"
43+
44+
@test sprint(show, utc) == "tz\"UTC\""
45+
@test sprint(show, gmt) == "tz\"GMT\""
46+
@test sprint(show, foo) == "FixedTimeZone(\"FOO\", 0)"
47+
@test sprint(show, null) == "FixedTimeZone(\"\", 10800)"
48+
@test sprint(show, fixed) == "tz\"UTC+01:00\""
49+
@test sprint(show, est) == "tz\"EST\""
50+
@test sprint(show, warsaw) == "tz\"Europe/Warsaw\""
51+
@test sprint(show, apia) == "tz\"Pacific/Apia\""
52+
@test sprint(show, honolulu) == "tz\"Pacific/Honolulu\""
53+
@test sprint(show, ulyanovsk) == "tz\"Europe/Ulyanovsk\""
54+
@test sprint(show, custom) == "VariableTimeZone(\"Test/Custom\", Transition[Transition($(repr(custom_dt)), tz\"UTC\")], nothing)"
55+
56+
@test sprint(show, MIME("text/plain"), utc) == "UTC"
57+
@test sprint(show, MIME("text/plain"), gmt) == "GMT"
58+
@test sprint(show, MIME("text/plain"), foo) == "FOO (UTC+0)"
59+
@test sprint(show, MIME("text/plain"), null) == "UTC+03:00"
60+
@test sprint(show, MIME("text/plain"), fixed) == "UTC+01:00"
61+
@test sprint(show, MIME("text/plain"), est) == "EST (UTC-5)"
62+
@test sprint(show, MIME("text/plain"), warsaw) == "Europe/Warsaw (UTC+1/UTC+2)"
63+
@test sprint(show, MIME("text/plain"), apia) == "Pacific/Apia (UTC+13/UTC+14)"
64+
@test sprint(show, MIME("text/plain"), honolulu) == "Pacific/Honolulu (UTC-10)"
65+
@test sprint(show, MIME("text/plain"), ulyanovsk) == "Europe/Ulyanovsk (UTC+4)"
66+
@test sprint(show, MIME("text/plain"), custom) == "Test/Custom (UTC+0)"
4567

4668
# ZonedDateTime as a string
4769
zdt = ZonedDateTime(dt, warsaw)
4870
@test string(zdt) == "1942-12-25T01:23:45+01:00"
49-
@test sprint(show, zdt) == "1942-12-25T01:23:45+01:00"
71+
@test sprint(show_compact, zdt) == "1942-12-25T01:23:45+01:00"
72+
@test sprint(show, zdt) == "ZonedDateTime(1942, 12, 25, 1, 23, 45, tz\"Europe/Warsaw\")"
73+
@test sprint(show, MIME("text/plain"), zdt) == "1942-12-25T01:23:45+01:00"
5074

5175

5276
# TimeZone parsing
@@ -105,3 +129,38 @@ f = "yyyy/m/d H:M:S zzz"
105129
df = Dates.DateFormat("yyyy-mm-ddTHH:MM:SS ZZZ")
106130
zdt = ZonedDateTime(dt, warsaw)
107131
@test_throws ArgumentError parse(ZonedDateTime, Dates.format(zdt, df), df)
132+
133+
134+
# Displaying VariableTimeZone's vector of transitions on the REPL has a special printing
135+
@testset "Transitions I/O" begin
136+
transitions = honolulu.transitions # Only contains a few transitions
137+
t = transitions[2] # 1896-01-13T22:31:26 UTC-10:30/+0 (HST)
138+
139+
@testset "basic" begin
140+
@test sprint(print, t) == "1896-01-13T22:31:26 UTC-10:30/+0 (HST)"
141+
@test sprint(show_compact, t) == "1896-01-13T22:31:26 UTC-10:30/+0 (HST)"
142+
@test sprint(show, t) == "Transition($(repr(t.utc_datetime)), FixedTimeZone(\"HST\", -37800))"
143+
@test sprint(show, MIME("text/plain"), t) == "1896-01-13T22:31:26 UTC-10:30/+0 (HST)"
144+
end
145+
146+
@testset "REPL vector" begin
147+
expected_full = string(
148+
TimeZones.Transition,
149+
"[",
150+
join(map(t -> sprint(show, t, context=:compact => false), transitions), ", "),
151+
"]",
152+
)
153+
154+
# Note: The output here is different from the interactive REPL but is representative
155+
# of the output.
156+
expected_repl = string(
157+
TimeZones.Transition,
158+
"[",
159+
join(map(t -> sprint(show, t, context=:compact => true), transitions), ", "),
160+
"]",
161+
)
162+
163+
@test sprint(show, transitions, context=:compact => false) == expected_full
164+
@test sprint(show, transitions; context=:limit => true) == expected_repl
165+
end
166+
end

test/utcoffset.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,17 @@ let a = UTCOffset(7200, 3600), b = UTCOffset(3600, 7200)
4242
@test isequal(a, b)
4343
end
4444

45-
@test sprint(show, UTCOffset(0, 0)) == "UTC+0/+0"
46-
@test sprint(show, UTCOffset(3600, 7200)) == "UTC+1/+2"
45+
@test sprint(show_compact, UTCOffset(0, 0)) == "UTC+0/+0"
46+
@test sprint(show_compact, UTCOffset(3600, 7200)) == "UTC+1/+2"
47+
48+
# https://github.com/JuliaLang/julia/pull/30817
49+
if VERSION >= v"1.2.0-DEV.223"
50+
@test sprint(show, UTCOffset(0, 0)) == "UTCOffset(Second(0), Second(0))"
51+
@test sprint(show, UTCOffset(3600, 7200)) == "UTCOffset(Second(3600), Second(7200))"
52+
else
53+
@test sprint(show, UTCOffset(0, 0)) == "UTCOffset(0 seconds, 0 seconds)"
54+
@test sprint(show, UTCOffset(3600, 7200)) == "UTCOffset(3600 seconds, 7200 seconds)"
55+
end
56+
57+
@test sprint(show, MIME("text/plain"), UTCOffset(0, 0)) == "UTC+0/+0"
58+
@test sprint(show, MIME("text/plain"), UTCOffset(3600, 7200)) == "UTC+1/+2"

0 commit comments

Comments
 (0)