Skip to content

Commit 892bc01

Browse files
author
Curtis Vogt
committed
Moved Time type to separate file so that types could access extremes.
1 parent 8eb178f commit 892bc01

File tree

7 files changed

+139
-133
lines changed

7 files changed

+139
-133
lines changed

src/TimeZones.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
module TimeZones
22

3+
using Base.Dates
4+
35
export TimeZone, FixedTimeZone, VariableTimeZone, ZonedDateTime,
46
AmbiguousTimeError, NonExistentTimeError, DateTime
57

68
const PKG_DIR = normpath(joinpath(dirname(@__FILE__), "..", "deps"))
79
const TZDATA_DIR = joinpath(PKG_DIR, "tzdata")
810
const COMPILED_DIR = joinpath(PKG_DIR, "compiled")
911

12+
include("timezones/time.jl")
1013
include("timezones/types.jl")
1114
include("timezones/accessors.jl")
1215
include("timezones/arithmetic.jl")

src/timezones/Olsen.jl

Lines changed: 9 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,16 @@
11
module Olsen
22

33
using Base.Dates
4-
import Base.Dates: value, toms
54

6-
import ..TimeZones: TZDATA_DIR, COMPILED_DIR
7-
import ..TimeZones: TimeZone, FixedTimeZone, VariableTimeZone, Transition
5+
import ..TimeZones: TZDATA_DIR, COMPILED_DIR, ZERO, MIN_GMT_OFFSET, MAX_GMT_OFFSET,
6+
MIN_SAVE, MAX_SAVE, ABS_DIFF_OFFSET, Time, toseconds
7+
import ..TimeZones: TimeZone, FixedTimeZone, VariableTimeZone, Transition, Time
88

99
const REGIONS = (
1010
"africa", "antarctica", "asia", "australasia",
1111
"europe", "northamerica", "southamerica",
1212
)
1313

14-
# Convenience type for working with HH:MM:SS.
15-
immutable Time <: TimePeriod
16-
seconds::Int
17-
end
18-
const ZERO = Time(0)
19-
20-
function Time(hour::Int, minute::Int, second::Int)
21-
Time(hour * 3600 + minute * 60 + second)
22-
end
23-
24-
function Time(s::String)
25-
# "-" represents 0:00 for some DST rules
26-
s == "-" && return ZERO
27-
parsed = map(n -> parse(Int, n), split(s, ':'))
28-
29-
# Only can handle up to hour, minute, second.
30-
length(parsed) > 3 && error("Invalid Time string")
31-
any(parsed[2:end] .< 0) && error("Invalid Time string")
32-
33-
# Handle variations where minutes and seconds may be excluded.
34-
values = [0,0,0]
35-
values[1:length(parsed)] = parsed
36-
37-
if values[1] < 0
38-
for i in 2:length(values)
39-
values[i] = -values[i]
40-
end
41-
end
42-
43-
return Time(values...)
44-
end
45-
46-
# TimePeriod methods
47-
value(t::Time) = t.seconds
48-
toms(t::Time) = t.seconds * 1000
49-
50-
toseconds(t::Time) = t.seconds
51-
hour(t::Time) = div(toseconds(t), 3600)
52-
minute(t::Time) = rem(div(toseconds(t), 60), 60)
53-
second(t::Time) = rem(toseconds(t), 60)
54-
55-
function hourminutesecond(t::Time)
56-
h, r = divrem(toseconds(t), 3600)
57-
m, s = divrem(r, 60)
58-
return h, m, s
59-
end
60-
61-
Base.convert(::Type{Second}, t::Time) = Second(toseconds(t))
62-
Base.convert(::Type{Millisecond}, t::Time) = Millisecond(toseconds(t) * 1000)
63-
Base.promote_rule{P<:Union{Week,Day,Hour,Minute,Second}}(::Type{P}, ::Type{Time}) = Second
64-
Base.promote_rule(::Type{Millisecond}, ::Type{Time}) = Millisecond
65-
66-
# Should be defined in Base.Dates
67-
Base.isless(x::Period, y::Period) = isless(promote(x,y)...)
68-
69-
# https://en.wikipedia.org/wiki/ISO_8601#Times
70-
function Base.string(t::Time)
71-
neg = toseconds(t) < 0 ? "-" : ""
72-
h, m, s = map(abs, hourminutesecond(t))
73-
@sprintf("%s%02d:%02d:%02d", neg, h, m, s)
74-
end
75-
76-
Base.show(io::IO, t::Time) = print(io, string(t))
77-
78-
# min/max offsets across all zones and all time.
79-
const MINOFFSET = Time("-15:56:00") # Asia/Manilla
80-
const MAXOFFSET = Time("15:13:42") # America/Metlakatla
81-
82-
# min/max save across all zones/rules and all time.
83-
const MINSAVE = Time("00:00")
84-
const MAXSAVE = Time("02:00") # France, Germany, Port, Spain
85-
86-
const MAXABSDIFF = abs((MAXOFFSET + MAXSAVE) - (MINOFFSET + MINSAVE))
87-
8814
# Zone type maps to an Olsen Timezone database entity
8915
type Zone
9016
gmtoffset::Time
@@ -101,7 +27,7 @@ function Base.isless(x::Zone,y::Zone)
10127

