gtz

Simple Gleam time zone conversions for all targets, built on top of tzif.

Converting an ambiguous calendar time to a timestamp assumes the first occurrence of the ambiguous time. Accounting for leap seconds depends on the dataset. If you need more control over how ambiguous dates are handled or the designation of the given time in the zone, use the tzif package directly.

The build function reads the host’s own time zone data. When the host has none, or when it is somewhere other than where the host usually stores it, supply a database yourself with build_from using the very nice tzif or zones packages. If you are running this in a bare environment with no time zone data available, the zones package is recommended.

import gleam/time/timestamp
import gtz

let zone_name = gtz.local_name() // "Asia/Kolkata"
let assert Ok(zone) = gtz.build(zone_name)

timestamp.from_unix_seconds(1_729_257_776)
|> gtz.to_calendar(zone)
// -> #(
//   calendar.Date(2024, calendar.October, 18),
//   calendar.TimeOfDay(18, 52, 56, 0),
//   duration.seconds(19_800),
// )

Types

Data for an IANA time zone. Build with build, then hand to to_calendar or from_calendar.

Examples

gtz.build("Asia/Kolkata")
// -> Ok(TimeZone)
pub opaque type TimeZone

Values

pub fn build(name: String) -> Result(TimeZone, Nil)

Build a TimeZone from an IANA zone name such as "America/New_York". Returns an error if the name is not a zone the host recognizes.

On the Erlang target, the operating system’s TZif database at /usr/share/zoneinfo is read once and memoized into a persistent term. A host with no zoneinfo tree there, such as a scratch container image, has no zone to recognize and every name fails; use build_from and provide your own database from the very nice tzif or zones packages instead. On JavaScript, information for the given zone is derived from the host’s native Temporal and Intl APIs.

Examples

gtz.local_name() |> gtz.build
// -> Ok(TimeZone)

gtz.build("Asia/Kolkata")
// -> Ok(TimeZone)

gtz.build("America/NewYork") // "New_York" is the correct name here
// -> Error(Nil)
pub fn build_from(
  name: String,
  database db: database.TzDatabase,
) -> Result(TimeZone, Nil)

Build a TimeZone from an IANA zone name and a tzif database of your own. Returns an error if the database holds no usable data for the name.

Use this when working in a bare environment (such as a scratch container image on the Erlang target), a host that keeps its zoneinfo somewhere other than /usr/share/zoneinfo, or you need precise control over the dataset.

The zones package ships a prebuilt database and needs no files at all, which suits bare environments. tzif itself can load from any directory, which suits a non-standard location or a database you compiled yourself.

Examples

import zones

let assert Ok(zone) = gtz.build_from("Asia/Kolkata", zones.database())
// -> Ok(TimeZone)
import tzif/database

let assert Ok(db) = database.load_from_path("/opt/zoneinfo")
let assert Ok(zone) = gtz.build_from("Asia/Kolkata", db)
// -> Ok(TimeZone)
pub fn from_calendar(
  date: calendar.Date,
  time: calendar.TimeOfDay,
  zone: TimeZone,
) -> Result(timestamp.Timestamp, Nil)

Convert a date and time of day in zone to a timestamp. When you know the offset, always prefer the timestamp.from_calendar in gleam_time over this function.

When the time is ambiguous in the given time zone because of an offset change such as daylight saving time, the timestamp that corresponds with the first occurrence of that time is returned. When the time does not exist in the given time zone because of an offset change such as daylight saving time, Error(Nil) is returned.

Examples

let assert Ok(zone) = gtz.build("Australia/Lord_Howe")

// Clocks go back half an hour at 02:00, so 01:30 to 01:59 happens twice
gtz.from_calendar(
  calendar.Date(2025, calendar.April, 6),
  calendar.TimeOfDay(1, 45, 0, 0),
  zone,
)
// -> Ok(Timestamp(1_743_864_300, 0)), the earlier of the two timestamps

// Clocks go forward from 02:00 to 02:30, so 02:15 never happens
gtz.from_calendar(
  calendar.Date(2025, calendar.October, 5),
  calendar.TimeOfDay(2, 15, 0, 0),
  zone,
)
// -> Error(Nil)
pub fn local_name() -> String

Returns the name of the host system’s time zone.

Examples

gtz.local_name()
// -> "Pacific/Auckland"

On the Erlang target the zone is read from the operating system, in order: the TZ environment variable, the symlink target of /etc/localtime, then /etc/timezone or /etc/sysconfig/clock. If none of those yield an IANA zone name, "UTC" is returned. On JavaScript the host’s Intl API is used.

pub fn to_calendar(
  timestamp: timestamp.Timestamp,
  zone: TimeZone,
) -> #(calendar.Date, calendar.TimeOfDay, duration.Duration)

Convert a timestamp to the equivalent date, time of day, and UTC offset in the given time zone.

Examples

let assert Ok(zone) = gtz.build("Asia/Kathmandu")

// Not every zone is a whole number of hours from UTC
timestamp.from_unix_seconds(1_729_257_776)
|> gtz.to_calendar(zone)
// -> #(
//   calendar.Date(2024, calendar.October, 18),
//   calendar.TimeOfDay(19, 7, 56, 0),
//   duration.seconds(20_700),
// )
Search Document