-
Notifications
You must be signed in to change notification settings - Fork 829
fix: prioritize TZ and TZDIR over /etc/localtime in TZDB search #13565
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
Open
algebraic-dev
wants to merge
1
commit into
master
Choose a base branch
from
sofia/time-tz-fallback
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+100
−26
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -26,23 +26,17 @@ structure TZdb where | |||||
| The path to the local timezone file. This is typically a symlink to a file within the timezone | ||||||
| database that corresponds to the current local time zone. | ||||||
| -/ | ||||||
| localPath : System.FilePath := "/etc/localtime" | ||||||
| localPath : System.FilePath | ||||||
|
|
||||||
| /-- | ||||||
| All the possible paths to the directories containing all available time zone files. These files define various | ||||||
| time zones and their rules. | ||||||
| -/ | ||||||
| zonesPaths : Array System.FilePath := #["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"] | ||||||
| zonesPaths : Array System.FilePath | ||||||
|
|
||||||
| namespace TZdb | ||||||
| open TimeZone | ||||||
|
|
||||||
| /-- | ||||||
| Returns a default `TZdb` instance with common timezone data paths for most Linux distributions and macOS. | ||||||
| -/ | ||||||
| @[inline] | ||||||
| def default : TZdb := {} | ||||||
|
|
||||||
| /-- | ||||||
| Parses binary timezone data into zone rules based on a given timezone ID. | ||||||
| -/ | ||||||
|
|
@@ -82,21 +76,101 @@ def localRules (path : System.FilePath) : IO ZoneRules := do | |||||
| Reads timezone rules from disk based on the provided file path and timezone ID. | ||||||
| -/ | ||||||
| def readRulesFromDisk (path : System.FilePath) (id : String) : IO ZoneRules := do | ||||||
| parseTZIfFromDisk (System.FilePath.join path id) id | ||||||
| parseTZIfFromDisk (path.join id) id | ||||||
|
|
||||||
| instance : Std.Time.Database TZdb where | ||||||
| getLocalZoneRules db := localRules db.localPath | ||||||
| /-- | ||||||
| Parsed form of the `TZ` environment variable. | ||||||
| -/ | ||||||
| inductive TZSpec | ||||||
|
|
||||||
| /-- | ||||||
| A file path (the part after the leading `:`), absolute or relative to a zoneinfo directory. | ||||||
| -/ | ||||||
| | filePath (p : String) | ||||||
|
|
||||||
| /-- | ||||||
| A timezone ID such as `America/New_York`, looked up in the zoneinfo search paths. | ||||||
| -/ | ||||||
| | zoneId (id : String) | ||||||
| deriving Repr, BEq | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| /-- | ||||||
| Parses the value of the `TZ` environment variable. Returns `none` for empty or | ||||||
| `:` (empty path after the colon) values. | ||||||
| -/ | ||||||
| def parseTZValue (tz : String) : Option TZSpec := | ||||||
| if tz.startsWith ":" then | ||||||
| let p := (tz.drop 1).toString | ||||||
| if p.isEmpty then none else some (.filePath p) | ||||||
| else if !tz.isEmpty then | ||||||
| some (.zoneId tz) | ||||||
| else | ||||||
| none | ||||||
|
|
||||||
| /-- | ||||||
| Returns the first path in `searchPaths` joined with `rel` that exists on disk, or `none`. | ||||||
| -/ | ||||||
| def findInPaths (searchPaths : Array System.FilePath) (rel : String) : IO (Option System.FilePath) := do | ||||||
| for base in searchPaths do | ||||||
| let full := base.join rel | ||||||
| if ← full.pathExists then | ||||||
| return some full | ||||||
|
|
||||||
| return none | ||||||
|
|
||||||
| /-- | ||||||
| Resolves the local timezone file path from the `TZ` environment variable, searching | ||||||
| `zonesPaths` for relative paths and timezone IDs. Falls back to `/etc/localtime` if | ||||||
| `TZ` is unset. Throws if `TZ` is set but its value cannot be found on disk. | ||||||
| -/ | ||||||
| def resolveLocalPath (zonesPaths : Array System.FilePath) : IO System.FilePath := do | ||||||
| let some tz := ← IO.getEnv "TZ" | ||||||
| | return "/etc/localtime" | ||||||
|
|
||||||
| let some spec := parseTZValue tz | ||||||
| | return "/etc/localtime" | ||||||
|
|
||||||
| match spec with | ||||||
| | .filePath p => | ||||||
| if p.startsWith "/" then return p | ||||||
| if let some path := ← findInPaths zonesPaths p then return path | ||||||
| throw <| IO.userError s!"TZ='{tz}': path '{p}' not found in any zoneinfo directory" | ||||||
| | .zoneId id => | ||||||
| if let some path := ← findInPaths zonesPaths id then return path | ||||||
| throw <| IO.userError s!"TZ='{tz}': timezone not found in any zoneinfo directory" | ||||||
|
|
||||||
| /-- | ||||||
| Returns a `TZdb` initialized from the `TZ` and `TZDIR` environment variables, with | ||||||
| common fallback paths for Linux and macOS. Call this once at program startup. | ||||||
| -/ | ||||||
| def default : IO TZdb := do | ||||||
| let defs : Array System.FilePath := #["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"] | ||||||
|
|
||||||
| let zonesPaths ← match ← IO.getEnv "TZDIR" with | ||||||
| | none | some "" => pure defs | ||||||
| | some d => | ||||||
| if ← System.FilePath.pathExists d then pure #[d] | ||||||
| else throw <| IO.userError s!"TZDIR='{d}': directory not found" | ||||||
|
|
||||||
| getZoneRules db id := do | ||||||
| let env ← IO.getEnv "TZDIR" | ||||||
| let localPath ← resolveLocalPath zonesPaths | ||||||
| return { localPath, zonesPaths } | ||||||
|
|
||||||
| /-- | ||||||
| Retrieves the timezone rules for the local timezone using `db.localPath`. | ||||||
| -/ | ||||||
| def getLocalZoneRules (db : TZdb) : IO ZoneRules := | ||||||
| localRules db.localPath | ||||||
|
|
||||||
| if let some path := env then | ||||||
| let result ← readRulesFromDisk path id | ||||||
| return result | ||||||
| /-- | ||||||
| Retrieves the timezone rules for the given timezone ID by searching `db.zonesPaths`. | ||||||
| -/ | ||||||
| def getZoneRules (db : TZdb) (id : String) : IO ZoneRules := do | ||||||
| for base in db.zonesPaths do | ||||||
| if ← (base.join id).pathExists then | ||||||
| return ← readRulesFromDisk base id | ||||||
|
|
||||||
| for path in db.zonesPaths do | ||||||
| if ← System.FilePath.pathExists path then | ||||||
| let result ← readRulesFromDisk path id | ||||||
| return result | ||||||
| throw <| IO.userError s!"cannot find {id} in the local timezone database" | ||||||
|
|
||||||
| throw <| IO.userError s!"cannot find {id} in the local timezone database" | ||||||
| instance : Std.Time.Database TZdb where | ||||||
| getLocalZoneRules db := db.getLocalZoneRules | ||||||
| getZoneRules db id := db.getZoneRules id | ||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Specifying-the-Time-Zone-with-TZ there is also the proleptic format. Should we document that this is not supported?