intldate

Date formatting for gleam_time following the JavaScript Intl.DateTimeFormat() API

Works on both the JavaScript and Erlang runtimes

On JavaScript it delegates to the native Intl.DateTimeFormat(), while on Erlang it relies on a pure Gleam reimplementation that mirrors the same behaviour, so you get consistent results whichever target you compile to.

Based on ICU 78.3 data, which includes CLDR 78.2 and tzdata 2026a (2026-mar).

Package Version JavaScript Compatible Erlang Compatible Hex Docs

gleam add intldate@2
import gleam/option
import gleam/time/timestamp
import intldate

pub fn main() {
  let assert Ok(date) = timestamp.parse_rfc3339("2026-02-24T17:48:22+04:00")

  let result =
    intldate.format(
      date:,
      time_zone: option.Some("Indian/Reunion"),
      locale: option.Some("fr-FR"),
      config: intldate.new()
        |> intldate.with_weekday(intldate.WeekdayLong)
        |> intldate.with_year(intldate.YearNumeric)
        |> intldate.with_month(intldate.MonthLong)
        |> intldate.with_day(intldate.DayNumeric)
        |> intldate.with_hour(intldate.HourNumeric)
        |> intldate.with_minute(intldate.MinuteNumeric),
    )

  // result == "mardi 24 février 2026 à 17:48"
}

Relative time formatting

The intlrelative module formats durations as human-readable relative times following the JavaScript Intl.RelativeTimeFormat() API. On JavaScript it delegates to the native Intl.RelativeTimeFormat(), while on Erlang it relies on the same pure Gleam reimplementation, so the output stays consistent whichever target you compile to.

A negative duration is formatted as a time in the past and a positive duration as a time in the future. The unit you pass selects which unit the duration is expressed in.

import gleam/option
import gleam/time/duration
import intlrelative

pub fn main() {
  let result =
    intlrelative.format(
      duration: duration.seconds(-5),
      unit: intlrelative.Second,
      locale: option.Some("fr-FR"),
      config: intlrelative.new(),
    )

  // result == "il y a 5 secondes"
}

Error handling

intldate.format never fails: if the time zone, locale, or calendar cannot be resolved, it returns a human-readable, English-only message describing the error (via intldate.describe_error), regardless of the requested locale. The same holds for intlrelative.format, which returns such a message if the locale cannot be resolved.

If you’d rather handle the error yourself, use intldate.try_format (or intlrelative.try_format), which returns a Result(String, intldate.IntlError):

import gleam/option
import gleam/time/timestamp
import intldate

pub fn main() {
  let assert Ok(date) = timestamp.parse_rfc3339("2026-02-24T17:48:22+04:00")

  let result =
    intldate.try_format(
      date:,
      time_zone: option.Some("Invalid/TimeZone"),
      locale: option.Some("fr-FR"),
      config: intldate.new()
        |> intldate.with_year(intldate.YearNumeric)
        |> intldate.with_month(intldate.MonthLong)
        |> intldate.with_day(intldate.DayNumeric),
    )

  // result == Error(intldate.FailedToLoadTimeZone("Invalid/TimeZone"))
}

More than just format

Both modules mirror more of their Intl counterparts than the basic example above:

Each has a try_* variant returning a Result instead of an error message, same as try_format.

Further documentation can be found at https://hexdocs.pm/intldate.

Development

The locale data under priv/ is generated from ICU’s own data (downloaded from a pinned ICU release) and is not committed, so you need to build it before running the tests:

git clone https://github.com/gungun974/gleam_intldate.git
cd gleam_intldate

gleam run -m intldate_generate

gleam run --target javascript -m intldate_test_data 
gleam test

gleam run --target javascript -m intldate_test_data generates the test data from Node’s built-in Intl, so it requires a Node version bundling ICU 78.3 to match the pinned ICU release used elsewhere.

Search Document