10228
# Easy to compare until's if they are using the same flag. Alternatively, it should
10329
# be safe to compare different until flags if the DateTimes are far enough apart.
104-
if x.until_flag == y.until_flag || abs(x_dt - y_dt) > MAXABSDIFF
30+
if x.until_flag == y.until_flag || abs(x_dt - y_dt) > ABS_DIFF_OFFSET
10531
return isless(x_dt, y_dt)
10632
else
10733
error("Unable to compare zones when until datetimes are too close and flags are mixed")
@@ -249,8 +175,8 @@ function ruleparse(from, to, rule_type, month, on, at, save, letter)
249175
letter = letter == "-" ? "" : letter
250176

251177
# Report unexpected save values that could cause issues during resolve.
252-
save_hm < MINSAVE && warn("Discovered save $save_hm less than the expected min $MINSAVE")
253-
save_hm > MAXSAVE && warn("Discovered save $save_hm larger than the expected max $MAXSAVE")
178+
save_hm < MIN_SAVE && warn("Discovered save $save_hm less than the expected min $MIN_SAVE")
179+
save_hm > MAX_SAVE && warn("Discovered save $save_hm larger than the expected max $MAX_SAVE")
254180

255181
# Now we've finally parsed everything we need
256182
return Rule(
@@ -270,8 +196,8 @@ function zoneparse(gmtoff, rules, format, until="")
270196
offset = Time(gmtoff)
271197

272198
# Report unexpected offsets that could cause issues during resolve.
273-
offset < MINOFFSET && warn("Discovered offset $offset less than the expected min $MINOFFSET")
274-
offset > MAXOFFSET && warn("Discovered offset $offset larger than the expected max $MAXOFFSET")
199+
offset < MIN_GMT_OFFSET && warn("Discovered offset $offset less than the expected min $MIN_GMT_OFFSET")
200+
offset > MAX_GMT_OFFSET && warn("Discovered offset $offset larger than the expected max $MAX_GMT_OFFSET")
275201

276202
format = format == "zzz" ? "" : format
277203

@@ -347,7 +273,7 @@ function order_rules(rules::Array{Rule})
347273
# there is a small chance that the results are not ordered correctly.
348274
last_date = typemin(Date)
349275
for (i, (date, rule)) in enumerate(date_rules)
350-
if i > 1 && date - last_date <= MAXABSDIFF
276+
if i > 1 && date - last_date <= ABS_DIFF_OFFSET
351277
error("Dates are probably not in order")
352278
end
353279
last_date = date

src/timezones/time.jl

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Convenience type for working with HH:MM:SS.
2+
immutable Time <: TimePeriod
3+
seconds::Int
4+
end
5+
const ZERO = Time(0)
6+
7+
function Time(hour::Int, minute::Int, second::Int)
8+
Time(hour * 3600 + minute * 60 + second)
9+
end
10+
11+
function Time(s::String)
12+
# "-" represents 0:00 for some DST rules
13+
s == "-" && return ZERO
14+
parsed = map(n -> parse(Int, n), split(s, ':'))
15+
16+
# Only can handle up to hour, minute, second.
17+
length(parsed) > 3 && error("Invalid Time string")
18+
any(parsed[2:end] .< 0) && error("Invalid Time string")
19+
20+
# Handle variations where minutes and seconds may be excluded.
21+
values = [0,0,0]
22+
values[1:length(parsed)] = parsed
23+
24+
if values[1] < 0
25+
for i in 2:length(values)
26+
values[i] = -values[i]
27+
end
28+
end
29+
30+
return Time(values...)
31+
end
32+
33+
# TimePeriod methods
34+
Base.Dates.value(t::Time) = t.seconds
35+
Base.Dates.toms(t::Time) = t.seconds * 1000
36+
37+
toseconds(t::Time) = t.seconds
38+
hour(t::Time) = div(toseconds(t), 3600)
39+
minute(t::Time) = rem(div(toseconds(t), 60), 60)
40+
second(t::Time) = rem(toseconds(t), 60)
41+
42+
function hourminutesecond(t::Time)
43+
h, r = divrem(toseconds(t), 3600)
44+
m, s = divrem(r, 60)
45+
return h, m, s
46+
end
47+
48+
Base.convert(::Type{Second}, t::Time) = Second(toseconds(t))
49+
Base.convert(::Type{Millisecond}, t::Time) = Millisecond(toseconds(t) * 1000)
50+
Base.promote_rule{P<:Union{Week,Day,Hour,Minute,Second}}(::Type{P}, ::Type{Time}) = Second
51+
Base.promote_rule(::Type{Millisecond}, ::Type{Time}) = Millisecond
52+
53+
# Should be defined in Base.Dates
54+
Base.isless(x::Period, y::Period) = isless(promote(x,y)...)
55+
56+
# https://en.wikipedia.org/wiki/ISO_8601#Times
57+
function Base.string(t::Time)
58+
neg = toseconds(t) < 0 ? "-" : ""
59+
h, m, s = map(abs, hourminutesecond(t))
60+
@sprintf("%s%02d:%02d:%02d", neg, h, m, s)
61+
end
62+
63+
Base.show(io::IO, t::Time) = print(io, string(t))
64+
65+
66+
# min/max offsets across all zones and all time.
67+
const MIN_GMT_OFFSET = Time("-15:56:00") # Asia/Manilla
68+
const MAX_GMT_OFFSET = Time("15:13:42") # America/Metlakatla
69+
70+
# min/max save across all zones/rules and all time.
71+
const MIN_SAVE = Time("00:00")
72+
const MAX_SAVE = Time("02:00") # France, Germany, Port, Spain
73+
74+
const MIN_OFFSET = MIN_GMT_OFFSET + MIN_SAVE
75+
const MAX_OFFSET = MAX_GMT_OFFSET + MAX_SAVE
76+
const ABS_DIFF_OFFSET = abs(MAX_OFFSET - MIN_OFFSET)

src/timezones/types.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ function possible_dates(local_dt::DateTime, tz::VariableTimeZone; from_utc::Bool
8686

8787
# Determine the earliest and latest possible UTC DateTime
8888
# that this local DateTime could be.
89-
# TODO: Maybe look at the range of offsets available within
90-
# this TimeZone?
9189
if from_utc
9290
earliest = latest = local_dt
9391
else
94-
earliest = local_dt - Hour(12)
95-
latest = local_dt + Hour(14)
92+
# TODO: Alternatively we should only look at the range of offsets available within
93+
# this TimeZone.
94+
earliest = local_dt + MIN_OFFSET
95+
latest = local_dt + MAX_OFFSET
9696
end
9797

9898
# Determine the earliest transition the local DateTime could

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ for name in ("australasia", "europe", "northamerica")
1515
tzdata[name] = tzparse(joinpath(TZDATA_DIR, name))
1616
end
1717

18+
include("timezones/time.jl")
1819
include("timezones/types.jl")
1920
include("timezones/Olsen.jl")
2021
include("timezones/arithmetic.jl")

test/timezones/Olsen.jl

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,6 @@
1-
import TimeZones.Olsen: Time, hour, minute, second, toseconds, hourminutesecond
21
import TimeZones.Olsen: ZoneDict, RuleDict, zoneparse, ruleparse, resolve, parsedate
32
import Base.Dates: Hour, Minute, Second
43

5-
# Time seconds constructor
6-
t = Time(5025)
7-
@test hour(t) == 1
8-
@test minute(t) == 23
9-
@test second(t) == 45
10-
@test toseconds(t) == 5025
11-
@test hourminutesecond(t) == (1, 23, 45)
12-
13-
t = Time(-5025)
14-
@test hour(t) == -1
15-
@test minute(t) == -23
16-
@test second(t) == -45
17-
@test toseconds(t) == -5025
18-
@test hourminutesecond(t) == (-1, -23, -45)
19-
20-
t = Time(0,61,61)
21-
@test t == Time(1,2,1)
22-
@test toseconds(t) == 3721
23-
24-
t = Time(1,-23,-45)
25-
@test t == Time(0,36,15)
26-
@test toseconds(t) == 2175
27-
28-
# Time String constructor
29-
@test Time("1") == Time(1,0,0) # See Pacific/Apia rules for an example.
30-
@test Time("1:23") == Time(1,23,0)
31-
@test Time("1:23:45") == Time(1,23,45)
32-
@test Time("-1") == Time(-1,0,0)
33-
@test Time("-1:23") == Time(-1,-23,0)
34-
@test Time("-1:23:45") == Time(-1,-23,-45)
35-
@test_throws Exception Time("1:-23:45")
36-
@test_throws Exception Time("1:23:-45")
37-
@test_throws Exception Time("1:23:45:67")
38-
39-
@test string(Time(1,23,45)) == "01:23:45"
40-
@test string(Time(-1,-23,-45)) == "-01:23:45"
41-
@test string(Time(0,0,0)) == "00:00:00"
42-
@test string(Time(0,-1,0)) == "-00:01:00"
43-
@test string(Time(0,0,-1)) == "-00:00:01"
44-
@test string(Time(24,0,0)) == "24:00:00"
45-
46-
# Math
47-
@test Time(1,23,45) + Time(-1,-23,-45) == Time(0)
48-
@test Time(1,23,45) - Time(1,23,45) == Time(0)
49-
504
# Variations of until dates
515
@test parsedate("1945") == (DateTime(1945), 'w')
526
@test parsedate("1945 Aug") == (DateTime(1945,8), 'w')

test/timezones/time.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import TimeZones: Time, hour, minute, second, toseconds, hourminutesecond
2+
3+
# Time seconds constructor
4+
t = Time(5025)
5+
@test hour(t) == 1
6+
@test minute(t) == 23
7+
@test second(t) == 45
8+
@test toseconds(t) == 5025
9+
@test hourminutesecond(t) == (1, 23, 45)
10+
11+
t = Time(-5025)
12+
@test hour(t) == -1
13+
@test minute(t) == -23
14+
@test second(t) == -45
15+
@test toseconds(t) == -5025
16+
@test hourminutesecond(t) == (-1, -23, -45)
17+
18+
t = Time(0,61,61)
19+
@test t == Time(1,2,1)
20+
@test toseconds(t) == 3721
21+
22+
t = Time(1,-23,-45)
23+
@test t == Time(0,36,15)
24+
@test toseconds(t) == 2175
25+
26+
# Time String constructor
27+
@test Time("1") == Time(1,0,0) # See Pacific/Apia rules for an example.
28+
@test Time("1:23") == Time(1,23,0)
29+
@test Time("1:23:45") == Time(1,23,45)
30+
@test Time("-1") == Time(-1,0,0)
31+
@test Time("-1:23") == Time(-1,-23,0)
32+
@test Time("-1:23:45") == Time(-1,-23,-45)
33+
@test_throws Exception Time("1:-23:45")
34+
@test_throws Exception Time("1:23:-45")
35+
@test_throws Exception Time("1:23:45:67")
36+
37+
@test string(Time(1,23,45)) == "01:23:45"
38+
@test string(Time(-1,-23,-45)) == "-01:23:45"
39+
@test string(Time(0,0,0)) == "00:00:00"
40+
@test string(Time(0,-1,0)) == "-00:01:00"
41+
@test string(Time(0,0,-1)) == "-00:00:01"
42+
@test string(Time(24,0,0)) == "24:00:00"
43+
44+
# Math
45+
@test Time(1,23,45) + Time(-1,-23,-45) == Time(0)
46+
@test Time(1,23,45) - Time(1,23,45) == Time(0)

0 commit comments

Comments
 (0)