Date and Time Formatting Guide

Copy Markdown View Source

This guide explains how to use Localize.Date, Localize.Time, Localize.DateTime, Localize.Interval, and Localize.DateTime.Relative for locale-aware date and time formatting.

Overview

Localize formats dates and times using CLDR pattern strings, locale-specific calendar names (months, days, eras, day periods), and Unicode TR35 field symbols. Each module accepts a struct or map and returns a locale-formatted string.

iex> Localize.Date.to_string(~D[2024-07-10], locale: :en)
{:ok, "Jul 10, 2024"}

iex> Localize.Time.to_string(~T[14:30:00], locale: :en, prefer: :ascii)
{:ok, "2:30:00 PM"}

iex> Localize.DateTime.to_string(~N[2024-07-10 14:30:00], locale: :en, prefer: :ascii)
{:ok, "Jul 10, 2024, 2:30:00 PM"}

Date formatting

Standard formats

The :format option selects a predefined CLDR format. The default is :medium.

iex> Localize.Date.to_string(~D[2024-07-10], format: :short, locale: :en)
{:ok, "7/10/24"}

iex> Localize.Date.to_string(~D[2024-07-10], format: :medium, locale: :en)
{:ok, "Jul 10, 2024"}

iex> Localize.Date.to_string(~D[2024-07-10], format: :long, locale: :en)
{:ok, "July 10, 2024"}

iex> Localize.Date.to_string(~D[2024-07-10], format: :full, locale: :en)
{:ok, "Wednesday, July 10, 2024"}

Skeleton formats

Skeleton atoms specify which fields to include without prescribing the exact pattern. CLDR resolves each skeleton to a locale-appropriate pattern.

iex> Localize.Date.to_string(~D[2024-07-10], format: :yMMMd, locale: :en)
{:ok, "Jul 10, 2024"}

iex> Localize.Date.to_string(~D[2024-07-10], format: :yMMMEd, locale: :en)
{:ok, "Wed, Jul 10, 2024"}

iex> Localize.Date.to_string(~D[2024-07-10], format: :yMd, locale: :en)
{:ok, "7/10/2024"}

Common date skeletons: :yMd, :yMMMd, :yMMMEd, :yMMM, :yMMMM, :MMMd, :MMMEd, :Md, :MEd.

Custom pattern strings

Pass a CLDR pattern string directly:

iex> Localize.Date.to_string(~D[2024-07-10], format: "dd/MM/yyyy", locale: :en)
{:ok, "10/07/2024"}

iex> Localize.Date.to_string(~D[2024-07-10], format: "EEEE d MMMM y", locale: :en)
{:ok, "Wednesday 10 July 2024"}

Partial dates

Maps with a subset of date fields are supported. Missing fields are omitted from the output:

iex> Localize.Date.to_string(%{year: 2024, month: 6}, format: :yMMM, locale: :en)
{:ok, "Jun 2024"}

iex> Localize.Date.to_string(%{year: 2024, month: 6}, format: :yMMM, locale: :fr)
{:ok, "juin 2024"}

Locale influence on dates

Different locales produce different patterns, field orders, and calendar names:

iex> Localize.Date.to_string(~D[2024-07-10], locale: :en)
{:ok, "Jul 10, 2024"}

iex> Localize.Date.to_string(~D[2024-07-10], locale: :de)
{:ok, "10.07.2024"}

iex> Localize.Date.to_string(~D[2024-07-10], locale: :ja)
{:ok, "2024/07/10"}

iex> Localize.Date.to_string(~D[2024-07-10], locale: :fr)
{:ok, "10 juil. 2024"}

Time formatting

Standard formats

iex> Localize.Time.to_string(~T[14:30:00], format: :short, locale: :en, prefer: :ascii)
{:ok, "2:30 PM"}

iex> Localize.Time.to_string(~T[14:30:00], format: :medium, locale: :en, prefer: :ascii)
{:ok, "2:30:00 PM"}

The :prefer option

CLDR provides two variants for time patterns in many locales: one using Unicode characters (curly quotes, non-breaking spaces) and one using ASCII equivalents. The :prefer option selects which variant to use. The default is :unicode.

