diff --git a/src/TimeZones.jl b/src/TimeZones.jl index ce7032b8..663f7643 100644 --- a/src/TimeZones.jl +++ b/src/TimeZones.jl @@ -5,7 +5,7 @@ using Dates using Printf using Scratch: @get_scratch! using Unicode -using InlineStrings: InlineString15 +using InlineStrings: InlineString15, InlineString31 using TZJData: TZJData import Dates: TimeZone, UTC @@ -42,7 +42,12 @@ abstract type Local <: TimeZone end function __init__() # Set at runtime to ensure relocatability - _COMPILED_DIR[] = @static if isdefined(TZJData, :artifact_dir) + # Prefer scratch-compiled directory with matching versions, fall back to artifact + expected_dir = TZData.compiled_dir() + + _COMPILED_DIR[] = if isdir(expected_dir) + expected_dir + elseif isdefined(TZJData, :artifact_dir) TZJData.artifact_dir() else # Backwards compatibility for TZJData versions below v1.3.1. The portion of the diff --git a/src/types/timezone.jl b/src/types/timezone.jl index 26dd330d..71eeee7f 100644 --- a/src/types/timezone.jl +++ b/src/types/timezone.jl @@ -39,14 +39,27 @@ US/Pacific (UTC-8/UTC-7) TimeZone(::AbstractString, ::Class) function TimeZone(str::AbstractString, mask::Class=Class(:DEFAULT)) - tz, class = get(_TZ_CACHE, str) do + tz, class, link = get(_TZ_CACHE, str) do if occursin(FIXED_TIME_ZONE_REGEX, str) - FixedTimeZone(str), Class(:FIXED) + FixedTimeZone(str), Class(:FIXED), InlineString31("") else throw(ArgumentError("Unknown time zone \"$str\"")) end end + # Auto-redirect LEGACY timezones to their modern equivalents + # Only when user hasn't explicitly opted in to LEGACY class + if !isempty(link) && class == Class(:LEGACY) && mask & Class(:LEGACY) == Class(:NONE) + # Note: Using depwarn here allows users to control behavior via --depwarn flag. + # With --depwarn=error, this becomes an error (strict mode). + # This matches the behavior requested in issue #469 https://github.com/JuliaTime/TimeZones.jl/issues/469#issuecomment-2341741754. + Base.depwarn( + "The time zone \"$str\" is deprecated, using \"$link\" instead.", + :TimeZone + ) + return TimeZone(String(link), mask) + end + if mask & class == Class(:NONE) throw(ArgumentError( "The time zone \"$str\" is of class `$(repr(class))` which is " * @@ -83,7 +96,10 @@ function istimezone(str::AbstractString, mask::Class=Class(:DEFAULT)) return true end - # Checks against pre-compiled time zones - class = get(() -> (UTC_ZERO, Class(:NONE)), _TZ_CACHE, str)[2] + # Checks against pre-compiled time zones (3-tuple now: tz, class, link) + _, class, link = get(() -> (UTC_ZERO, Class(:NONE), InlineString31("")), _TZ_CACHE, str) + + # Allow linked legacy timezones to auto-redirect + !isempty(link) && class == Class(:LEGACY) && mask & Class(:LEGACY) == Class(:NONE) && return true return mask & class != Class(:NONE) end diff --git a/src/types/timezonecache.jl b/src/types/timezonecache.jl index b9b89174..fd8b8268 100644 --- a/src/types/timezonecache.jl +++ b/src/types/timezonecache.jl @@ -1,8 +1,10 @@ # Use a separate cache for FixedTimeZone (which is `isbits`) so the container is concretely # typed and we avoid allocating a FixedTimeZone every time we get one from the cache. +# Note: link uses InlineString31 (not Union{InlineString31,Nothing}) to keep tuples isbits. +# An empty string "" is used as a sentinel value for "no link target". struct TimeZoneCache - ftz::Dict{String,Tuple{FixedTimeZone,Class}} - vtz::Dict{String,Tuple{VariableTimeZone,Class}} + ftz::Dict{String,Tuple{FixedTimeZone,Class,InlineString31}} + vtz::Dict{String,Tuple{VariableTimeZone,Class,InlineString31}} lock::ReentrantLock initialized::Threads.Atomic{Bool} end @@ -28,12 +30,16 @@ function reload!(cache::TimeZoneCache, compiled_dir::AbstractString=_COMPILED_DI empty!(cache.vtz) walk_tz_dir(compiled_dir) do name, path - tz, class = open(TZJFile.read, path, "r")(name) + tz, class, link = open(TZJFile.read, path, "r")(name) + + # Convert link to InlineString31 to keep tuples isbits + # Use empty string as sentinel for "no link target" + entry = (tz, class, link === nothing ? InlineString31("") : InlineString31(link)) if tz isa FixedTimeZone - cache.ftz[name] = (tz, class) + cache.ftz[name] = entry elseif tz isa VariableTimeZone - cache.vtz[name] = (tz, class) + cache.vtz[name] = entry else error("Unhandled TimeZone class encountered: $(typeof(tz))") end @@ -63,10 +69,17 @@ function Base.get(body::Function, cache::TimeZoneCache, name::AbstractString) end # Build specific tzdata version if specified by `JULIA_TZ_VERSION` -function _build() - desired_version = TZData.tzdata_version() - if desired_version != TZJData.TZDATA_VERSION - _COMPILED_DIR[] = TZData.build(desired_version, _scratch_dir()) +# Also rebuilds if the TZJFile format version doesn't match the expected version +function _build(tzjf_version::Integer=TZJFile.tzjfile_version()) + expected_dir = TZData.compiled_dir() + + # Rebuild if the expected directory doesn't exist + # TODO: I believe this currently avoids using TZJData.jl and forces a rebuild, but makes testing V1 vs V2 behaviour easier + if !isdir(expected_dir) + _COMPILED_DIR[] = TZData.build(TZData.tzdata_version(), _scratch_dir(); tzjf_version) + elseif _COMPILED_DIR[] != expected_dir + # Expected directory exists but we're pointing to wrong location (e.g., artifact) + _COMPILED_DIR[] = expected_dir end return nothing diff --git a/src/tzdata/build.jl b/src/tzdata/build.jl index 55652f66..cd274ebc 100644 --- a/src/tzdata/build.jl +++ b/src/tzdata/build.jl @@ -21,12 +21,25 @@ const REGIONS = [STANDARD_REGIONS; LEGACY_REGIONS] _archive_relative_dir() = "archive" _tz_source_relative_dir(version::AbstractString) = joinpath("tzsource", version) -_compiled_relative_dir(version::AbstractString) = joinpath("compiled", "tzjf", "v$(TZJFile.DEFAULT_VERSION)", version) +_compiled_relative_dir(version::AbstractString, tzjf_version::Integer) = joinpath("compiled", "tzjf", "v$tzjf_version", version) -function build(version::AbstractString, working_dir::AbstractString) +""" + compiled_dir() -> String + +Returns the expected compiled directory path for the current tzdata and TZJFile versions. +This is where TimeZones.jl will look for compiled timezone data. +""" +function compiled_dir() + return joinpath( + _scratch_dir(), + _compiled_relative_dir(tzdata_version(), TZJFile.tzjfile_version()) + ) +end + +function build(version::AbstractString, working_dir::AbstractString; tzjf_version::Integer=TZJFile.tzjfile_version()) tzdata_archive_dir = joinpath(working_dir, _archive_relative_dir()) tz_source_dir = joinpath(working_dir, _tz_source_relative_dir(version)) - compiled_dir = joinpath(working_dir, _compiled_relative_dir(version)) + compiled_dir = joinpath(working_dir, _compiled_relative_dir(version, tzjf_version)) url = tzdata_url(version) tzdata_archive_file = joinpath(tzdata_archive_dir, basename(url)) @@ -63,10 +76,10 @@ function build(version::AbstractString, working_dir::AbstractString) return compiled_dir end -function cleanup(version::AbstractString, working_dir::AbstractString) +function cleanup(version::AbstractString, working_dir::AbstractString; tzjf_version::Integer=TZJFile.tzjfile_version()) tzdata_archive_file = joinpath(working_dir, _archive_relative_dir(), basename(tzdata_url(version))) tz_source_dir = joinpath(working_dir, _tz_source_relative_dir(version)) - compiled_dir = joinpath(working_dir, _compiled_relative_dir(version)) + compiled_dir = joinpath(working_dir, _compiled_relative_dir(version, tzjf_version)) isfile(tzdata_archive_file) && rm(tzdata_archive_file) isdir(tz_source_dir) && rm(tz_source_dir; recursive=true) diff --git a/src/tzdata/compile.jl b/src/tzdata/compile.jl index 6aef95fb..5e273aa2 100644 --- a/src/tzdata/compile.jl +++ b/src/tzdata/compile.jl @@ -643,6 +643,11 @@ end function compile(name::AbstractString, tz_source::TZSource; kwargs...) ordered = OrderedRuleDict() + # Get the direct link target if this is a link (no chain resolution needed) + # Note: We don't need to handle link chains (A→B→C) because the IANA tzdata + # backward file explicitly avoids them, as stated in the backward file header. + link = get(tz_source.links, name, nothing) + if haskey(tz_source.links, name) # When the name is a link we'll generate a time zone from the link's target and # rename the time zone with the link name. @@ -650,17 +655,17 @@ function compile(name::AbstractString, tz_source::TZSource; kwargs...) tz = compile!(zone_name, tz_source, ordered; kwargs...) class = Class(name, associated_regions(tz_source, name)) - return rename(tz, name), class + return rename(tz, name), class, link else tz = compile!(name, tz_source, ordered; kwargs...) class = Class(name, associated_regions(tz_source, name)) - return tz, class + return tz, class, link end end function compile(tz_source::TZSource; kwargs...) - results = Vector{Tuple{TimeZone,Class}}() + results = Vector{Tuple{TimeZone,Class,Union{String,Nothing}}}() ordered = OrderedRuleDict() lookup = Dict{String,TimeZone}() @@ -668,7 +673,7 @@ function compile(tz_source::TZSource; kwargs...) tz = compile!(zone_name, tz_source, ordered; kwargs...) class = Class(zone_name, associated_regions(tz_source, zone_name)) - push!(results, (tz, class)) + push!(results, (tz, class, nothing)) lookup[zone_name] = tz end @@ -678,8 +683,10 @@ function compile(tz_source::TZSource; kwargs...) target_tz = lookup[target] tz = rename(target_tz, link_name) class = Class(link_name, associated_regions(tz_source, link_name)) + # Only store link target for LEGACY timezones to save memory + link = class == Class(:LEGACY) ? target : nothing - push!(results, (tz, class)) + push!(results, (tz, class, link)) elseif !haskey(lookup, target) error("Unable to resolve link \"$link_name\" referencing \"$target\"") end @@ -692,7 +699,7 @@ function compile(tz_source::TZSource, dest_dir::AbstractString; kwargs...) results = compile(tz_source; kwargs...) isdir(dest_dir) || error("Destination directory doesn't exist") - for (tz, class) in results + for (tz, class, link) in results parts = split(TimeZones.name(tz), '/') tz_path = joinpath(dest_dir, parts...) tz_dir = dirname(tz_path) @@ -700,7 +707,7 @@ function compile(tz_source::TZSource, dest_dir::AbstractString; kwargs...) isdir(tz_dir) || mkpath(tz_dir) open(tz_path, "w") do fp - TZJFile.write(fp, tz; class) + TZJFile.write(fp, tz; class, link) end end diff --git a/src/tzjfile/TZJFile.jl b/src/tzjfile/TZJFile.jl index 35fca98d..9c972c37 100644 --- a/src/tzjfile/TZJFile.jl +++ b/src/tzjfile/TZJFile.jl @@ -4,7 +4,36 @@ using Dates: Dates, DateTime, Second, datetime2unix, unix2datetime using ...TimeZones: FixedTimeZone, VariableTimeZone, Class, Transition using ...TimeZones.TZFile: combine_designations, get_designation, timestamp_min -const DEFAULT_VERSION = 1 +const DEFAULT_VERSION = 2 + +""" + tzjfile_version() -> Int + +Returns the TZJFile format version to use, controlled by the `JULIA_TZJ_VERSION` environment +variable. If not set, defaults to `DEFAULT_VERSION` (currently $DEFAULT_VERSION). + +This allows users to opt-in or opt-out of new file format versions for testing or +compatibility purposes. + +# Examples +```julia +# Use default version (currently 2) +julia> TZJFile.tzjfile_version() +2 + +# Use version 1 via environment variable +julia> ENV["JULIA_TZJ_VERSION"] = "1" +julia> TZJFile.tzjfile_version() +1 +``` +""" +function tzjfile_version() + version_str = get(ENV, "JULIA_TZJ_VERSION", string(DEFAULT_VERSION)) + version = tryparse(Int, version_str) + version === nothing && error("Invalid JULIA_TZJ_VERSION: \"$version_str\". Must be an integer (1 or 2).") + version ∉ (1, 2) && error("Unsupported JULIA_TZJ_VERSION: $version. Must be 1 or 2.") + return version +end include("utils.jl") include("read.jl") diff --git a/src/tzjfile/read.jl b/src/tzjfile/read.jl index 8e94b9d9..74dcd14f 100644 --- a/src/tzjfile/read.jl +++ b/src/tzjfile/read.jl @@ -51,7 +51,7 @@ function read_content(io::IO, version::Val{1}) # Now build the time zone transitions tz_constructor = if tzh_timecnt == 0 || (tzh_timecnt == 1 && transition_types[1] == TIMESTAMP_MIN) tzj_info = transition_types[1] - name -> (FixedTimeZone(name, tzj_info.utc_offset, tzj_info.dst_offset), class) + name -> (FixedTimeZone(name, tzj_info.utc_offset, tzj_info.dst_offset), class, nothing) else transitions = Transition[] cutoff = timestamp2datetime(cutoff_time, nothing) @@ -75,8 +75,32 @@ function read_content(io::IO, version::Val{1}) prev_zone = zone end - name -> (VariableTimeZone(name, transitions, cutoff), class) + name -> (VariableTimeZone(name, transitions, cutoff), class, nothing) end return tz_constructor end + +function read_content(io::IO, version::Val{2}) + # Read v1 content first (reuse existing implementation) + tz_constructor_v1 = read_content(io, Val(1)) + + # Read version 2 extension: link information + has_link = ntoh(Base.read(io, UInt8)) != 0 + link = if has_link + length = ntoh(Base.read(io, UInt16)) + chars = Vector{UInt8}(undef, length) + for i in eachindex(chars) + chars[i] = ntoh(Base.read(io, UInt8)) + end + String(chars) + else + nothing + end + + # Return constructor that adds link to v1 result + return function(name) + tz, class, _ = tz_constructor_v1(name) + return (tz, class, link) + end +end diff --git a/src/tzjfile/write.jl b/src/tzjfile/write.jl index d1560bdf..5cd940fa 100644 --- a/src/tzjfile/write.jl +++ b/src/tzjfile/write.jl @@ -1,4 +1,4 @@ -function write(io::IO, tz::VariableTimeZone; class::Class, version::Integer=DEFAULT_VERSION) +function write(io::IO, tz::VariableTimeZone; class::Class, version::Integer=tzjfile_version(), link::Union{String,Nothing}=nothing) combined_designation, designation_indices = combine_designations(t.zone.name for t in tz.transitions) # TODO: Sorting provides us a way to avoid checking for the sentinel on each loop @@ -25,10 +25,11 @@ function write(io::IO, tz::VariableTimeZone; class::Class, version::Integer=DEFA transition_types, cutoff, combined_designation, + link, ) end -function write(io::IO, tz::FixedTimeZone; class::Class, version::Integer=DEFAULT_VERSION) +function write(io::IO, tz::FixedTimeZone; class::Class, version::Integer=tzjfile_version(), link::Union{String,Nothing}=nothing) combined_designation, designation_indices = combine_designations([tz.name]) transition_times = Vector{Int64}() @@ -53,6 +54,7 @@ function write(io::IO, tz::FixedTimeZone; class::Class, version::Integer=DEFAULT transition_types, cutoff, combined_designation, + link, ) end @@ -71,6 +73,7 @@ function write_content( transition_types::Vector{TZJTransition}, cutoff::Int64, combined_designation::AbstractString, + link::Union{String,Nothing}=nothing, # Ignored in v1 for compatibility ) if length(transition_times) > 0 unique_transition_types = unique(transition_types) @@ -111,3 +114,38 @@ function write_content( return nothing end + +function write_content( + io::IO, + version::Val{2}; + class::UInt8, + transition_times::Vector{Int64}, + transition_types::Vector{TZJTransition}, + cutoff::Int64, + combined_designation::AbstractString, + link::Union{String,Nothing}=nothing, +) + # Write v1 content first (reuse existing implementation) + write_content( + io, + Val(1); + class, + transition_times, + transition_types, + cutoff, + combined_designation, + ) + + # Version 2 extension: write link information + if link === nothing + Base.write(io, hton(UInt8(0))) # No link target + else + Base.write(io, hton(UInt8(1))) # Has link target + Base.write(io, hton(UInt16(length(link)))) + for char in link + Base.write(io, hton(UInt8(char))) + end + end + + return nothing +end diff --git a/test/helpers.jl b/test/helpers.jl index c7832673..dff27681 100644 --- a/test/helpers.jl +++ b/test/helpers.jl @@ -1,4 +1,5 @@ # Utility functions for testing +using InlineStrings if VERSION < v"1.9.0-DEV.1744" # https://github.com/JuliaLang/julia/pull/47367 macro allocations(ex) @@ -50,7 +51,21 @@ show_compact = (io, args...) -> show(IOContext(io, :compact => true), args...) function add!(dict::Dict, t::Tuple{TimeZone,TimeZones.Class}) tz, class = t name = TimeZones.name(tz) - push!(dict, name => t) + push!(dict, name => (tz, class, InlineString31(""))) + return tz +end + +function add!(dict::Dict, t::Tuple{TimeZone,TimeZones.Class,InlineString31}) + tz, class, link = t + name = TimeZones.name(tz) + push!(dict, name => (tz, class, link)) + return tz +end + +function add!(dict::Dict, t::Tuple{TimeZone,TimeZones.Class,Nothing}) + tz, class, _ = t + name = TimeZones.name(tz) + push!(dict, name => (tz, class, InlineString31(""))) return tz end @@ -59,16 +74,26 @@ function add!(cache::TimeZones.TimeZoneCache, t::Tuple{T,TimeZones.Class}) where return add!(dict, t) end +function add!(cache::TimeZones.TimeZoneCache, t::Tuple{T,TimeZones.Class,InlineString31}) where {T<:TimeZone} + dict = T == FixedTimeZone ? cache.ftz : cache.vtz + return add!(dict, t) +end + +function add!(cache::TimeZones.TimeZoneCache, t::Tuple{T,TimeZones.Class,Nothing}) where {T<:TimeZone} + dict = T == FixedTimeZone ? cache.ftz : cache.vtz + return add!(dict, t) +end + function add!(cache::TimeZones.TimeZoneCache, tz::VariableTimeZone) # Not all `VariableTimeZone`s are the STANDARD class. However, for testing purposes # the class doesn't need to be precise. class = TimeZones.Class(:STANDARD) - return add!(cache.vtz, (tz, class)) + return add!(cache.vtz, (tz, class, InlineString31(""))) end function add!(cache::TimeZones.TimeZoneCache, tz::FixedTimeZone) class = TimeZones.Class(:FIXED) - return add!(cache.ftz, (tz, class)) + return add!(cache.ftz, (tz, class, InlineString31(""))) end function with_tz_cache(f, cache::TimeZones.TimeZoneCache) diff --git a/test/runtests.jl b/test/runtests.jl index b04df5cc..6be50536 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,7 @@ using TimeZones using TimeZones: _scratch_dir using TimeZones.TZData: TZSource, compile, build, tzdata_url, unpack, _tz_source_relative_dir, _archive_relative_dir, _compiled_relative_dir +using TimeZones.TZJFile: TZJFile using TZJData: TZJData using Unicode @@ -17,12 +18,13 @@ Mocking.activate() const TZDATA_VERSION = "2016j" const TZFILE_DIR = joinpath(@__DIR__, "tzfile", "data") -const TEST_REGIONS = ["asia", "australasia", "europe", "northamerica"] +const TEST_REGIONS = ["asia", "australasia", "europe", "northamerica", "backward"] const TEST_TZ_SOURCE_DIR = joinpath(_scratch_dir(), _tz_source_relative_dir(TZDATA_VERSION)) # By default use a specific version of tzdata so we just testing for TimeZones.jl code -# changes and not changes to the dataa. -build(TZDATA_VERSION, _scratch_dir()) +# changes and not changes to the data. Build with the current tzjfile_version() so that +# tests always use the format version being tested (controlled by JULIA_TZJ_VERSION env var). +build(TZDATA_VERSION, _scratch_dir(); tzjf_version=TZJFile.tzjfile_version()) # For testing we'll reparse the tzdata every time to instead of using the compiled data. # This should make interactive development/testing cycles simplier since you won't be forced diff --git a/test/types/timezone.jl b/test/types/timezone.jl index 666ef4cd..b2a9a219 100644 --- a/test/types/timezone.jl +++ b/test/types/timezone.jl @@ -26,6 +26,36 @@ end @test TimeZone("Etc/GMT-14", Class(:LEGACY)) == FixedTimeZone("Etc/GMT-14", 14 * 3600) end +@testset "legacy timezone auto-redirect" begin + # Auto-redirect only works with TZJFile v2 (which stores link information) + if TimeZones.TZJFile.tzjfile_version() >= 2 + # Auto-redirect with deprecation warning + # Note: US/Pacific is a LEGACY timezone that links to America/Los_Angeles + @test_logs (:warn, r"US/Pacific.*deprecated.*America/Los_Angeles") match_mode=:any begin + tz = TimeZone("US/Pacific") + @test TimeZones.name(tz) == "America/Los_Angeles" + end + + # Explicit opt-in to LEGACY bypasses auto-redirect + tz_legacy = TimeZone("US/Pacific", Class(:LEGACY)) + @test TimeZones.name(tz_legacy) == "US/Pacific" + + # istimezone returns true for LEGACY that will auto-redirect + @test istimezone("US/Pacific") # Uses default mask, will redirect + @test istimezone("US/Pacific", Class(:DEFAULT)) + @test istimezone("US/Pacific", Class(:LEGACY)) # Explicit LEGACY allowed + else + # With v1 format, LEGACY timezones don't have link info, so they're blocked + @test_throws ArgumentError TimeZone("US/Pacific") + @test !istimezone("US/Pacific") + + # Explicit opt-in to LEGACY still works + tz_legacy = TimeZone("US/Pacific", Class(:LEGACY)) + @test TimeZones.name(tz_legacy) == "US/Pacific" + @test istimezone("US/Pacific", Class(:LEGACY)) + end +end + # These allocation tests are a bit fragile. Clearing the cache makes these tests more # in on Julia 1.12.0-DEV.1786. @testset "allocations" begin diff --git a/test/tzdata/compile.jl b/test/tzdata/compile.jl index f3242a8e..c47af17f 100644 --- a/test/tzdata/compile.jl +++ b/test/tzdata/compile.jl @@ -395,6 +395,24 @@ dates, ordered = order_rules([rule_post, rule_endless, rule_overlap, rule_pre], @test longyearbyen.cutoff == oslo.cutoff end + @testset "Link targets saved for LEGACY only" begin + # Combine backward with northamerica to test LEGACY links + # backward file only has links, needs zones from other regions + tz_source_combined = TZSource( + joinpath.(TEST_TZ_SOURCE_DIR, ["northamerica", "backward"]) + ) + + # US/Pacific is a LEGACY link (from backward file) + tz, class, link = compile("US/Pacific", tz_source_combined) + @test class == Class(:LEGACY) + @test link == "America/Los_Angeles" # Link target stored for LEGACY + + # America/Los_Angeles is STANDARD (not a link) + tz2, class2, link2 = compile("America/Los_Angeles", tz_source_combined) + @test class2 == Class(:STANDARD) + @test link2 === nothing # Not a link, so no link target + end + # Zones that don't include multiple lines and no rules should be treated as a FixedTimeZone. @testset "FixedTimeZone" begin tz = first(compile("MST", tzdata["northamerica"])) diff --git a/test/tzjfile/read.jl b/test/tzjfile/read.jl index 280245f3..a1af24b0 100644 --- a/test/tzjfile/read.jl +++ b/test/tzjfile/read.jl @@ -51,3 +51,39 @@ end end end end + +@testset "v1 backward compatibility" begin + @testset "Existing v1 files from disk read correctly" begin + # The pre-compiled test data files are in v1 format + tzj_utc, tzj_class, tzj_link = open(joinpath(TZJFILE_DIR, "UTC"), "r") do fp + TZJFile.read(fp)("UTC") + end + @test tzj_utc == FixedTimeZone("UTC", 0) + @test tzj_class == Class(:FIXED) + @test tzj_link === nothing + end +end + +@testset "v2 format" begin + @testset "with link" begin + warsaw, class = compile("Europe/Warsaw", tzdata["europe"]) + io = IOBuffer() + TZJFile.write(io, warsaw; class, version=2, link="Poland") + tzj_warsaw, tzj_class, tzj_link = TZJFile.read(seekstart(io))("Europe/Warsaw") + + @test tzj_warsaw == warsaw + @test tzj_class == class + @test tzj_link == "Poland" + end + + @testset "without link_target" begin + warsaw, class = compile("Europe/Warsaw", tzdata["europe"]) + io = IOBuffer() + TZJFile.write(io, warsaw; class, version=2, link=nothing) + tzj_warsaw, tzj_class, tzj_link = TZJFile.read(seekstart(io))("Europe/Warsaw") + + @test tzj_warsaw == warsaw + @test tzj_class == class + @test tzj_link === nothing + end +end diff --git a/test/tzjfile/write.jl b/test/tzjfile/write.jl index 614ae443..2c44177e 100644 --- a/test/tzjfile/write.jl +++ b/test/tzjfile/write.jl @@ -42,4 +42,14 @@ end @test tzj_moscow == moscow @test tzj_class == class end + + @testset "v2" begin + moscow, class = compile("Europe/Moscow", tzdata["europe"]) + io = IOBuffer() + TZJFile.write(io, moscow; class, version=2) + tzj_moscow, tzj_class = TZJFile.read(seekstart(io))("Europe/Moscow") + + @test tzj_moscow == moscow + @test tzj_class == class + end end