std.datetimezone
Time zones: which offset from UTC (std.offset) the clocks of a place
are at when — +08:00 in Taipei all year round, -05:00 in New York in
winter and -04:00 in summer — and so which instant a date and time
on its clocks (std.localdatetime) is. NodaTime’s DateTimeZone is
the model. A zone is its id, Asia/Taipei, and its rules: the
changes of the tz database, each an instant and the offset after it,
and a POSIX TZ rule for the years after the last of them. fromTZif
reads a zone from the bytes of a TZif file (RFC 8536), as the tz
database is kept on the system, std.tzdb reading them; fromPosix
makes one from a POSIX rule alone, fixed from an offset.
An instant has one offset in a zone (offsetAt), but a date and
time on its clocks may have none, where the clocks skipped over it,
or two, where they went back over it (mapLocal): std.zoneddatetime
resolves it, the program saying which instant each of those becomes.
A leap second is no second here, as POSIX time has none. Import it
by a name, import std.datetimezone as DateTimeZone, as its names are
short.
import std.datetimezone as DateTimeZoneimport std.instant as Instantimport std.offset as Offset
-- | the offset in New York at an instant, by its rule-- | >>> inNewYork Instant.epoch-- | "-05:00"inNewYork : Instant.Instant -> StringinNewYork t = maybe "" (\z -> show (DateTimeZone.offsetAt z t)) (DateTimeZone.fromPosix "EST5EDT,M3.2.0,M11.1.0")DateTimeZone
data DateTimeZoneA time zone: its id, the offset before its first change, its changes, and the rule after the last of them, if there is one.
Mapping
data Mapping = Unique Offset.Offset | Skipped Offset.Offset Offset.Offset Instant.Instant | Ambiguous Offset.Offset Offset.OffsetWhat a date and time on a zone’s clocks is there: one instant, at an offset; none, where the clocks skipped over it, the offsets before and after the gap and the instant it ends at; or two, where they went back over it, the offsets of the earlier and the later.
utc
utc : DateTimeZoneUTC’s own, with no change: UTC.
id
id : DateTimeZone -> StringIts id: Asia/Taipei.
fixed
fixed : Offset.Offset -> DateTimeZoneThe zone always at the offset, with no change, as NodaTime’s
ForOffset: its id UTC and the offset, UTC+08:00, or UTC for
none.
>>> List.map (\n -> maybe "" (\o -> id (fixed o)) (Offset.fromSeconds n)) [0, 28800, -34200]["UTC", "UTC+08:00", "UTC-09:30"]instanceEqualityDateTimeZone
instance instanceEqualityDateTimeZone : Equality DateTimeZoneThe same id, as NodaTime has it.
>>> fixed Offset.utc == utcTrueinstanceDebugDateTimeZone
instance instanceDebugDateTimeZone : Debug DateTimeZoneAs show writes it: its id.
instanceShowDateTimeZone
instance instanceShowDateTimeZone : Show DateTimeZoneIts id, Asia/Taipei.
>>> show utc"UTC"instanceEqualityMapping
instance instanceEqualityMapping : Equality MappingThe same, at the same offsets.
instanceDebugMapping
instance instanceDebugMapping : Debug MappingAs show writes it.
instanceShowMapping
instance instanceShowMapping : Show MappingIts tag, then its offsets and its instant as they write them:
Skipped -05:00 -04:00 2024-03-10T07:00:00Z.
fromPosix
fromPosix : String -> Maybe DateTimeZoneThe zone of a POSIX TZ rule, EST5EDT,M3.2.0,M11.1.0, its id the
rule: a name of three letters or more (or quoted, <+08>), its
offset west of UTC (5 is -05:00, -8 is +08:00), then perhaps a
daylight name, its offset (an hour ahead if none), and when each
starts, Jn, n or Mm.w.d, perhaps a / and a time (02:00:00 if
none), as RFC 8536 has them. With a daylight name and no changes,
the United States’, as the tz code has them. Nothing if it writes
none, or an offset beyond 18 hours.
>>> List.map (\s -> maybe "" id (fromPosix s)) ["CST-8", "<+0545>-5:45", "AEST-10AEDT,M10.1.0,M4.1.0/3"]["CST-8", "<+0545>-5:45", "AEST-10AEDT,M10.1.0,M4.1.0/3"]>>> (List.map (\s -> maybe True (\_ -> False) (fromPosix s)) ["", "CS-8", "CST", "CST-8CDT,M3.2.0", "CST-8CDT,M13.2.0,M11.1.0", "CST-8CDT,J0,J365", "CST19", "<+08-8"] : List Bool)[True, True, True, True, True, True, True, True]offsetAt
offsetAt : DateTimeZone -> Instant.Instant -> Offset.OffsetThe offset of the zone at the instant.
>>> maybe "" (\z -> joinWith " " (List.map (\s -> show (offsetAt z (Instant.fromUnixNanoseconds (s * 1000000000)))) [1710054000, 1710053999, 1730613600, 1730613599])) (fromPosix "EST5EDT,M3.2.0,M11.1.0")"-04:00 -05:00 -05:00 -04:00">>> List.map (\s -> maybe "" (\z -> show (offsetAt z Instant.epoch)) (fromPosix s)) ["CST-8", "<+0545>-5:45", "EST5EDT", "AEST-10AEDT,M10.1.0,M4.1.0/3", "<-03>3<-02>,M3.5.0/-2,M10.5.0/-1"]["+08:00", "+05:45", "-05:00", "+11:00", "-03:00"]mapLocal
mapLocal : DateTimeZone -> LocalDateTime.LocalDateTime -> MappingWhat the date and time on the zone’s clocks is: at one offset, the
one instant (Unique); at none, where the clocks skipped over it
(Skipped, the offsets before and after the gap, and the instant
the gap ends at); at two, where the clocks went back over it
(Ambiguous, the offset of the earlier instant, then the later’s).
>>> maybe [] (\z -> List.map (\s -> maybe "" (\t -> show (mapLocal z t)) (LocalDateTime.parse s)) ["2024-03-10T01:59:59", "2024-03-10T02:30:00", "2024-03-10T03:00:00", "2024-11-03T01:30:00", "2024-11-03T02:00:00"]) (fromPosix "EST5EDT,M3.2.0,M11.1.0")["Unique -05:00", "Skipped -05:00 -04:00 2024-03-10T07:00:00Z", "Unique -04:00", "Ambiguous -04:00 -05:00", "Unique -05:00"]fromTZif
fromTZif : String -> Bytes -> Either String DateTimeZoneThe zone the bytes of a TZif file are (RFC 8536), as the tz database
is kept on a system, its id name: of version 1, by its changes of 4
bytes; of version 2 and on, by its changes of 8 bytes and its footer’s
POSIX rule for after them. Its leap seconds are not read. Why not
if the bytes are none, or an offset is beyond 18 hours.
>>> either (\why -> why) (\z -> joinWith " " (List.map (\n -> show (offsetAt z (Instant.fromUnixNanoseconds n))) [-1, 0, 4102444800000000000])) (fromTZif "Etc/Example" b["TZif2", 0::64, 0::56, 0::32, 0::32, 0::32, 1::32, 2::32, 8::32, 0::32, 1, 29160::32, 0, 0, 28800::32, 0, 4, "LMT", 0, "CST", 0, "TZif2", 0::64, 0::56, 0::32, 0::32, 0::32, 1::32, 2::32, 8::32, 0::64, 1, 29160::32, 0, 0, 28800::32, 0, 4, "LMT", 0, "CST", 0, "\nCST-8\n"])"+08:06 +08:00 +08:00">>> List.map (\b -> either (\why -> why) id (fromTZif "x" b)) [Bytes.fromString "TZ", b["TZif", 0::64, 0::64, 0::64, 0::64, 0::64]]["no TZif header", "TZif with no local time type"]