Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use tzdata version specified by TimeZones.jl #14

Merged
merged 5 commits into from
Dec 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TimeZoneFinder"
uuid = "3ccf6684-3f25-4581-8c58-114637dcab4a"
authors = ["Tom Gillam <[email protected]>"]
version = "0.2.0"
version = "0.3.0"

[deps]
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
Expand All @@ -18,7 +18,7 @@ JSON3 = "1"
Memoize = "0.4"
Meshes = "0.23, 0.24, 0.25, 0.26"
Scratch = "1"
TimeZones = "1.9.1"
TimeZones = "1.8"
julia = "1.6"

[extras]
Expand Down
2 changes: 1 addition & 1 deletion artifact_build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

This environment is used for building the artifacts that are then added to the main package.

Only pakage maintainers should need to look at this.
Only package maintainers should need to look at this.
41 changes: 18 additions & 23 deletions src/TimeZoneFinder.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ using Scratch
using Serialization
using TimeZones

const LATEST_RELEASE = "2022f"

function _get_points(coord_list)::Vector{Point{2,Float64}}
return [Point(Float64(x[1]), Float64(x[2])) for x in coord_list]
end
Expand Down Expand Up @@ -53,10 +51,10 @@ function Base.in(point::Point, bpa::BoundedPolyArea)
end

"""
Generate the timezone map data from the artifact identified by `release`.
Generate the timezone map data from the artifact identified by `version`.
"""
function generate_data(release::AbstractString)
artifact_name = "timezone-boundary-builder-$release"
function generate_data(version::AbstractString)
artifact_name = "timezone-boundary-builder-$version"
dir = LazyArtifacts.@artifact_str(artifact_name)
obj = open(JSON3.read, joinpath(dir, "combined-with-oceans.json"))

Expand All @@ -83,11 +81,11 @@ function generate_data(release::AbstractString)
end

"""
_scratch_dir(release)
_scratch_dir(version)

Get the scratch directory path in which the serialized mapping data will be kept.
"""
function _scratch_dir(release::AbstractString)
function _scratch_dir(version::AbstractString)
# The scratch directory should be different for different package versions, since we
# may generate data in a different format.
pkg_version = VersionNumber(
Expand All @@ -98,36 +96,36 @@ function _scratch_dir(release::AbstractString)
# protocol may change.
julia_version = string(VERSION)

scratch_name = "$(release)-$(pkg_version)-$(julia_version)"
scratch_name = "$(version)-$(pkg_version)-$(julia_version)"
return @get_scratch!(scratch_name)
end

"""Serialized data file path for this version."""
_cache_path(release::AbstractString) = joinpath(_scratch_dir(release), "data.bin")
_cache_path(version::AbstractString) = joinpath(_scratch_dir(version), "data.bin")

"""
load_data(release)
load_data(version)

Load timezone map data for `release`.
Load timezone map data for `version`.

This is memoized, such that the data is only read from disk once within the lifetime of the
Julia process.
"""
@memoize function load_data(release::AbstractString)
@memoize function load_data(version::AbstractString)
# Read data from the cache path if it exists, otherwise generate from the artifact, and
# cache.
path = _cache_path(release)
path = _cache_path(version)
return if isfile(path)
deserialize(path)
else
data = generate_data(release)
data = generate_data(version)
serialize(path, data)
data
end
end

"""
timezone_at(latitude, longitude; release=LATEST_RELEASE)
timezone_at(latitude, longitude)

Get the timezone at the given `latitude` and `longitude`.

Expand All @@ -136,18 +134,15 @@ julia> timezone_at(52.5061, 13.358)
Europe/Berlin (UTC+1/UTC+2)
```

The optional `release` argument can be used to specify a different version of the
[timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder) dataset
to use. By default the latest release will be used, which will be the desired setting for
common use.
!!! note
The library always uses the same version of tzdata currently used by `TimeZones`.

Returns a `TimeZone` instance if `latitude` and `longitude` correspond to a known timezone,
otherwise `nothing` is returned.
"""
function timezone_at(
latitude::Real, longitude::Real; release::AbstractString=LATEST_RELEASE
)
data = load_data(release)
function timezone_at(latitude::Real, longitude::Real)
version = TimeZones.TZData.tzdata_version()
data = load_data(version)
p = Point{2,Float64}(longitude, latitude)
# This is an unintelligent linear search through all polygons. There is much room for
# improvement by building a spatial index.
Expand Down
53 changes: 49 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ using Memoize
using Test
using TimeZoneFinder
using TimeZones
using TimeZones.TZData: tzdata_version

"""
Location
Expand Down Expand Up @@ -30,6 +31,33 @@ function Location(latitude::Real, longitude::Real, timezone::AbstractString)
end
Location(args::Tuple) = Location(args...)

"""
tzdata_context(f, version)

Run all code in `f` in the context of tzdata `version`.

!!! warning
The `@tz_str` macro should NOT be used inside this context, since it works by obtaining
the TimeZone at parse time. This means that we are not necessarily using the correct
version.

Instead, one should always call `TimeZone` directly.
"""
function tzdata_context(f::Function, version::AbstractString)
return try
withenv("JULIA_TZ_VERSION" => version) do
# We need to re-build TimeZones to ensure that we use the correct version.
@assert TimeZones.TZData.tzdata_version() == version
TimeZones.build()
f()
end
finally
# At this point the version should have been re-set. We must re-build the
# TimeZones library to use this other version.
TimeZones.build()
end
end

# These test locations are duplicated from https://github.com/jannikmi/timezonefinder
# under the MIT license.
const TEST_LOCATIONS =
Expand Down Expand Up @@ -111,18 +139,18 @@ const TEST_LOCATIONS =

@testset "TimeZoneFinder.jl" begin
# We run all the tests twice. The first time they are run we ensure that we are
# generating a fresh binary cache file. The second time, we ensure that the in-memory
# Memoize cache is cleared, but read from the binary file.
# generating a fresh binary cache file. The second time, we ensure that the
# in-memory Memoize cache is cleared, but read from the binary file.

# Clear binary cache directory.
rm(TimeZoneFinder._scratch_dir(TimeZoneFinder.LATEST_RELEASE); recursive=true)
rm(TimeZoneFinder._scratch_dir(tzdata_version()); recursive=true)

for read_from_cache in (false, true)
# Clear memoize cache.
empty!(memoize_cache(TimeZoneFinder.load_data))

# Ensure that binary cache either exists or doesn't exist as we expect.
cache_path = TimeZoneFinder._cache_path(TimeZoneFinder.LATEST_RELEASE)
cache_path = TimeZoneFinder._cache_path(tzdata_version())
@test read_from_cache == isfile(cache_path)

@testset "basic (read_from_cache=$read_from_cache)" begin
Expand Down Expand Up @@ -152,4 +180,21 @@ const TEST_LOCATIONS =
end
end
end

@testset "old tzdata versions" begin
# Run for several tzdata versions that we should be able to support.
for version in ["2021c", "2022d", "2022f"]
tzdata_context(version) do
@test timezone_at(52.5061, 13.358) == TimeZone("Europe/Berlin")
end
end

# We can verify that certain things change as expected over time.
tzdata_context("2021c") do
@test timezone_at(50.438114, 30.5179595) == TimeZone("Europe/Kiev")
end
tzdata_context("2022b") do
@test timezone_at(50.438114, 30.5179595) == TimeZone("Europe/Kyiv")
end
end
end