UTC-offset lookup for a small, hand-coded set of timezones in Standard ML. Converts a civil timestamp to a UTC offset (in minutes) using exact, weekday-based DST rules — no system calls, no file I/O, and not the full IANA database.
Supported zones: UTC, America/New_York, America/Chicago,
America/Denver, America/Los_Angeles, Europe/London, Europe/Paris,
Europe/Berlin, Asia/Kolkata, Asia/Tokyo, Asia/Shanghai. Any other name
falls back to UTC (offset 0).
(* UTC offset in minutes for a given civil instant *)
val off : int = Tzdb.offsetMinutes "America/New_York"
{ year=2024, month=7, day=4, hour=12, minute=0 }
(* -240 (EDT = UTC-4, expressed as -240 minutes) *)
(* Human-readable offset string *)
val s : string = Tzdb.utcOffset "Asia/Kolkata"
{ year=2024, month=1, day=1, hour=0, minute=0 }
(* "+0530" *)
(* Is daylight saving time in effect? *)
val d : bool = Tzdb.isDst "America/New_York"
{ year=2024, month=3, day=10, hour=12, minute=0 } (* true *)
(* Zone abbreviation for the instant *)
val a : string = Tzdb.abbreviation "America/New_York"
{ year=2024, month=1, day=15, hour=12, minute=0 } (* "EST" *)
(* Discovery *)
val zs : string list = Tzdb.listZones ()
val k : bool = Tzdb.knownZone "Asia/Tokyo" (* true *)type zone = string (* IANA zone name, e.g. "America/Chicago" *)
type instant = { year : int, month : int, day : int,
hour : int, minute : int }- Not the IANA database. Only the zones listed above are known, and their
rules are hand-coded, not loaded from
tzdata. Historical rule changes (e.g. pre-2007 US DST dates) are not represented — the current rules are applied to every year. - Exact transitions, current rules. US DST = 2nd Sunday of March 02:00 to 1st Sunday of November 02:00; EU DST = last Sunday of March to last Sunday of October, computed with a real weekday calculation (Sakamoto). Instants within the one-hour transition window are not specially disambiguated.
- Fixed-offset zones (
Asia/Kolkata+05:30,Asia/Tokyo+09:00,Asia/Shanghai+08:00,UTC) have no DST and are always exact. - Civil instants are interpreted as wall-clock time in the given zone.
- Zone names are case-sensitive; unknown zones return offset 0 / abbreviation
"UTC"andisDst = falserather than raising.monthis 1-indexed.
smlpkg add github.com/sjqtentacles/sml-tzdb
smlpkg syncReference from your .mlb:
lib/github.com/sjqtentacles/sml-tzdb/tzdb.mlb
make test # MLton
make test-poly # Poly/ML
make all-tests # both
make cleanmake example builds and runs examples/demo.sml, which
looks up UTC offsets, DST status, and abbreviations for seven zones at two
fixed civil instants — no system clock is read (output is byte-identical
under MLton and Poly/ML):
=== sml-tzdb demo ===
known zones = 11
knownZone "Asia/Tokyo" = true
knownZone "Mars/Olympus" = false
-- 2024-07-04 12:00 (northern-hemisphere summer) --
UTC offset=+0000 (0 min) dst=false abbr=UTC
America/New_York offset=-0400 (~240 min) dst=true abbr=EDT
America/Los_Angeles offset=-0700 (~420 min) dst=true abbr=PDT
Europe/London offset=+0100 (60 min) dst=true abbr=BST
Europe/Berlin offset=+0200 (120 min) dst=true abbr=CEST
Asia/Kolkata offset=+0530 (330 min) dst=false abbr=IST
Asia/Tokyo offset=+0900 (540 min) dst=false abbr=JST
-- 2024-01-15 12:00 (northern-hemisphere winter) --
UTC offset=+0000 (0 min) dst=false abbr=UTC
America/New_York offset=-0500 (~300 min) dst=false abbr=EST
America/Los_Angeles offset=-0800 (~480 min) dst=false abbr=PST
Europe/London offset=+0000 (0 min) dst=false abbr=GMT
Europe/Berlin offset=+0100 (60 min) dst=false abbr=CET
Asia/Kolkata offset=+0530 (330 min) dst=false abbr=IST
Asia/Tokyo offset=+0900 (540 min) dst=false abbr=JST
sml.pkg
Makefile
lib/github.com/sjqtentacles/sml-tzdb/
tzdb.sig TZDB signature
tzdb.sml compiled timezone rules + offset lookup
tzdb.mlb
test/
test.sml known-city offset tests (winter and summer)
MIT. See LICENSE.