DetermineTimeZoneOffset (datetime.c:1575-1733): a local date and
time plus a zone to an OFFSET.
This is a separate step from parsing because an offset is a function of the INSTANT, and the instant is only known once the date has been decoded and validated.
THE AMBIGUITY RULE IS THE OPPOSITE OF java.time's, in both
directions, and this is the whole reason the namespace exists rather
than a call to ZonedDateTime/of:
spring forward (a GAP, the local time never happened) PostgreSQL takes the BEFORE offset and keeps the local fields java.time shifts the time forward and takes the AFTER offset
fall back (an OVERLAP, the local time happened twice) PostgreSQL takes the AFTER offset java.time takes the earlier, BEFORE offset
datetime.c:1697-1706 states the rule and why it is not phrased as "prefer standard time": that older rule could not resolve zones where both readings report as standard time -- Europe/Moscow in October 2014 -- and in zones like Europe/Dublin there is widespread disagreement about which offset is "standard" at all.
An hour wrong, twice a year, with no error, is exactly the failure this port is meant to remove, so it is implemented from the C's branches rather than from the convenience method.
SIGN: everything here returns and accepts SECONDS WEST, as PostgreSQL stores internally.
`DetermineTimeZoneOffset` (datetime.c:1575-1733): a local date and
time plus a zone to an OFFSET.
This is a separate step from parsing because an offset is a function
of the INSTANT, and the instant is only known once the date has been
decoded and validated.
THE AMBIGUITY RULE IS THE OPPOSITE OF `java.time`'s, in both
directions, and this is the whole reason the namespace exists rather
than a call to `ZonedDateTime/of`:
spring forward (a GAP, the local time never happened)
PostgreSQL takes the BEFORE offset and keeps the local fields
java.time shifts the time forward and takes the AFTER offset
fall back (an OVERLAP, the local time happened twice)
PostgreSQL takes the AFTER offset
java.time takes the earlier, BEFORE offset
datetime.c:1697-1706 states the rule and why it is not phrased as
"prefer standard time": that older rule could not resolve zones
where both readings report as standard time -- Europe/Moscow in
October 2014 -- and in zones like Europe/Dublin there is widespread
disagreement about which offset is "standard" at all.
An hour wrong, twice a year, with no error, is exactly the failure
this port is meant to remove, so it is implemented from the C's
branches rather than from the convenience method.
SIGN: everything here returns and accepts SECONDS WEST, as
PostgreSQL stores internally.(determine-abbrev-offset tm zone-name)DetermineTimeZoneAbbrevOffset (datetime.c:1748-1779) for a DYNTZ
abbreviation -- one that names a zone instead of carrying an offset.
PARTIAL, and deliberately so. The C first asks the zone's own transition data whether the abbreviation as WRITTEN matches at that instant, and uses that offset if it does; only on a miss does it fall back to the zone's offset. Java exposes no abbreviation strings from tzdb, so only the fallback is implementable here without shipping the tzdb text ourselves.
What that costs: writing an abbreviation that disagrees with the
zone's state at the instant -- '2000-01-01 12:00 ARST', summer
time in Buenos Aires named on a winter date -- gets the zone's
offset for that date rather than the abbreviation's own. The
abbreviation still selects the right ZONE, so it is wrong only when
the two disagree, and only for the 50 DYNTZ entries of 195. Recorded
in doc/review-backlog.md rather than papered over.
`DetermineTimeZoneAbbrevOffset` (datetime.c:1748-1779) for a DYNTZ abbreviation -- one that names a zone instead of carrying an offset. PARTIAL, and deliberately so. The C first asks the zone's own transition data whether the abbreviation as WRITTEN matches at that instant, and uses that offset if it does; only on a miss does it fall back to the zone's offset. Java exposes no abbreviation strings from tzdb, so only the fallback is implementable here without shipping the tzdb text ourselves. What that costs: writing an abbreviation that disagrees with the zone's state at the instant -- `'2000-01-01 12:00 ARST'`, summer time in Buenos Aires named on a winter date -- gets the zone's offset for that date rather than the abbreviation's own. The abbreviation still selects the right ZONE, so it is wrong only when the two disagree, and only for the 50 DYNTZ entries of 195. Recorded in doc/review-backlog.md rather than papered over.
(determine-offset {:keys [year mon mday hour min sec]} z)DetermineTimeZoneOffsetInternal. The local fields and a zone to
SECONDS WEST.
Three cases, and the last two are where this differs from
ZonedDateTime/of:
one valid offset use it none (a gap) the BEFORE offset, local fields unchanged two (an overlap) the AFTER offset
An out-of-range date is not an error: the C falls through to
overflow and assumes UTC (datetime.c:1726-1730).
`DetermineTimeZoneOffsetInternal`. The local fields and a zone to SECONDS WEST. Three cases, and the last two are where this differs from `ZonedDateTime/of`: one valid offset use it none (a gap) the BEFORE offset, local fields unchanged two (an overlap) the AFTER offset An out-of-range date is not an error: the C falls through to `overflow` and assumes UTC (datetime.c:1726-1730).
(fixed-offset z)pg_get_timezone_offset: the zone's offset if it has exactly one for
all time, else nil. DecodeTimeOnly needs this -- a time with no
date can take a zone only when the answer does not depend on the
date, so '12:00 UTC'::timetz works and
'12:00 America/New_York'::timetz does not.
`pg_get_timezone_offset`: the zone's offset if it has exactly one for all time, else nil. `DecodeTimeOnly` needs this -- a time with no date can take a zone only when the answer does not depend on the date, so `'12:00 UTC'::timetz` works and `'12:00 America/New_York'::timetz` does not.
(posix-offset name)GMT+8 and friends, as SECONDS WEST -- or nil if name is not one.
THE SIGN IS INVERTED relative to everything else, and this is not a
quirk of ours: in a POSIX TZ string the offset is "the value added
to local time to arrive at UTC", so GMT+8 is eight hours BEHIND
UTC. The oracle agrees -- '2000-01-01 12:00:00 GMT+8'::timestamptz
is 20:00:00+00.
Java reads it the other way. ZoneId/of("GMT+8") is +08:00, so
routing these through ZoneId gives an answer sixteen hours out
with no error. That is why this is checked BEFORE the tzdb lookup
rather than after.
It does not shadow real zone names: PST8PDT and EST5EDT have a
second alpha run and do not match, and Etc/GMT+5 has a /. Those
go to tzdb, which applies the same inversion itself.
`GMT+8` and friends, as SECONDS WEST -- or nil if `name` is not one.
THE SIGN IS INVERTED relative to everything else, and this is not a
quirk of ours: in a POSIX TZ string the offset is "the value added
to local time to arrive at UTC", so `GMT+8` is eight hours BEHIND
UTC. The oracle agrees -- `'2000-01-01 12:00:00 GMT+8'::timestamptz`
is `20:00:00+00`.
Java reads it the other way. `ZoneId/of("GMT+8")` is +08:00, so
routing these through `ZoneId` gives an answer sixteen hours out
with no error. That is why this is checked BEFORE the tzdb lookup
rather than after.
It does not shadow real zone names: `PST8PDT` and `EST5EDT` have a
second alpha run and do not match, and `Etc/GMT+5` has a `/`. Those
go to tzdb, which applies the same inversion itself.(resolve-zone-name name)Cached zone-id. Zone lookup walks every available id on a miss and
a literal can carry any string, so the misses are cached too.
Cached `zone-id`. Zone lookup walks every available id on a miss and a literal can carry any string, so the misses are cached too.
The session zone until SET TimeZone is honoured (expr.clj:1613
reports UTC unconditionally).
The session zone until `SET TimeZone` is honoured (expr.clj:1613 reports UTC unconditionally).
(zone-id name)pg_tzset: a zone NAME to a zone, or nil if there is no such zone.
Case-insensitive, because pg_tzset is and because the lexer has
already lowercased the field. Java's own ids are mixed case, so the
lookup is against a folded index.
ZoneId/of with SHORT_IDS is deliberately NOT used: it maps PST
to America/Los_Angeles, which observes DST, while PostgreSQL's PST
is a fixed -08:00 from the abbreviation table. Abbreviations never
reach this function -- they are resolved earlier, against
tokens/zone-abbrevs -- and routing them here would be the bug.
`pg_tzset`: a zone NAME to a zone, or nil if there is no such zone. Case-insensitive, because `pg_tzset` is and because the lexer has already lowercased the field. Java's own ids are mixed case, so the lookup is against a folded index. `ZoneId/of` with `SHORT_IDS` is deliberately NOT used: it maps `PST` to America/Los_Angeles, which observes DST, while PostgreSQL's `PST` is a fixed -08:00 from the abbreviation table. Abbreviations never reach this function -- they are resolved earlier, against `tokens/zone-abbrevs` -- and routing them here would be the bug.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |