Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ benchmark/tune.json
# Pkg
deps/build.log
/Manifest.toml

# VSCode settings
.vscode/settings.json
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
InlineStrings = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
Mocking = "78c3b35d-d492-501b-9361-3d52fe80e533"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"
Expand All @@ -23,6 +24,7 @@ Dates = "1"
Downloads = "1"
InlineStrings = "1"
Mocking = "0.7, 0.8"
PrecompileTools = "1"
Printf = "1"
RecipesBase = "0.7, 0.8, 1"
Scratch = "1.1.1"
Expand Down
54 changes: 42 additions & 12 deletions src/TimeZones.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module TimeZones

using Artifacts: Artifacts
using Dates
using PrecompileTools: @compile_workload, @setup_workload
using Printf
using Scratch: @get_scratch!
using Unicode
Expand Down Expand Up @@ -40,20 +41,22 @@ const _COMPILED_DIR = Ref{String}()
# abstract type UTC <: TimeZone end # Already defined in the Dates stdlib
abstract type Local <: TimeZone end

# Resolve the TZJData artifact directory using direct function calls that the
# juliac trimmer can trace. We intentionally avoid TZJData.artifact_dir() which
# uses the @artifact_str macro — that macro expands to Base.invokelatest(...),
# an opaque dynamic dispatch barrier the trimmer cannot follow through, causing
# the callee to be removed from trimmed binaries.
function _resolve_tzjdata_dir()
pkg = Base.identify_package(TZJData, "TZJData")
pkg_dir = dirname(dirname(Base.locate_package(pkg)))
artifact_dict = Artifacts.parse_toml(joinpath(pkg_dir, "Artifacts.toml"))
hash = Base.SHA1(artifact_dict["tzjdata"]["git-tree-sha1"])
return Artifacts.artifact_path(hash)
end

function __init__()
# Set at runtime to ensure relocatability
_COMPILED_DIR[] = @static if isdefined(TZJData, :artifact_dir)
TZJData.artifact_dir()
else
# Backwards compatibility for TZJData versions below v1.3.1. The portion of the
# code which determines the `pkg_dir` could be replaced by `pkgdir(TZJData)` however
# the `pkgdir` function doesn't work well with relocated system images.
pkg = Base.identify_package(TZJData, "TZJData")
pkg_dir = dirname(dirname(Base.locate_package(pkg)))
artifact_dict = Artifacts.parse_toml(joinpath(pkg_dir, "Artifacts.toml"))
hash = Base.SHA1(artifact_dict["tzjdata"]["git-tree-sha1"])
Artifacts.artifact_path(hash)
end
_COMPILED_DIR[] = _resolve_tzjdata_dir()

# Dates extension needs to happen everytime the module is loaded (issue #24)
init_dates_extension()
Expand Down Expand Up @@ -97,4 +100,31 @@ if !isdefined(Base, :get_extension)
include("../ext/TimeZonesRecipesBaseExt.jl")
end

# Ensure methods used in __init__ are preserved by juliac --trim (requires Julia >= 1.12).
# The trimmer only keeps methods reachable from @ccallable entry points and precompiled
# methods. This workload exercises the __init__ code paths so the trimmer retains them.
# On Julia < 1.9, @compile_workload still executes the code but doesn't cache native code
@setup_workload begin
@compile_workload begin
# Preserve artifact resolution methods needed by __init__.
_resolve_tzjdata_dir()

@static if VERSION >= v"1.9"
# Also sets _COMPILED_DIR so the TimeZone("UTC") call below can load tzdata.
_COMPILED_DIR[] = _resolve_tzjdata_dir()
Copy link
Copy Markdown
Contributor

@giordano giordano Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the internals of the package, but in general backing at precompile time constants which are supposed to be evaluated at runtime is a bad idea. Or that's changed again at runtime?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# Exercise core timezone functionality so timezone loading methods
# are also preserved.
TimeZone("UTC")

# Reset the cache so it doesn't persist into the loaded module with
# stale data from the precompilation environment. Without this, the
# cache's `initialized` flag stays true and `_build()` is never called
# at runtime — breaking `JULIA_TZ_VERSION` overrides and any scenario
# where the compiled tzdata differs from what was available here.
copy!(_TZ_CACHE, TimeZoneCache())
end
end
end

end # module
Loading