iex> Localize.Time.to_string(~T[14:30:00], locale: :en, prefer: :ascii)
{:ok, "2:30:00 PM"}

12-hour vs 24-hour clocks

The hour cycle is determined by the locale. English defaults to 12-hour with AM/PM, while German and Japanese default to 24-hour:

iex> Localize.Time.to_string(~T[14:30:00], locale: :en, prefer: :ascii)
{:ok, "2:30:00 PM"}

iex> Localize.Time.to_string(~T[14:30:00], locale: :de)
{:ok, "14:30:00"}

iex> Localize.Time.to_string(~T[14:30:00], locale: :ja)
{:ok, "14:30:00"}

Partial times

Maps with a subset of time fields are supported:

iex> Localize.Time.to_string(%{hour: 14, minute: 30}, format: :hm, locale: :en, prefer: :ascii)
{:ok, "2:30 PM"}

DateTime formatting

Localize.DateTime.to_string/2 formats combined date-and-time values. It accepts DateTime, NaiveDateTime, and maps.

Standard formats

iex> Localize.DateTime.to_string(~N[2024-07-10 14:30:00], format: :short, locale: :en, prefer: :ascii)
{:ok, "7/10/24, 2:30 PM"}

iex> Localize.DateTime.to_string(~N[2024-07-10 14:30:00], format: :medium, locale: :en, prefer: :ascii)
{:ok, "Jul 10, 2024, 2:30:00 PM"}

Separate date and time formats

Combine different format levels for the date and time portions:

iex> Localize.DateTime.to_string(~N[2024-07-10 14:30:00], date_format: :full, time_format: :short, locale: :en, prefer: :ascii)
{:ok, "Wednesday, July 10, 2024, 2:30 PM"}

Locale influence on datetimes

iex> Localize.DateTime.to_string(~N[2024-07-10 14:30:00], locale: :en, prefer: :ascii)
{:ok, "Jul 10, 2024, 2:30:00 PM"}

iex> Localize.DateTime.to_string(~N[2024-07-10 14:30:00], locale: :de, prefer: :ascii)
{:ok, "10.07.2024, 14:30:00"}

Automatic date-only and time-only fallback

When a map contains only date fields or only time fields, Localize.DateTime.to_string/2 delegates to Localize.Date or Localize.Time automatically.

Interval formatting

Localize.Interval.to_string/3 formats a range between two dates by identifying the greatest calendar field that differs and selecting a CLDR interval pattern.

iex> {:ok, result} = Localize.Interval.to_string(~D[2024-04-22], ~D[2024-04-25], locale: :en)
iex> String.contains?(result, "22") and String.contains?(result, "25")
true

iex> {:ok, result} = Localize.Interval.to_string(~D[2024-01-15], ~D[2024-03-20], locale: :en)
iex> String.contains?(result, "Jan") and String.contains?(result, "Mar")
true

Interval styles

The :style option controls which fields appear in the output:

StyleDescriptionExample skeleton
:dateFull date (default):yMMMd
:monthMonth only:MMM
:month_and_dayMonth and day:MMMd
:year_and_monthYear and month:yMMM

The :format option selects the detail level: :short, :medium (default), or :long.

How interval formatting works

  1. The greatest difference between the two dates is identified (year, month, or day).

  2. The style and format resolve to a skeleton atom.

  3. CLDR provides interval patterns that split the format at the repeat point so that shared fields are not repeated. For example, two dates in the same month produce "Apr 22 - 25, 2024" rather than "Apr 22, 2024 - Apr 25, 2024".

Relative time formatting

Localize.DateTime.Relative.to_string/2 formats time differences as human-readable phrases like "2 hours ago" or "in 3 days".

From integer seconds

iex> Localize.DateTime.Relative.to_string(-60, locale: :en)
{:ok, "1 minute ago"}

iex> Localize.DateTime.Relative.to_string(3600, locale: :en)
{:ok, "in 1 hour"}

From dates and datetimes

When given a Date, DateTime, or Time, the difference is calculated relative to now (or the :relative_to option):

