From 4caacd305a5352c443408bf7db600e8990909b1c Mon Sep 17 00:00:00 2001 From: Jack Frigaard Date: Mon, 11 May 2020 19:52:27 +0930 Subject: [PATCH] Added static timezone code generation --- src/TimeZones.jl | 6 ++ src/types/fastfixedtimezone.jl | 36 ++++++++++ src/types/fastvariabletimezone.jl | 8 +++ src/tzdata/build.jl | 3 +- src/tzdata/compile.jl | 113 ++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/types/fastfixedtimezone.jl create mode 100644 src/types/fastvariabletimezone.jl diff --git a/src/TimeZones.jl b/src/TimeZones.jl index 0360cc74a..b1df523b5 100644 --- a/src/TimeZones.jl +++ b/src/TimeZones.jl @@ -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")) @@ -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 diff --git a/src/types/fastfixedtimezone.jl b/src/types/fastfixedtimezone.jl new file mode 100644 index 000000000..69010629a --- /dev/null +++ b/src/types/fastfixedtimezone.jl @@ -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 \ No newline at end of file diff --git a/src/types/fastvariabletimezone.jl b/src/types/fastvariabletimezone.jl new file mode 100644 index 000000000..431af7919 --- /dev/null +++ b/src/types/fastvariabletimezone.jl @@ -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 diff --git a/src/tzdata/build.jl b/src/tzdata/build.jl index ae35ae407..3ceb22812 100644 --- a/src/tzdata/build.jl +++ b/src/tzdata/build.jl @@ -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 diff --git a/src/tzdata/compile.jl b/src/tzdata/compile.jl index ec4a29f70..12954a2e6 100644 --- a/src/tzdata/compile.jl +++ b/src/tzdata/compile.jl @@ -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))