Skip to content

Commit

Permalink
Added static timezone code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
kcajf committed May 11, 2020
1 parent 2cb5ea7 commit 4caacd3
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/TimeZones.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ include("utcoffset.jl")
include(joinpath("types", "timezone.jl"))
include(joinpath("types", "fixedtimezone.jl"))
include(joinpath("types", "variabletimezone.jl"))
include(joinpath("types", "fastfixedtimezone.jl"))
include(joinpath("types", "fastvariabletimezone.jl"))
include(joinpath("types", "zoneddatetime.jl"))
include("exceptions.jl")
include(joinpath("tzdata", "TZData.jl"))
Expand All @@ -71,4 +73,8 @@ include("parse.jl")
include("plotting.jl")
include("deprecated.jl")

zones_file = joinpath(TZData.COMPILED_DIR, "zones.jl")
Base.include_dependency(zones_file) # Re-precompile if zones.jl changes
ispath(zones_file) && include(zones_file)

end # module
36 changes: 36 additions & 0 deletions src/types/fastfixedtimezone.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
struct FixedTimeZoneF <: TimeZone
local_minus_utc::Int32
function FixedTimeZoneF(secs::Int)
if (-86_400 < secs) && (secs < 86_400)
new(secs)
else
error("Invalid offset")
end
end
end

FixedTimeZoneF(s::Second) = FixedTimeZoneF(s.value)

function name(tz::FixedTimeZoneF)
offset = tz.local_minus_utc

# TODO: could use offset_string in utcoffsets.jl with some adaptation
if offset < 0
sig = '-'
offset = -offset
else
sig = '+'
end

hour, rem = divrem(offset, 3600)
minute, second = divrem(rem, 60)

if hour == 0 && minute == 0 && second == 0
name = "UTC"
elseif second == 0
name = @sprintf("UTC%c%02d:%02d", sig, hour, minute)
else
name = @sprintf("UTC%c%02d:%02d:%02d", sig, hour, minute, second)
end
return name
end
8 changes: 8 additions & 0 deletions src/types/fastvariabletimezone.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
primitive type VariableTimeZoneF <: TimeZone 16 end

struct TransitionSet
utc_datetimes::Vector{DateTime}
utc_offsets::Vector{Second}
dst_offsets::Vector{Second}
names::Vector{String}
end
3 changes: 2 additions & 1 deletion src/tzdata/build.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ function build(
if !isempty(compiled_dir)
@info "Converting tz source files into TimeZone data"
tz_source = TZSource(joinpath.(tz_source_dir, regions))
compile(tz_source, compiled_dir)
results = compile(tz_source, compiled_dir)
build_fast_files(results, compiled_dir)
end

return version
Expand Down
113 changes: 113 additions & 0 deletions src/tzdata/compile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,119 @@ function compile(tz_source::TZSource, dest_dir::AbstractString; kwargs...)
return results
end


# Convert all '/' to '__', all '+' to 'Plus' and '-' to 'Minus', unless
# it's a hyphen, in which case remove it. This is so the names can be used
# as identifiers.
function convert_bad_chars(name::String)
name = replace(replace(name, "/" => "__"), "+" => "Plus")
pos = findfirst('-', name)
if pos !== nothing
rest = name[pos:end]
if length(rest) > 0 && isnumeric(rest[1])
replace(name, "-" => "Minus")
else
replace(name, "-" => "")
end
else
name
end
end

function fast_repr(dt::DateTime)
# Using the numeric DateTime constructor is faster than the string one
y = Year(dt).value
mo = Month(dt).value
d = Day(dt).value
h = Hour(dt).value
mi = Minute(dt).value
s = Second(dt).value
"DateTime($y, $mo, $d, $h, $mi, $s)"
end

fast_repr(::Nothing) = "nothing"

function repr_vector(xs::Vector{String}, indent="")
b = IOBuffer()
print(b, indent * "[")
for x in xs
print(b, "$x, ")
end
print(b, "]")
return String(take!(b))
end

function write_fast_files(f, zones)
# Zone consts
for (i, (tz, class)) in enumerate(zones)
varname = convert_bad_chars(tz.name)
println(f, "const $varname = reinterpret(VariableTimeZoneF, UInt16($i))")
end
println(f, "\n")

# Names
println(f, "function name(tz::VariableTimeZoneF)")
for (i, (tz, class)) in enumerate(zones)
varname = convert_bad_chars(tz.name)
if_str = i == 1 ? "if" : "elseif"
println(f, " $if_str tz == $varname; $(repr(tz.name))") # ; only needed for readability
end
println(f, " else error()\n end\nend\n\n")

# Cutoffs
println(f, "function cutoff(tz::VariableTimeZoneF)")
for (i, (tz, class)) in enumerate(zones)
varname = convert_bad_chars(tz.name)
if_str = i == 1 ? "if" : "elseif"
println(f, " $if_str tz == $varname; $(fast_repr(tz.cutoff))")
end
println(f, " else error()\n end\nend\n\n")

# TransitionSets
for (i, (tz, class)) in enumerate(zones)
varname = convert_bad_chars(tz.name)
println(f, "const $(varname)__transitions = TransitionSet(")
tran_times_strs = String[]
std_strs = String[]
dst_strs = String[]
name_strs = String[]

for tran in tz.transitions
push!(tran_times_strs, fast_repr(tran.utc_datetime))
push!(std_strs, "Second($(tran.zone.offset.std.value))")
push!(dst_strs, "Second($(tran.zone.offset.dst.value))")
push!(name_strs, repr(tran.zone.name))
end

indent = " "
println(f, repr_vector(tran_times_strs, indent) * ",")
println(f, repr_vector(std_strs, indent) * ",")
println(f, repr_vector(dst_strs, indent) * ",")
println(f, repr_vector(name_strs, indent))
println(f, ")\n")
end

# Transition getter
println(f, "function transitions(tz::VariableTimeZoneF)")
for (i, (tz, class)) in enumerate(zones)
varname = convert_bad_chars(tz.name)
if_str = i == 1 ? "if" : "elseif"
println(f, " $if_str tz == $varname; $(varname)__transitions")
end
println(f, " else error()\n end\nend\n\n")
end

function build_fast_files(results, dest_dir::AbstractString)
variable_zones = sort(filter(x -> isa(x[1], VariableTimeZone), results); by=x -> x[1].name)
@assert length(variable_zones) <= 700 # In theory, up to 65535

path = joinpath(dest_dir, "zones.jl")
open(path, "w") do f
write_fast_files(f, variable_zones)
end
end


# TODO: Deprecate?
function compile(tz_source_dir::AbstractString=TZ_SOURCE_DIR, dest_dir::AbstractString=COMPILED_DIR; kwargs...)
tz_source_paths = joinpath.(tz_source_dir, readdir(tz_source_dir))
Expand Down

0 comments on commit 4caacd3

Please sign in to comment.