Localize.DateTime.Relative.to_string(~D[2024-01-01], relative_to: ~D[2024-01-04], locale: :en)
{:ok, "3 days ago"}

Format styles

iex> Localize.DateTime.Relative.to_string(-86400, format: :standard, locale: :en)
{:ok, "1 day ago"}

iex> Localize.DateTime.Relative.to_string(-86400, format: :short, locale: :en)
{:ok, "1 day ago"}

iex> Localize.DateTime.Relative.to_string(-86400, format: :narrow, locale: :en)
{:ok, "1 day ago"}

Special ordinal forms

For offsets of -2 to +2 days, many locales provide special names:

  • -1 day: "yesterday"
  • 0 days: "today"
  • +1 day: "tomorrow"

Format pattern reference

CLDR format patterns use field symbols to represent date and time components. Each symbol can be repeated to control the output width.

Date field symbols

SymbolMeaning12345
GEraADADADAnno DominiA
yYear202424---
MMonth707JulJulyJ
LStandalone month707JulJulyJ
dDay of month101---
EDay nameMonMonMonMondayM
eDay of week (numeric)202MonMondayM
cStandalone day202MonMondayM
QQuarter101Q11st quarter1

Time field symbols

SymbolMeaning12
hHour (1-12)202
HHour (0-23)1414
KHour (0-11)202
kHour (1-24)1414
mMinute505
sSecond909
SFractional second1-N digits
aAM/PMAM

Timezone field symbols

SymbolCountExample
z1-3EST
z4Eastern Standard Time
Z1-3+0500
Z4GMT+05:00
Z5+05:00 or Z
O1GMT+5
O4GMT+05:00
X1-5+05, +0500, +05:00 (Z for zero)
x1-5+05, +0500, +05:00 (no Z)

Hour cycles

The hour symbol determines the cycle:

  • h — 12-hour (1-12), requires AM/PM (a).
  • H — 24-hour (0-23), no AM/PM.
  • K — 12-hour (0-11), requires AM/PM.
  • k — 24-hour (1-24), where 24 means midnight.

Skeleton atoms can use j as a meta-symbol that resolves to the locale's preferred hour cycle.

Options reference

Localize.Date.to_string/2

OptionTypeDefaultDescription
:localeatom, string, or LanguageTagLocalize.get_locale()Locale for patterns and calendar names.
:formatatom or pattern string:mediumStandard name (:short, :medium, :long, :full), skeleton atom, or custom pattern.
:prefer:unicode or :ascii:unicodeSelects Unicode or ASCII variant of the format pattern.

Localize.Time.to_string/2

OptionTypeDefaultDescription
:localeatom, string, or LanguageTagLocalize.get_locale()Locale for patterns and day period names.
:formatatom or pattern string:mediumStandard name, skeleton atom, or custom pattern.
:prefer:unicode or :ascii:unicodeSelects Unicode or ASCII variant.

Localize.DateTime.to_string/2

OptionTypeDefaultDescription
:localeatom, string, or LanguageTagLocalize.get_locale()Locale for patterns and calendar names.
:formatatom or pattern string:mediumStandard name, skeleton atom, or custom pattern.
:date_formatatom(same as :format)Format level for the date portion when using separate levels.
:time_formatatom(same as :format)Format level for the time portion when using separate levels.
:styleatom:defaultWrapper style when combining date and time.
:prefer:unicode or :ascii:unicodeSelects Unicode or ASCII variant.

Localize.Interval.to_string/3

OptionTypeDefaultDescription
:localeatom, string, or LanguageTagLocalize.get_locale()Locale for patterns and calendar names.
:formatatom:mediumDetail level: :short, :medium, or :long.
:styleatom:dateField scope: :date, :month, :month_and_day, or :year_and_month.

Localize.DateTime.Relative.to_string/2

OptionTypeDefaultDescription
:localeatom, string, or LanguageTagLocalize.get_locale()Locale for patterns and pluralization.
:formatatom:standardWidth: :standard, :short, or :narrow.
:unitatom(auto-derived)Explicit unit: :second, :minute, :hour, :day, :week, :month, :quarter, :year.
:relative_toDateTimeDateTime.utc_now()Baseline for calculating the difference.