Repository navigation
Localize version 1.4.0
[1.4.0] — October 23rd, 2026
Release notes
-
Built on CLDR 49 and Unicode 18. CLDR 49 no longer publishes 114 locales below Basic coverage, collation follows UCA 18, and its new data is supported, from dual and UTC offset formats to new units and the Unicode 18 currency signs.
-
Significant work on date and time formatting towards TR35: semantic skeletons, append items, qualified metazone names, cyclic years, and a calendar's own weeks and quarters. Many outputs change; see the breaking changes below.
-
Date, time, date-time and interval parsing move from Calendrical into Localize, so an application that uses only the proleptic Gregorian calendar no longer needs Calendrical.
-
Time zones are parsed and resolved by
Localize.DateTime.Timezone.parse_zone/2,resolve/3andparse_offset/2, andLocalize.TimeZoneDatabasehandles fixed offsets. -
Calendars other than
Calendar.ISOcome from Calendrical 2.0, which ships the same day, and a locale's-u-ca-converts a value into its calendar. -
Localize.Inflectionis added as a technical preview: inflected words, pronouns and quantities, MessageFormat 2'si:functions and inflected units. -
CLDR 49's technical previews are supported and marked as such: ordinal days, numeric date and time separators, the
c12andc24hour cycles, semantic skeletons and rational number formats. -
Number parsing reads currency amounts and a locale's alternative separators (
:currency,:separators). -
Locale data is verifiable and self-hostable:
mix localize.verify_locales,Localize.cldr_repo_ref/0and a self-hosting guide. -
Public functions return
{:error, exception}for arguments and options of the wrong type, where they previously raised.
Breaking changes
Localize 1.4 includes a significant number of breaking changes but they are all a result of improved conformance with TR35 and would not, in typical use, be expected to be observed in the majority of current use cases.
Calendars
-
A calendar other than
Calendar.ISOmust implement the Calendrical 2.0 behaviour, and any other is refused withLocalize.UnknownCalendarError. Use Calendrical 2.0 with Localize 1.4. -
A locale's
-u-ca-converts a value into that calendar before it is formatted:en-u-ca-hebrewwrites a Gregorian date as a Hebrew one. The calendar comes from the provider registered withLocalize.Calendar.register_provider/1, which Calendrical 2.0 registers when it starts. -
MessageFormat 2's
calendaroption names the calendar on the message's locale, as-u-ca-does, rather than a calendar module. -
The parse functions'
:calendaroption is a calendar module, and the date is returned in it. A CLDR calendar name such as:hebrew, or a module that is no calendar, returnsLocalize.UnknownCalendarError. -
The parse functions no longer take
:return_calendar. UseDate.convert/2on the result. -
Localize.DependencyRequiredErroris removed. Nothing returns it, since Localize no longer reaches for Calendrical by name. -
A numeric month is written as the calendar writes it (
written_date/3): a Hebrew common year's Adar is "6", as ICU writes it, where it was "7". -
yin the Chinese and Dangi calendars is the year's place in the 60-year cycle: the year that began in 2026 is "43", where it was "4663".uwrites the year's number. -
Quarters and week numbers are the calendar's own: a Coptic or Hebrew thirteenth month is in Q4, where it was Q5.
Calendar.ISOkeeps the locale's weeks. -
Wnumbers a month's weeks as a year's are numbered, and its pattern writes the month that week belongs to: 1 October 2021 is "week 5 of September" inen-GB, where it was "week 0 of October". -
A date in a calendar of weeks is written with CLDR's generic calendar, "M06 16, 2026 AD", where its week was written as its month.
-
A calendar's own formats are read before ISO 8601, so "2023-11-22" read in the Chinese calendar is a Chinese date, where it was a Gregorian date converted.
-
Text that none of a calendar's formats reads is read in the calendar its
info/0names as:parsing_calendar, then converted: "Feb 1, 2024" inCalendrical.ISOWeekis 1 February 2024. -
Japanese eras before Meiji start on proleptic Gregorian dates, so each moves by days to weeks: CLDR recorded their lunisolar proclamation dates.
Formatting dates and times
-
Localize.DateTime.to_string/2joins date and time with the locale's "at" pattern by default:en:longis "July 6, 2024 at 2:30:45 PM".style: :defaultrestores the previous output;:mediumand:shortare unchanged. -
An interval joins date and time with the locale's standard date-time pattern, not the "at" pattern: "June 15, 2026, 10:00 – 14:30".
style: :atrestores it. -
An interval at a standard format writes the fields and widths of that format's single-value pattern:
vi:shortis "1/4/23 – 10/4/23", where it was "01/04/2023 – 10/04/2023". -
Two times on one day at a format with seconds are each written whole: "Apr 8, 2026, 12:00:00 PM – Apr 8, 2026, 2:00:00 PM".
time_format: :short, or a skeleton without seconds, writes the date once. -
Skeletons match the locale's
availableFormatsonly, as CLDR's pattern generator does, not its standard formats:ko:yMdis "2024/7/6". -
A pattern or skeleton that asks for a field the value lacks returns
{:error, %Localize.DateTimeInvalidInputError{}}, where it left the field blank: "Jun , 2026". -
A partial date takes its skeleton from the fields it has:
%{year: 2024, month: 6}is "Jun 2024", where it was "6/2024". -
The numeric weekday fields
eandccount from the locale's first day of the week: Saturday is 7 inenand 6 inde, where both gave 6.ccis one digit. -
A pattern field at a width TR35 does not list, such as
MMMMMMorHHH, formats as U+FFFD. -
Localize.Calendar.localize/3returns aLocalize.DateTimeInvalidInputErrorfor a value that lacks the field it names, where it gave the field's first value:localize(%{year: 2026}, :month)was "January".
Time zones
-
A metazone name is qualified by the zone's country or city unless the zone is the metazone's preferred zone for the locale's country: in
en, Berlin is "Central European Time (Germany)" (v) and "Central European Summer Time (Germany)" (z). -
A zone's standard name stands in for a missing generic name only where the zone keeps one offset for 184 days either side, so London's summer is no longer "Greenwich Mean Time".
-
A zero offset is spelled out in the localized GMT format, "GMT+00:00" long and "GMT+0" short.
Localize.DateTime.Timezone.gmt_format/3drops its:zero_formatoption. -
EST5EDT,CST6CDT,MST7MDTandPST8PDTare zones of their own in CLDR 49, not aliases of New York, Chicago, Denver and Los Angeles, so they format by their offset.
Parsing
-
A date-time parsed with an offset keeps its wall time and offset: "2026-05-23T14:30:00+05:00" is 14:30 at +05:00, where it was 09:30 UTC.
DateTime.shift_zone/3gives the previous result. -
The time zone of a date-time parsed with an offset is the offset itself,
"+05:00", where it was"Etc/UTC".DateTime.add/4andDateTime.shift/3on it need the newLocalize.TimeZoneDatabase. -
A named zone in parsed text resolves to a
DateTimethrough the configured time zone database, where it was dropped unless Calendrical was installed. A zone the locale does not write, such as "XQZV", is an error.
Relative time
-
Localize.DateTime.Relativecounts the calendar periods between two dates with the calendar'sdiff/3andshift/6, not a mean month or year: 1 February is "next month" from 31 January. Hours count the clock, so 00:01 is "in 1 hour" from 23:59. -
Without
:unit, the largest unit of which a whole one lies between the two values is chosen, as ECMA-262 Temporal chooses it. A number of seconds is counted in weeks at most.
MessageFormat 2
{$t :time}and{$d :datetime}give the time to the minute, as TR35 specifies;precision=secondrestores the seconds. A date or time option other thantimeZone,hour12andcalendarset from a variable is an error.
Locale data and display names
-
CLDR 49 drops 114 locales below Basic coverage, among them
aa,ab,anandht. Such a locale resolves to a fallback,:undforaa. -
An unsupported
:prefervalue is an error, where it resolved to:standard.:prefernow applies toLocalize.Language,Localize.TerritoryandLocalize.Scripttoo. -
Collation moves to Unicode 18 (UCA 18.0.0), so any stored
Localize.Collation.sort_key/2must be regenerated.compare/3is unchanged for existing characters.
Added
-
An ISO 8601 week date parses in any of its forms, "2026-W25-2", "2026W252" or "2026-W25", as a week and day of the calendar it is read in.
-
Localize.Number.Parser.parse/2takes:currencyand reads an amount with that currency's separators:de-ATwrites€ 1.234,56but1 234,56plain, so"1.234,56"parses only with the option. -
Localize.Number.Parser.parse/2takes:separators, naming the set of decimal and grouping separators to read with for a locale that has more than one:en-ZAwrites1 234,56with its:standardset and1,234.56with its:usset. A locale without the named set reads with its standard one. -
Localize.Locale.data_locale_id/1returns the id of the locale whose data is loaded for a locale, which is its own unless a fallback supplied it. Language rules now follow that locale so they agree with its text, while territory preferences keep following the requested locale. -
Localize.Number.Symbol.for_currency/2returns the symbols an amount of a currency is written with, taking the currency's own decimal separator over the locale's currency separators over its plain ones. -
The day of the month (
d) is the day the calendar writes its date with, itswritten_date/3, so Calendrical's Julian year-start variants write 25 March 1750 from month 1 day 1. -
Such a date reads back through the calendar's
date_from_written_date/3: "March 25, 1750" is month 1 day 1 again, alone, in a map or at either end of an interval. -
The months between two dates are counted by the calendar's
diff/3, so Calendrical'sMarch25months 1 and 13, both named March, are two months. -
Every parse function refuses text longer than
:max_parse_length, 1,024 bytes by default, with aLocalize.DateTimeParseLengthError. The longest text any locale writes is 666 bytes, and reading cost grows with the square of the length. -
Localize.Territory.Hemisphere.hemisphere/1returns the hemisphere of a territory or locale,:northern,:southernor:ambiguouswhere the equator crosses it:hemisphere(:AU)is{:ok, :southern}. A region is answered from the territories it contains. -
Localize.Calendar.register_provider/1registers the module that supplies calendar modules for a locale's-u-ca-, andcalendar_provider/0returns it. Calendrical 2.0 registers itself when it starts. -
Localize.Date.parse/2,Localize.DateTime.parse/2andLocalize.Interval.parse/2read a composite calendar's date in each calendar itsparsing_calendars/0names, so "Mo5 11, 1872" inCalendrical.Reform.Japanreads back. -
Localize.Date.parse/2takes:format, a standard format, skeleton or pattern, and reads the text with it alone:mt's:yMd"4/3/2024" is 3 April.Localize.Interval.parse/2takes it as the interval's format. -
Localize.DateTime.parse/2reads the date and the time with the two halves of:format: "3/4/2024 22:05" withformat: "d/M/y HH:mm".:date_formatand:time_formatset one half. -
Localize.Time.parse/2takes:formattoo, a standard format, a skeleton or a pattern, and reads the text with that format alone: "14h30" withformat: "HH'h'mm". A:longor a:fullformat reads a time with its zone and, as it is written for a time that has none, without it. -
A date's months, days and patterns are named from the CLDR calendar in effect on that date, the calendar's
cldr_calendar_type/3: Japan's lunisolar dates before 1873 take the Chinese calendar's names. -
Localize.Number.resolve_currency/2andresolve_currencies/2resolve a symbol several currencies share by the nearest locale that uses one, so "$" is the Canadian dollar infr. It stays unknown where no locale is a clear fit, as ines. -
Localize.Interval.to_string/3andto_parts/3take a skeleton or a pattern as a date interval's:formator:date_format, as CLDR keys interval formats by skeleton, so a format no standard format reaches can be named:format: :yMMMEdis "Mon, Jun 15 – Thu, Jun 18, 2026" inen. -
Localize.DateTime.Timezone.parse_zone/2reads a time zone in any form a locale writes one — a name, location, city, zone ID or localized GMT format — as TR35's time zone parsing does, andresolve/3resolves it at a date and time through the configured time zone database. -
Localize.Inflection, a technical preview, inflects words from CLDR's inflection data, withpronoun/2,3andquantify/4:inflect("Haus", :de, %{case: "dative", number: "plural"})is "Häusern". Data for 48 languages comes frommix localize.download_inflection. -
MessageFormat 2 gains the Unicode inflection project's
i:functions,:i:inflect,:i:pronoun,:i:quantify,:i:listand:i:numeral, with SSML output (output: :ssml).Localize.Message.Namespacelets an application register a namespace of its own. -
Localize.Unit.to_string/2takes:inflect, synthesizing a pattern when the requested:grammatical_casehas no CLDR one —:safeuses attested paths only,:alwaysalso guesses from suffixes.Localize.Unit.grammatical_gender/2returns a unit's gender. -
Localize.Locale.LocaleDisplay.key_name/2andtype_name/3return a BCP 47 key's localized name and the name of one of its type values —key_name(:ca)is "Calendar",type_name(:ca, :buddhist)is "Buddhist Calendar", and "Buddhist", CLDR'sscope="core"name, withprefer: :menu. A value may be given in its BCP 47 form,:gregoryor:level1. -
Localize.Locale.LocaleDisplay.display_name/2takeskey_value_display: :separate, TR35'sSeparateKeyValue, soen-u-ca-gregoryis "English (Calendar: Gregorian)". -
Localize.Calendar.display_name(:calendar, calendar)takesprefer: :menufor the short name a menu of calendars uses, "Gregorian", and accepts BCP 47 names such as:gregory. -
Localize.affirmative_responses/1andnegative_responses/1return CLDR's POSIXyesstr/nostrforms —{:ok, ["ja", "j"]}for:de— andaffirmative?/2andnegative?/2match a response against them, folding case as TR35 requires. -
Localize.Locale.LocaleDisplay.type_value_name/2returns the localized name for a boolean BCP 47 keyword value:"On"and"Off"inen,"Ein"and"Aus"inde. -
CLDR 49's
placeholderBoundarySpacingseparates a text field from a neighbour its output would run digits into, as TR35 now specifies, so zh's "vHH:mm:ss" renders a nameless zone as "GMT+8 14:30:45" rather than "GMT+814:30:45". Numeric fields written together are left alone. -
designation: :utconLocalize.DateTime.Timezone.gmt_format/3writes CLDR 49'salt="utc"forms, "UTC+01:00" and "UTC+?" inen, andnon_location_format/3passes it to its GMT fallback. Both spellings read back throughparse_offset/2. -
CLDR 49's
dualOffsetFormatjoins the two offsets a zone keeps, so the localized GMT format describes a zone that moves between them:offsets: :dualwrites Los Angeles as "GMT-8/-7" and Denver as "GMT-7/-6", where Phoenix keeps one offset and stays "GMT-7". A value with no zone writes its single offset. -
A skeleton whose time half is only a zone joins its date through CLDR 49's
Date-Timezoneitem, and one whose date half is only a weekday joins its time throughTime-Day-Of-Week, as TR35 now specifies.en:yMMMdzrenders "Jul 6, 2024 EDT", where it errored. -
Localize.DateTime.SemanticSkeletonimplements TR35 semantic skeletons, which ask for a date by meaning ("YMDE") rather than by field, and:formataccepts one everywhere.:time_precision,:zone_length,:hour_cycleandalignment: :columnshape the result. -
MessageFormat 2's
:date,:timeand:datetimetake TR35's options, fromfieldsandlengthtotimeZoneStyle, and the overridestimeZone,hour12andcalendar:{$d :date fields=month-day}inen-AUis "14 Jun". -
Localize.DateTime.Timezone.parse_offset/2reads a fixed UTC offset from a zone string — ISO 8601, or the localized GMT format in any locale's spelling — invertinggmt_format/3.Localize.DateTime.parse/2uses it, so"May 16, 2026 2:30 PM GMT+10:30"parses withoutcalendrical. -
Localize.Date.parse/2,Localize.Time.parse/2andLocalize.DateTime.parse/2parse natively, no longer requiringcalendrical—parse("March 22, 2026", locale: :en)gives{:ok, ~D[2026-03-22]}.Localize.Interval.parse/2andLocalize.DateTime.Parser.parse/2join them. -
Localize.MinimalPairsexposes CLDR's minimal pairs — the phrases demonstrating a locale's plural, ordinal, case and gender forms.cardinal/1,ordinal/1,grammatical_case/1andgrammatical_gender/1return them;format/3picks the one a number selects. -
CLDR's
symbol-alt-variantcurrency symbols are ingested asLocalize.Currency.variant_symboland selected withcurrency_symbol: :variant—:TRYinengives "TL". CLDR 49's new Unicode 18 signs resolve through it::AED"",:MVR"",:OMR"" and:SAR"". -
CLDR 49's numeric date and time separators, a technical preview:
Localize.DateTime.numeric_separators/2returns a locale's pair, and:numeric_date_separatorand:numeric_time_separatorset them on the date, time and interval formatters. -
CLDR 49's
-u-hc-c12and-u-hc-c24, a technical preview, ask for the locale's own 12- or 24-hour cycle, resolved from the cycles its region allows:ja-u-hc-c12writes 00:30 as "午前0:30". -
Ordinal dates from CLDR 49, which CLDR designates a technical preview: the
dddfield formats the day from the locale'sdayOfMonthsdata, soen:yMMMdddrenders "Jul 6th, 2024". Locales without that data, and patterns with a numeric month, format the plain day. -
Date-time skeletons resolve through TR35's append items when no available format carries every requested field, appending the missing ones from the locale's
appendItemstemplates.enhas no quarter format, so:yMMMdQnow renders "Jul 6, 2024 (quarter: 3)" where it errored. -
Localize.DateTime.Timezone.location_name/3returns the place the generic location format names for a timezone — a country for a single-zone territory or a CLDR primary zone, a city otherwise — andgeneric_location_format/2renders it through the locale'sregionFormat. -
The
gpattern symbol formats the modified Julian day and the deprecatedlis ignored, as TR35 specifies, where both returned a tokenize error. -
mix localize.verify_localeschecks a directory of locale files against the release's SHA-256 manifest, exiting non-zero on a mismatch so it can gate a build. It reports the CLDR tag the reference data came from, since the hashes are not overridable. -
Localize.cldr_repo_ref/0returns the CLDR repository tag the bundled data was generated from, such as"release-49", and the tag now ships in the hex package. Together withLocalize.version/0it traces a locale file to the source it came from. -
A self-hosting guide covers provisioning, verifying and serving locale data from a private CDN. Configure the cache location before downloading, since the download target follows it.
Changed
-
Localize.Interval.parse/2reads text asLocalize.Interval.to_string/3writes it at the given:format, so "Jun 16 – 20, 2026" at:mediumparses, where only two dates written whole did. -
A relative time uses its named form only for a whole offset:
Localize.DateTime.Relative.to_string(0.9999, unit: :day)is "in 1 day", where it was "tomorrow". -
Localize.Date.parse/2without a format keeps its compiled patterns for each locale and calendar: "Jun 16, 2026" inentakes about 0.4 ms, where it took 5. -
Localize.Interval.parse/2keeps its compiled patterns too, so aneninterval takes about 2 ms, where it took up to 80. They use up to about 300 KB of:persistent_termfor each locale and calendar. -
The Japanese calendar keeps all 237 eras, from 大化 (645) to 令和, where CLDR 49 has Meiji onwards only; the earlier eras take curated dates and CLDR 48.2's names. 白鳳 carries
private_era: true, and four unattested erasunverified: true. -
Skeleton matching ranks narrow and short widths nearer abbreviated than wide and breaks ties by TR35's field order, as CLDR's reference generator does:
ja:yMdEEEEErenders "2024/7/6(土)", not "2024/7/6土".
Fixed
-
A cached locale whose bytes do not match the release's hash manifest is stale, as one of another data version is, so
mix localize.download_localesand runtime downloads replace it where it was kept as current. -
A stale cached locale that cannot be refreshed logs a warning naming
mix localize.download_locales, once per locale, where it fell back to its parent with only a debug message. -
Regional and script locales name subdivisions, inheriting from their parents:
en-GBnames:gbeng"England" where it named none. Names CLDR marks unconfirmed are left out. -
Relative months know a month by its name,
cldr_month/1, again: a Julian year reckoned from 25 March counts its March as two months, and 21 February to 25 March is "next month", where it was "in 2 months". -
A fiscal calendar's date is written as the Gregorian date it is, through the calendar's
cardinal_year/3: 10 August 2026 in a year begun in July is "Aug 10, 2026", where it was "Aug 10, 2027". It reads back throughdate_from_cardinal_date/3, and an interval across 1 January writes both years. -
A locale's
-u-mu-fahrenheoverrides the unit to Fahrenheit, where the override was dropped: BCP 47 caps a value at eight characters, so CLDR spells itfahrenhe, which named no unit. -
A currency amount uses the locale's currency separators where CLDR gives them:
fr-CHwrites1'234.56 CHFandde-AT€ 1.234,56, where both used their plain-number separators. -
Locale generation fails on a CLDR key its structs do not define, where
struct/2dropped it silently, which is how the currency separators went missing. -
A missing locale falls back to its parents and then to
:enrather than to theundroot, which has no currency data and no localized names. Only an explicit request for:undloads the root, and the error of a locale that cannot be loaded names the locale requested rather than the last fallback tried. -
A pre-validated
Localize.Number.Format.Optionsstruct takes the sign from each number it formats. The sign of the number it was validated with was reused, so options validated with0formatted-70as "CHF 70.00". -
Localize.validate_locale/1returns an error for an unknown-u-tz-timezone id, a-t-numeric value that is not a 4, 6 or 8 digit date and a-t-keyword without a value, where it raised. A-u-vt-value of several codepoints is accepted, where it raised too. -
Localize.Message.format_to_safe_list/3returns a data-model error rather than raisingWithClauseError: a duplicate declaration parses and then fails validation, which itswithhad no clause for. -
RBNF plural-keyed substitutions select with the plural rules of the locale the rule set came from, since the forms they choose between are that locale's own words.
-
Localize.Locale.gettext_locale_id/2caches its match per locale and known Gettext locales, so a repeated call takes about 0.4 µs where it took about 34 µs. -
Localize.Currency.currency_strings/2reads the index of currency-naming strings from the locale data, where the pipeline now generates it, so a call takes about 0.6 µs where it built the index each time for about 1.2 ms. A filtered call is still built, because a filter changes which strings name a single currency. -
Localize.validate_locale/1returnsLocalize.InvalidLocaleErrorfor an unknown-u-tz-zone, a-t-date that is not 4, 6 or 8 digits and a-t-keyword without a value, where it raised. A multi-codepoint-u-vt-value is accepted. -
A display name is that of the code it is keyed by, not of a retired alias:
akis "Akan", where it was "Twi", andfilis "Filipino" on every OTP release (#59). -
Inflection data is stored under its data version, so an earlier version is never read for a current one, as a stale
zhfile was forzh-TW.mix localize.download_inflection --pruneremoves superseded versions. -
A locale that
:persistent_termhas no room for returns aLocalize.LiteralMemoryErrornaming the VM flag+MIscs, where loading it stopped the VM. All CLDR locales take about 814 MB of its 1 GB default; the performance guide's "Memory" section gives the sizes. -
A skeleton asking a non-Gregorian date for a field its formats lack has it appended, after matching the calendar's formats and then the Gregorian's: a Hebrew
:yMMMdwis "1 Tamuz 5786 (week: 39)", where it was an error. The Chinese and Dangi calendars match only their own. -
A week date in every ISO 8601 form is the calendar's own week, where "2026W252" and "2026-W25" were ISO 8601's in every calendar. A week or day the calendar lacks is an error, where "2026-W53-1" was read in a 52-week year.
-
A year, or a year and quarter, written for a calendar other than
Calendar.ISOreads as a map of those fields: "2023 AD" in a calendar of weeks is%{year: 2023}, where it was an error, and "Q2 2023 AD" is the year and quarter, not the quarter's first day. -
A related Gregorian year reads as the calendar year that begins in it, however far from the reference date: the Islamic "AH 1699-12-28" is 16 June 1700, where it was an error. Of two years beginning in one Gregorian year, the nearer is read.
-
A cyclic year beside an era is that era's year of the name:
Calendrical.LunarJapanese's "Genroku (1688–1704) geng-chen 4 29" is 1700, where it was 2000. A name the era has no year of is an error. -
An inverted range read for a calendar whose dates are read in another names its two dates in the calendar asked for:
Localize.Interval.parse/2of "Feb 5, 2024 – Feb 1, 2024" for a calendar of weeks returned aLocalize.DateRangeParseErrorholding the Gregorian dates the text was read as. -
A year below 1 is read where its numbering spells its sign:
jawrites a Japanese date before the first era with thejpanyearnumbering, "大化マイナス688年3月15日", and it was read as no date, alone and in an interval. -
A week without its year is a week of the reference date's week-based year: "Sun (week: 53)" read on 3 January 2021 is that day, where it was no date. Two such weeks read as an interval.
-
A pattern's
''is read inside and outside quoted text, as TR35 has it, somt's "25 ġimgħa ta' 2026" reads back, where its "ta'" was read as "ta". -
A weekday beside a week is that day of the week in every calendar, where "Tue, week 25 of 2026" was read as the week's first day.
-
An interval at a standard format is written in that format's numbering, as the date alone is:
he's Hebrew medium interval is "א׳–ה׳ בתמוז ה׳תשפ״ו", where it used digits. It reads back, and an interval at a skeleton stays in digits. -
A year a format names is never written as a number, nor a numbered year as a name, as TR35's adjustments require:
en's Chinese medium interval is "Mo5 2 – 6, bing-wu", where the related year took the name's place. Between two numbered years the one asked for is written, "5/2/2026 – 5/6/2026". -
An interval whose dates are in two sixty-year cycles of the Chinese or Dangi calendar writes both dates in full, "5/2/2026 – 5/2/2086" for
en'syMd, as ICU does, where the interval's pattern wrote the two alike, "5/2/43 – 5/2/43". A format that writes no year takes one first, as it does for any two years. -
Gon a Chinese or Dangi date writes the number of its sixty-year cycle, "78" in 2026, as ICU does, where it returnedLocalize.ItemNotFoundError. Read beside the year's place it names one year: "77 60" began in 1983. -
An interval written without a year across the new year ends in the year after: "Dec 28 – Jan 3" was refused as an inverted range of the reference date's year. The later date is a year on only where it would otherwise come before the earlier, by its calendar's own days, and never an earlier day of the same month.
-
A date without its year in a range takes the other date's year, or the next across the new year: "June 16 to August 20, 2031" is that summer, where it was five years earlier, and "Dec 28 – Jan 3, 2027" reads, where it was refused.
-
A zone keeps its metazone from 1970 on, as ICU has it: New York in 1965 is "GMT-05:00" and "New York Time", and Los Angeles in 1850 "GMT-07:52:58", not "Pacific Standard Time".
Localize.DateTime.Timezone.metazone_for/2isnilbefore 1970. -
A pattern that writes a year's name beside its number keeps the name when a skeleton asks for another numbered year. The skeleton
rwrote a Chinese year as "2026(2026)" in most locales, root's "r(U)" turned to "r(r)", and writes "2026(bing-wu)". -
Localize.DateTime.parse/2reads with aLocalize.DateTime.SemanticSkeletongiven as:format, where the skeleton was ignored. A skeleton whose minutes are optional reads "2 PM", whichLocalize.Time.parse/2refused. -
A date and time is written with the numbering its date format states, CLDR's
numbersattribute, as the date alone is.he's Hebrew date and time was "1 בתמוז 5786, 14:30" beside the date's "א׳ בתמוז ה׳תשפ״ו", andzh's Chinese day "2" beside "初二". -
An interval whose CLDR item names the year is read with the related year written in the name's place.
en's Chinese "Mo5 2 – 6, 2026", beside the item "MMM d – d, U", was an error, as "2026 M05 2–6" was in 487 locales. -
A date two of a locale's formats read is read by the one whose own separators it has.
af's "10-7-2569 BE" is itsGyMd's, "M-d-y G", the seventh of October, where its "d/M/y GGGGG" read it first, a hyphen for its slash, as the tenth of July. -
An interval written without a year is read as dates of the reference date's year, as a date alone is. "Jun 16 – 20", which the month and day fields write, was read only as two partial dates with
as: :map. -
An interval reads with or without spaces about its dash, so
en's "Jun 16–20, 2026" andde's "16. – 20.06.2026" read, where they were errors. -
An interval in a year two eras share is read where one of its dates is written without its month.
en's "May 1 – 5, 1 Reiwa" asked the calendar about the year 2019 alone, which is Heisei 31 until April, and was an error; each date now asks with the month and the day it takes from the other. -
A numeric field takes the abbreviated width of a named one where the calendar has no numeric format, as CLDR's conformance data and ICU do:
GyMdfor a Chinese date inenis "Mo5 2, 2026", where it was "Fifth Month 2, 2026(bing-wu)". -
An interval's values are written and read in the order the locale's fallback pattern states, as ICU does:
kekwrites 16 June 2026 to 20 August 2027 as "20/8/2027 – 16/6/2026", where the earlier date came first. A date written once beside two times stays before them. -
The first year of a Japanese era is read as
jawrites it. Its full, long and medium dates take CLDR'sjpanyearnumbering, whose year 1 is 元, so "令和元年5月1日" was an error. -
A year of an era that began before a calendar changed how it numbers its years is read.
Calendrical.Reform.Japancounts its years from 645 until 1872 and as the Gregorian calendar does from 1873, so every date from the reform to the end of Meiji in 1912, "Jun 16, 6 Meiji", was an error. -
A range is cut at every place its separator occurs, where only the first was tried, so
da's "2026-W25-2-2026-W27-1" and "October 5, 2026 to October 10, 2026" read. Text before the first date (fr-CH's "du {0} au {1}") and a later-date-first pattern read too. -
An interval is read by the pattern whose own separators are in the text before one that reads it through a lenient separator.
ha'syMdwrites a year's difference with root's pattern, "26-06-16 – 27-08-20", which its own day-first pattern read as 26 June 2016 to 27 August 2020. -
An interval date without an era takes the other date's era, where it took the reference date's:
ja's "H5/06/16~5/06/20" is five days of 1993, where it ran to 2023. A narrow era name reads where it names one era, so "R 8/06/16 – 8/06/20" reads back. -
A Hebrew date in Elul is read beside its era. The year of an era is settled by asking the calendar about the date, and it was asked about CLDR's number for the month, 13, in a common year of twelve months, so
de's "07.13.5786 AM",af's "07 Elul 5786 AM" and every interval ending in Elul were errors. -
A narrow month name reads where the format writes one and the calendar's narrow names are distinct:
mn's "2026 VI" and "2026 оны VI/16 – VIII/20" read back.en's "J", shared by three months, is still no month. -
A format with a variant is read as the formatter writes it, standard pattern first:
en-CA's Chinese short date "5/2/2026" is the fifth month's second day, where the variant was tried first, and itsyMdinterval "6/16/2026–6/20/2026" reads back. -
A skeleton's year is a number at every width and
Ua name at every width, so ayrequest never takes the year-name format:yMdfor a Chinese date inenis "5/2/2026", where it was "5/2/bing-wu".yyyyandyystay apart, soyyyyinenis "2024". -
A 24-hour hour beside a day period reads only where the two agree, as TR35 checks, so
ksh's "4:00:00 n.M. GMT+5:30", four in the afternoon, is no longer read as 04:00. -
Localize.DateTime.Timezone.resolve/3takes the reading whose zone is written so at the date:it's "Ora dell’Europa orientale (Kaliningrad)" is Kaliningrad in 2026, where it was Minsk. A name qualified by a city is that city's zone, so "Israel Time (Gaza)" is Gaza. -
A country or city in parentheses is read as a zone's qualifier only after a name the locale writes, so text that is no name before it is no zone. A 24-hour pattern took a time's day period for part of its zone:
en-GBread "4:00:00 pm (India)" as 04:00 in India. -
A locale's GMT literal alone is read as GMT, as TR35's parsing reads "the absence of a numeric offset" in "localized or global formats": "غرينتش" in
arandga's "MAG" are offset 0, where only "GMT", "UTC" and "UT" were. -
Localize.DateTime.Timezone.parse_zone/2,parse_offset/2andresolve/3read a number with no sign after the GMT literal as east ("GMT 3", "UTC5:30"), as TR35 allows. A zone inside a date or time still needs its sign, since the number could be the year or the hour. -
An offset's seconds are written by the localized GMT format and by
ZtoZZZ: Los Angeles before standard time is "GMT-07:52:58", where it read back 58 seconds out.parse_offset/2reads seconds after a one-digit hour and hours up to 23, so Juneau's "+15:02:19" reads. -
The long localized GMT format (
OOOO,ZZZZ, and a zone name's fallback to it) has a two-digit hour incs,fiandvmw, as TR35 gives the long format always: "GMT+05:30" and "UTC+05.30", where the one-digit hour of those locales'hourFormat, the short format's, wrote "GMT+5:30" and "UTC+5.30". -
resolve/3,parse_zone/2andparse_offset/2read an offset in any numbering system's digits, as TR35 has it:ar-EG's "غرينتش+٥:٣٠" andne's "GMT+५:३०" read, where they were an unknown zone in the 67 locales with digits of their own. -
Localize.DateTime.Timezone.parse_zone/2andresolve/3return an error for bytes that are not UTF-8, where they raised anArgumentError. -
A custom MF2 function receives its options under their names as strings, as
Localize.Message.Functiondocuments. It received an atom for any name an atom existed for, so the documentedfunc_opts["format"]readnil. -
A zone name qualified by a place reads as that place's zone even where its country has a primary zone: "Chile Time (Punta Arenas)" is Punta Arenas, where it was Santiago. A metazone name shaped like a region format reads as the name, so
ko's "사모아 표준시" is American Samoa. -
A standard or daylight name reads with the time its zone keeps at the date, as the formatter names it, where "Chile Summer Time (Punta Arenas)" read back an hour out. A standard name that is its metazone's only name ("Kyrgyzstan Time") follows the zone's clock.
-
The location format writes a country the locale has no name for by its code, as TR35 and ICU4C do: Havana's zone is "CU" in
suand "ora de CU" inoc, where it was "Havana". The code reads back in a locale that writes it. -
A location whose country's name has the shape of the fallback format is read as that country:
fr-CA's "heure : Saint-Martin (France)" was read as France's zone, andru's "Виргинские о-ва (Великобритания)" as the United Kingdom's. -
Localize.Interval.parse/2reads two-digit years with the era written once:nl's Buddhist "16-06-69 – 20-06-69 BE" reads, where its second year was the year 69. The reading nearest the reference date is taken. -
The six zones whose data key is not their lowercase name, such as
America/Port-au-PrinceandAntarctica/McMurdo, take the city names their locale gives them, "بلانك-سابلون" inar, where every locale used their own name. -
A zone name qualified by a place is read where the place or the name holds the fallback format's own text:
pt-AO's "Hora de Greenwich (Côte d’Ivoire (Costa do Marfim))" was an error, andko's "중부유럽 하계 표준시(독일)", whose name has the shape of the standard region format, was read as standard time. -
A date and time in a calendar other than
Calendar.ISOtakes the zone names of the instant it names, where its fields were read as ISO's: a Persian date in January 2026 inAsia/Almatyis "Kazakhstan Time", where it was "GMT+05:00".metazone_for/2reads it in its calendar too. -
A time reads only an hour its field allows, as TR35 and a strict ICU do, where "45:30 PM" was 21:30 and "13:30 AM" 01:30 in 618 locales. "13:30 PM" is an error too.
-
Localize.Time.parse/2reads ISO 8601's times in every locale: "14:30" without seconds, whichfi,daand 13 more did not read, and "T14", "T1430" and "T143045".zforZand U+2212 in an offset read too. -
Localize.DateTime.parse/2reads every ISO 8601 date-time form: week dates and ordinal days before theT, times without seconds or minutes ("2026-W25-2T10:30"), and the basic form ("20260616T103000"). MessageFormat 2's date and time literals take the same forms. -
Localize.Date.parse/2reads the rest of ISO 8601's date forms: a week alone ("2026-W25"), as its Monday in ISO 8601's weeks, and basic-format week and ordinal dates ("2026W252", "2026166"). A year and month, or a year, readsas: :mapin every locale. -
Localize.Time.parse/2reads an hour only in the range TR35 gives its field,h1 to 12 andK0 to 11: "0:30 AM" ath:mm aand "12:30 AM" atK:mm aare errors, where they were read as 00:30 and 12:30.Handkwere held to their ranges already. -
An interval writes only the fields its format names, as TR35's algorithm has it:
:MMMdfrom 28 December to 3 January is "Dec 28 – Jan 3", and:yMMMHmon two June days at 10:00 is "Jun 2026, 10:00". A year, month or era the values differ in is written where the format asks for it. -
Localize.Time.to_string/2writes aTimeorNaiveDateTimeat a standard format with the locale's standard time pattern, where it took its skeleton's, which differs in 44 locales at:short:bg's short time is "10:30", not "10:30 ч.". -
A date and time with no zone is written at
:longand:fullwithLocalize.Time.to_string/2's time, so it reads back in every locale:ja's full is "2024年4月3日水曜日 10:30:00", where the time was "10時30分00秒".fa,esandzh-Hantno longer leave "()" or "[]" for the missing zone. -
A date interval compares its two dates in the fields its format writes, as TR35 does, so a week format writes both weeks (
format: :ywis "week 25 of 2026 – week 30 of 2026", where only the first was written) and a quarter is written once for two dates within it ("Q2 2026", where "Q2 2026 – Q2 2026" was written). -
Localize.Interval.parse/2reads the year of a Chinese or Dangi interval as the related Gregorian year or as the calendar's own, whichever is nearer the reference year, so "11/8/2023 – 11/18/2023", which the formatter writes for two days of the Chinese year 4660, reads back, where it was the Chinese year 2023. -
An ISO 8601 offset or
Zwritten hard against a time is kept:Localize.DateTime.parse("11/22/2023 14:30:45+02:00", locale: :en)is aDateTimetwo hours ahead of UTC, where the offset was dropped and aNaiveDateTimecame back. -
Compiling Localize on Elixir 1.17 and 1.18 no longer logs "The on_load function for module Elixir.Localize.Nif returned: {:undef, …}" and an
UndefinedFunctionError:Localize.Priv, which the NIF module's@on_loadcallback calls, is compiled and loaded first. -
Localize.Interval.to_string/3andto_parts/3take dates with only some fields, in CLDR's interval format for them: two months are "Jun – Aug" and a year and month "Jun – Aug 2026", where they were errors. A date and time with no year keeps its date. -
wandYtake a date without its day when every day it could be is in one week. -
A specific time zone name takes standard or daylight time from TR35's
stdOffsetanddstOffsetwhere a metazone period names them, as CLDR 49 does forEurope/DublinandAmerica/Winnipeg, rather than from the time zone database's flag: Dublin's summer is "Irish Standard Time" howeverstd_offsetis set. -
Parsing no longer depends on map iteration order, which varies between VMs: standard formats are tried in
Localize.DateTime.Format.standard_formats/0order, and a place name two zones share resolves to the oneLocalize.Territorypicks, else the alphabetically first. -
Localize.Locale.parent/1returns a parent that names its CLDR locale, where it cleared the CLDR locale id with the child's subtags, so a caller walking up the locales no longer validates each parent against every locale, about 20 ms a step. -
Localize.Number.to_string/2with an RBNF rule only an ancestor locale has, such as Hebrew and Roman numerals, takes a fraction of a millisecond where it took 40 to 50: the chain of locales a rule is looked up in is found once a locale, where each call revalidated every parent tag. -
A date or time pattern is read by code points, so a combining mark after a quote is literal text:
nnh's long and full dates ("lyɛ̌ʼ 4 na …"), whose caron follows a quoted "lyɛ", parse, where the quote never closed. -
A numeric month written in an algorithmic numbering parses, as
hawwrites its short date's month in Roman numerals ("31/xii/24"). -
A comma that is the only separator between two date fields is no longer read as optional, so
en-ZW's "May 2019" is May 2019, where its "dd MMM,y" read it as 20 May 2019. A comma beside a space may still be left out ("May 5 2026"). -
A Hebrew date written in Hebrew numerals parses, as
hewrites it ("כ״ב בטבת ה׳תשפ״ד" is 22 Tevet 5784): ahebryear is read by its letters' values, its thousands marked by a geresh, where none ofhe's Hebrew dates read back. -
Localize.Time.parse/2, and the time ofLocalize.DateTime.parse/2, read the time formats of the:calendargiven before the Gregorian calendar's, sode's Chinese "10 vorm." parses as the formatter writes it. A:calendarthat is not a calendar module is aLocalize.UnknownCalendarError, as it is for a date. -
The localized GMT format writes its offset in the locale's digits, as TR35 and ICU do ("GMT-४" in
ne, "غرينتش-٤" inar-EG), or in those of-u-nu-or the formatter's:number_system.Localize.DateTime.Timezone.gmt_format/3andnon_location_format/3take a:number_systemoption. -
Localize.Territory.territory_from_locale/1returns the region of a-u-rg-subdivision, soen-u-rg-gbsct(Scotland) is:GBwhere it was:gbsct, and such a locale's currency, unit preferences and time zone names are its region's. -
The week fields
Y,w,W,eandchonour a locale's-u-fw-and-u-rg-, which they ignored: Friday ise5 inen-u-fw-mon.first_day_for_locale/1andmin_days_for_locale/1follow TR35's algorithm in full,-u-ca-iso8601and-u-sd-included. -
A datetime interval splits a skeleton into date and time fields, as TR35 does: one day's
:yMMMdHmis "Jun 15, 2026, 10:00 – 14:30", where it was an error, and:Hmwrites the times alone. A skeleton of date fields formats as a date interval, "Jun 15 – 16, 2026". -
A date or time its calendar lacks is a
Localize.InvalidValueErrorin formatting, intervals,Localize.Calendar.localize/3and relative time, by the calendar'svalid_date?/3andvalid_time?/4. Some fields raised for one and others wrote it, "Feb 30, 2019" or hour 25 as "1:00 AM". -
A calendar whose year begins in another month, such as a fiscal year beginning in July, names its months for the months they are, period 1 "Jul" and "7", where they were named by their place in the year, "Jan" and "1".
-
Localize starts inside an escript built with
include_priv_for: [:localize], reading its data from a copy extracted into the user cache directory that rebuilds share; one built without it stops at startup saying so, where every process crashed on its first read. Closes #58. -
A collation table file that cannot be read is logged as a warning, as a missing one already was, where any other read error stopped Localize with a
CaseClauseError. -
Localize.DateTime.Relative.to_string/2counts the quarters, and the calendar weeks from the locale's first day for:monto:sun, between two dates, where it formatted their difference in seconds as that many quarters or weekdays. -
Localize.DateTime.Relative.to_string/2counts a number of seconds beyond the range of a float, where it raisedArithmeticError. -
Localize.Duration.new/2counts years, months and days with the calendar'sdiff/3andshift/6, giving the spanDate.shift/2adds to the earlier date: 14 January to 13 July is 5 months and 29 days, where it was 30. A date the calendar lacks is an error, where it raised. -
Localize.Duration.new/2measures two zoned date-times as two moments, as ECMA-262 Temporal does: 10:00 UTC to 18:00 in Karachi is 3 hours, where it was 8. A time paired with a naive date-time no longer raises, and no pairing gives a negative duration. -
Localize.DateTime.Relative.to_string/2returns an error for aDateTimebuilt by hand without its offsets from UTC or its time zone, where it raisedArithmeticError. -
uwrites the calendar's extended year, itsextended_year/3, where it wrote the date's year: 15 June 1 BC in the Julian calendar, whose year is -1, is "0", as TR35 defines it. A calendar withoutextended_year/3is refused, as one without any other answer is. -
A parsed range of dates is ascending or inverted by its calendar's days, where the dates' fields decided: in a calendar whose year turns after its first month, as a Julian year reckoned from 25 March does, 31 December to 1 January of the same year is no longer refused as inverted.
-
Localize.Substitution.substitute/2substitutes into a template of any shape, where it raised on one it did not list, such asel's zone fallback format "[{1} ({0})]". -
A zone in parsed text reads in the locale's own words and spelling, "heure d’été de l’Est nord-américain" or "غرينتش-4", and a standard or daylight name keeps its own offset, as ICU reads it. Of 52,560 zoned date-times formatted in every locale, all but 80 in
nnhround-trip, where 14,099 did. -
Localize.Time.parse/2reads a time written in the digits of the locale's number system, as the date parser does, sobn"১০:০৫ AM" andfa"۱۰:۰۵" parse, and with them the date-times of 67 locales that write their own digits. -
The date parser reads month, quarter, weekday and era names written in the locale's digits, as
dz's abbreviated months ("༤" is April),ckb's short weekdays andff-Adlm's Japanese eras are, where transliterating the input's digits left them unmatched. -
Localize.Time.parse/2tries the locale's standard formats first, then its available formats with those carrying a zone or a flexible day period last, where their order followed a map, soms"11:59 PTG" is 23:59 rather than 11:59 in a zone "PTG". -
A flexible day period reads as the period its hour falls in, so
fr's "9:05 matin" is 09:05, not 21:05, and as AM or PM where the locale names none. A word the locale does not name is no longer read as the morning. -
Localize.Interval.to_string/3andto_parts/3format with the formats of the endpoints' calendar — its interval patterns, date and time formats and date-time pattern — where they used the Gregorian calendar's, and return an error for endpoints in two calendars. -
An interval across a change of era writes each date's era where its format has one:
format: :GyMMMdis "Dec 31, 1 BC – Jan 1, 1 AD", and the Japanese calendar's formats, which write the era with the year, "Apr 30, 31 Heisei – May 1, 1 Reiwa" across the change within 2019. -
An interval whose endpoints differ in no field it shows is the date at the requested standard format, as
Localize.Date.to_string/2and ECMA-402 write it:ko"2023. 4. 1.", where it was "2023/4/1". A date-time interval formatted whole honours:style. -
Localize.DateTime.parse/2returns the date-time where it raised for every calendar butCalendar.ISO, and reads a date-time pattern that puts the time first, asvi's "{0} {1}" does. -
The date parser tries the locale's standard formats first and then its available formats most specific first, where their order varied between runs, and reads a
yyyear beside the era of a calendar numbering its years as they are in the century around the reference year ("01.04.66 BE" is 2566 BE). -
Localize.DateTime.to_string/2andLocalize.Time.to_string/2resolve formats in the value's calendar — its date-time pattern, available formats and append items — where they used the Gregorian calendar's, andLocalize.DateTime.parse/2reads the calendar's date-time pattern as well as the Gregorian one. -
The date and interval parsers read a lunisolar date as the formatter and ICU4C write it:
r,U, leap months ("Mo2bis", "闰二月") andzh's andja's Han numerals. Where a locale writes both the calendar year and the related year, askodoes, the nearer reading is taken. -
A date read without its year is completed with the reference date's year in the calendar it is read in, so "15 Nisan" in the Hebrew calendar is in 5786 where it was in the Hebrew year 2026, and a two-digit year is read in the century around it in every calendar.
-
Era names come from the CLDR calendar a calendar's
info/0names as:era_calendar_type, so Calendrical's lunisolar Japanese calendar names its eras (令和), where it returned an error. A year written without its era reads as one of the reference date's era, as ICU reads it. -
The date parser reads a year of an era that begins before the calendar's first year from the calendar year it begins in, so "Hedar 15, 5495 AA" is the Ethiopic year -5, as Calendrical numbers it.
-
A lunisolar month written as a number is its traditional number, and a leap month is in CLDR's numeric leap pattern, as ICU4C writes them — the Chinese leap second month is "2bis" in
enand "闰2" inzhwhere it was its place in the year. A partial lunisolar date writes its related and cyclic year. -
The date parser reads a year written with its sign, as a calendar without a before era writes one below 1 — "Mar 15, -456 BE" in the Buddhist calendar — and never pivots a signed year.
-
The date and interval parsers read a year with its era in every calendar, as the formatter writes it — "Jun 1, 44 BC" is -0043-06-01 where it was 2044-06-01 and "1 BC" is year 0 — and no longer pivot a two-digit year its era qualifies.
-
A year before a Calendrical calendar's first era counts back from the era, as TR35 and
Calendar.ISOcount it —Calendrical.Gregorianyear 0 is "1 BC" andCalendrical.Rocyear -5 "6 B.R.O.C." — where it showed the year as it is, "0 BC". -
A partial date shows its era and year of era as its days do —
%{year: 2025, month: 1}with:GyMMMis "Jan 2025 AD" where the era was blank, and%{year: 0}is year "1" — and a Japanese one whose days span two eras returns an error naming the fields that settle it. -
The short localized GMT format of a whole hour keeps the offset pattern up to its hour field, as ICU does, so
he's "GMT-5" carries one left-to-right mark where it carried two. -
A binary that is not UTF-8 is an error wherever Localize reads text — number and unit parsing, number and date/time patterns, territory names, collation and inflection — where it raised
ArgumentErrororUnicodeConversionError.Localize.Unit.parse(<<255>>)returnsLocalize.InvalidValueError. -
A MessageFormat 2 date/time literal keeps the UTC offset it carries, where
|2006-01-02T15:04:06-05:00|was read as a floating time and its offset dropped. -
A fixed UTC offset carried under a UTC identifier, as a parsed ISO 8601 offset and Calendrical's fixed offsets are, names its zone in the localized GMT format ("GMT-5"), where it was named "UTC".
-
Localize.DateTime.Timezone.metazone_for/2takes a map whose fields name no real date and time, returning the zone's current metazone, where it raised. -
Localize.Locale.parent/1gives a parent found by dropping a subtag its own CLDR locale rather than the child's, soar-SA,de-AT,fr-CA,es-419andzh-Hant-HKspell numbers out with their language's RBNF rules instead of failing. A validatedundwalks to root rather than to English. -
Localize.DateTime.parse/2andLocalize.Time.parse/2withas: :mapgive a fixed offset such as "GMT+5" theDateTimezone fields the struct form resolves, where the map kepttime_zone: "GMT+5"with no offset; a named zone resolves too when the input gives its full date. -
Localize.Time.parse/2andLocalize.DateTime.parse/2read a time in the locale's-u-hc-hour cycle ("24:30" under h24) and resolve a flexible day period's hour by CLDR's day period rules. Japanese "夜中0:30" is 00:30, where it parsed as 12:30. -
Localize.Date.parse/2,Localize.DateTime.parse/2andLocalize.Date.parse_range/2parse a date with an era, whose patterns never compiled, and one whose weekday does not lead its pattern. A month abbreviation that is also a weekday name (es "mar") is no longer stripped. -
Localize.Date.parse/2resolves a month name to that month in the parsed year, so a Hebrew month name parses to its position in an ordinary or a leap year, and "Adar II" parses. -
A semantic skeleton passed as
:formatfor a partial date or time is used rather than replaced by a skeleton derived from the fields present. -
prefer: :menuonLocalize.Language.display_name/2composes CLDR'smenu="core"andmenu="extension"halves where a locale ships noalt="menu"string of its own, so"ku"renders "Kurdish (Kurmanji)" rather than the unqualified "Kurdish". Affects 571 language entries across 446 locales. -
Localize.DateTimekeeps a width the matched format's own skeleton asks for, asLocalize.Datedid, sofr:yyMdrenders "06/07/24" not "6/7/24". A zone keeps its width the same way, soel:yMdHmsvnames the zone in full, as itsHmsvformat does. -
A skeleton's weekday matches the locale's weekday formats whether spelled
E,eorc, and a year whichever ofy,Y,u,Uorr, taking the width asked for.en:EEEErenders "Saturday" where it gave "Sat", andja:U"2024年". -
jandCcarry TR35's day-period width, three or four letters wide and five or six narrow, soen:jjjjjrenders "2 p". Anabeside an hour sets only the width of the locale's own day period, TR35 treatinghaash, sozh-Hant:hakeeps its "Bh時". -
A skeleton no available format can build on starts from its first field and appends the rest, as CLDR's reference generator does, so
:Grenders "AD" and:QQQQ"3rd quarter" where both errored. -
A locale passed to
Localize.Inflectionmust be a well-formed identifier. One containing "../" reached files outside the inflection data directory, loaded them, and created a new atom for every spelling of the path. -
Quoted text in a matched format survives width adjustment:
dsb'sjjmformat "'zeg'. H:mm" rendered "eg. 14:30", its quotedzread as a zone field and deleted. -
Skeleton resolution is covered by CLDR's four skeleton conformance files, 2,871 Gregorian cases, which had not been exercised. All agree with CLDR's reference generator but 116, each recorded with its TR35 reason: hour widths,
Jpadding,Cin Indian locales andenyyyy. -
Full UCA conformance restored: all 210,155 pairs in both CLDR collation conformance files sort correctly, where 637 and 536 failed.
FractionalUCA.txtand the UCD property files had been hand-vendored at Unicode 17 and are now copied by the pipeline. -
Localize.Locale.LocaleDisplay.display_name/2renders a locale inroot(orund) as bare subtag codes per TR35's code fallback —display_name("nl-BE", locale: :root)is"nl (BE)", where it answered in English.rootis now accepted whereverundis. -
Month names come from the calendar's
cldr_month/2, so Hebrew months are named correctly in ordinary and leap years ("Adar II" included) and a Chinese leap month takes the leap-month pattern ("Second Monthbis"), where they were looked up by month number. -
The
Localize.FormatErrormessage for a selector without an annotation is rendered. Its example declaration was being formatted as a live placeholder, which failed, so the raw template was returned and a warning logged. -
Public functions return
{:error, exception}— usuallyLocalize.InvalidValueError— for options that are not a keyword list and for arguments or option values of the wrong type, where they raised; predicates answerfalse. -
Localize.Message.format/3returns aLocalize.FormatErrorwhere it raised when a placeholder,:stringor a:stringselector was given a value with no string form — a tuple, map, pid, function, a list that is not chardata, or a bitstring that is not whole bytes. -
Localize.to_string/1,2andLocalize.ListreturnLocalize.InvalidValueErrorfor an improper list and for a bitstring that is not whole bytes, where they raisedFunctionClauseError. -
Localize.Message.format/3withbackend: :niffalls back to the Elixir interpreter for bindings other than strings, 64-bit integers and floats, where some raised and others were misformatted. The NIF decodes JSON\b,\fand\uescapes and exponent-form numbers, which reached ICU as text. -
Localize.Nif.mf2_format/3returns{:error, exception}for arguments that are not a map or JSON object of strings, 64-bit integers and floats, and for a message or locale that is not a string, where it raised. -
Localize.Message.format/3andformat_to_iolist/3return an error for an improper binding list, where both backends raised, and the NIF backend skips a list element that is not a{name, value}pair, as the interpreter does. -
Localize.Currency.current_currency_for_territory/1returns CLDR's primary currency where several are current, where it depended on atom-creation order (LS gave LSL, ZW USD), and gives ML its XOF. The territory currency data carries each currency's CLDR position as:order. -
Localize.Currency.currency_strings/2, and so currency parsing, leaves out a narrow symbol that several current currencies share, such as "kr" inen, where it went to whichever currency came first in atom-creation order. One current currency among historic ones still takes it. -
Currency parsing takes the longest matching currency string by bytes rather than graphemes, so a plural spelled with as many graphemes as its singular (in te, mr, ml and 14 other locales) no longer matches as the singular and leaves the rest of the word in the remainder.
-
Localize.Number.System.number_system_names_for/1andLocalize.Number.Format.format_system_types_for/1return systems in type order (default, native, traditional, finance), where they followed atom-creation order. -
The module
mix localize.unit.gen_conversionsgenerates lists each unit once inknown_units/0, where the simple base units appeared twice. -
Localize.Territory.to_territory_code/2matches a name as written before its normalised form and, where territories share it, prefers a country to a region, then the first code alphabetically. Inscnthe name South Africa and Southern Africa share gives:ZA. -
Fuzzy currency matching takes the alphabetically first of equally close currency strings, where it took whichever came first in map order.
-
Functions taking a
Localize.LanguageTag, including every:localeoption, no longer raise on a hand-built struct with wrong-shaped fields: anillist or map is empty, a string subtag is its atom, and anything else returnsLocalize.InvalidLocaleError. -
Interpolating a parsed
Localize.LanguageTaginto a string gives its BCP 47 form, where it raised because the tag had no canonical id yet. -
Localize.Territory.territory_from_locale/1takes the territory of a language tag struct that has none from its likely subtags, so a parsed "fr" gives:FRwhere it gave the default locale's:US. -
A locale that Localize holds data for now resolves to itself.
resolve_cldr_locale/1asked the language matcher even for an exact identity, soar-EG, which formats inarabdigits, resolved toar, which useslatn. -
rootis accepted whereverundis. TR35 makes it a synonym forund, but CLDR's alias data does not carry it, soLocalize.validate_locale("root")failed whileLocalize.Locale.cldr_locale_id_from("root")succeeded. -
mix localize.download_localesrejects a name that is not a CLDR locale rather than turning it into an atom, and unit preference lookups no longer create atoms from the prefixes of a usage. -
Locale downloads retry a CDN server error, timeout or dropped connection up to three times with exponential backoff, where one failure was final; a 404 still fails at once.
:http_retries,:http_retry_delayand:http_ip_familyconfigure the retries and the address family. -
Skeleton formats honour the field widths asked for. TR35 matches a skeleton then adjusts its widths to the request; only the first step happened, so
format: :MMMMrendered "Jul" inenwhere the literal pattern"MMMM"rendered "July". -
A skeleton combining a date and a time adjusts both halves' field widths to the request, where a requested zone symbol took the matched format's —
:MMMMdjmsOgave "GMT" not "GMT+0". Every Gregorian case in CLDR's datetime conformance fixture now passes, from 47 of 78. -
Skeleton matching is deterministic. Where two formats sat the same distance from a skeleton the winner came from the formats map's iteration order, which depends on when the atom keys were created — so one skeleton could resolve differently between runs.
-
A matched format keeps a field width the locale states deliberately. TR35 leaves a pattern field alone where the matched
availableFormatsid already carries the requested width, soru'syMdrendersdd.MM.yrather than being narrowed tod.M.y. -
Resolving a date-time skeleton no longer creates atoms.
Localize.DateTime.Format.Match.split_fractional_seconds/1interned the remainder left after stripping the fractional-second field; it now resolves against existing atoms only, with identical output. -
A skeleton whose date and time halves no single available format covers joins them at the length TR35 derives from the requested date fields rather than always at
:medium. -
:date_formatand:time_formatresolve a skeleton through TR35 matching, where a missing skeleton errored. A skeleton date format joins through the locale's date-time pattern, sojarenders "2026年6月15日 14:30". -
Localize.DateTime.to_string/2andto_parts/2keep both halves of a map holding some date and some time fields, joined through the locale's wrapper, where%{year: 2026, month: 6, hour: 14}rendered "6/2026". A partial value given a skeleton is formatted with it. -
Partial times keep the locale's hour cycle, so
%{hour: 14, minute: 30}is "14:30" inderather than "2:30 PM". A-u-hc-override now applies to partial times and throughoutLocalize.DateTime, which ignored it. -
Localize.Time,Localize.DateTimeandLocalize.Intervalrender a-u-hc-h11or-u-hc-h24cycle with theKorkhour ("0:30 AM", "24:30") and resolvejin an interval skeleton. Under-u-hc-, an explicithorHkeeps the cycle it names, as in ICU. -
Time skeletons resolve day periods as ICU does:
harenders "h a" andhb"h b" where both gave a flexible day period, and anHorkskeleton drops a requested one.Jdrops it as TR35 requires andCignores a-u-hc-override. -
Localize.DateTime.to_string/2applies a-u-hc-hour cycle to a skeleton that matches an available format exactly or takes append items, and renders anXorxzone field requested by a skeleton, which it dropped. -
bandBjudge noon and midnight at the precision their pattern shows, as ICU does, so "h B" renders 12:05 as "12 noon".VVVrendersEtc/UTCas the unknown location, "Unknown Location" inen, rather than "UTC". -
Shas exactly as many digits as the field has letters, as TR35 specifies, where it stopped at six and at the value's precision, soss.SSSon 45.9 seconds is "45.900".Uwithout cyclic year names formats asy, andYYYYYYpads rather than rendering empty. -
Timezone format symbols follow TR35:
Vis the BCP 47 short identifier ("usnyc"),VVVthe exemplar city ("New York") andVVVVthe generic location format, where all three returned the IANA name or a GMT offset. An alias likeUS/Easternresolves to its canonical zone. -
The generic location format names a country where TR35 requires one — a single-zone territory, or a zone in
primaryZones— soVVVVrendersEurope/Romeas "Italy Time".primaryZonesis new supplemental data, read frommetaZones.xmlbecause the JSON omits it. -
vandvvvvfall back to the generic location format before the localized GMT format, which TR35 specifies for the generic symbols only, soAsia/Kolkatais"India Time"rather than"GMT+05:30". The specific symbolszandzzzzstill fall straight through to GMT. -
ZZZZspells a zero offset as"GMT+00:00", matchingOOOOas TR35 requires of the localized GMT formats. A metazone with no daylight name also resolves a generic request to its standard name, sovvvvis"Greenwich Mean Time"forEtc/GMTand"India Standard Time"forAsia/Kolkata. -
An unknown timezone offset renders the locale's
gmtUnknownFormat—"GMT+?", translated where the locale translates it — instead of a hard-coded English"GMT". -
The short localized GMT format drops the hour's leading zero when the offset has minutes, as TR35 specifies, so
Ofor India is "GMT+5:30" rather than "GMT+05:30". -
Localize.Duration.to_time_string/2no longer raises on a field longer than two letters, such as"hhh:mm"; the field formats as U+FFFD, as in a date pattern. -
Date intervals use the locale's interval pattern where CLDR ships a matching one, adjusted to the requested widths, rather than gluing two full endpoints —
deat:mediumrenders "03.–05.05.2026". TR35's closest-match step was skipped. -
An interval whose endpoints differ in no field it shows is one value, as TR35's interval algorithm has it, where
fields: :year_and_monthgave "Jul – Jul 2024". A selection of fields without a year writes none for dates of two years, sofields: :month_and_dayacross the new year is "Dec 30 – Jan 2". -
Time intervals across noon take CLDR's day-period pattern, "10:00 AM – 2:00 PM" rather than "10:00 – 2:00 PM", and an
:hinterval within one hour formats a single time. A skeleton the interval table lacks, such as:hms, joins both times through the fallback pattern. -
Same-day datetime intervals join the date, shown once, to CLDR's time interval pattern as TR35 specifies, so
en:shortis "7/6/24, 10:00 – 10:30 AM" rather than "7/6/24, 10:00 AM – 10:30 AM". Datetimes differing only in seconds format as an interval at:mediumrather than as one value. -
Localize.DateTime.Relative.to_string/2andto_parts/2return{:error, %Localize.InvalidValueError{}}for a value that is not a number, date, time or datetime, and for options that are not a keyword list, rather than raisingFunctionClauseError. -
Localize.DateTime.Relative.to_string/2andto_parts/2measure a value against a:relative_toof another type where the conversion is unambiguous, and returnLocalize.InvalidValueErrorotherwise. The baseline was silently replaced by the current time. -
Localize.DateTime.Relative.to_string/2andto_parts/2format the number for the locale ("in 1,000 days", with ECMA-402 parts) and select the pattern by the displayed number's plural category. A float with:unitis a count of that unit, where it was read as seconds. -
A display name with no entry for the requested preference and none for
:standardfalls back to a stable key rather than whichever the map yielded first, which depended on atom creation order in the VM. -
Localize.Number.Rbnf.rule_names_for_locale/1returns its names sorted. On OTP 26 and later their order followed the order the VM had created the atoms, so it varied between runs. -
Localize.Numberformats a non-default numbering system with the data CLDR gives the locale for it:fa-u-nu-latnis "1,000.5", not "1٬000٫5", anden-u-nu-arab"١٬٠٠٠٫٥", not "١,٠٠٠.٥". -
The long currency format no longer raises in
sdandckb, whosearabhas no unit patterns and now takes theirlatnones, and writes a currency with no display name by its code, as TR35 specifies ("٣٫٠٠ USD" inckb). -
Number patterns give each currency sign width its TR35 meaning —
¤¤the ISO code,¤¤¤the plural name,¤¤¤¤¤the narrow symbol — and take the currency's decimal places and CLDR's currency spacing, so¤#,##0.00gives "CHF 1,234.56" and "¥1,235". -
Localize.Number.to_string/2rounds significant digits and scientific mantissas half-even, or by:rounding_mode, where it rounded half-up. A mantissa shows the digits TR35 derives from its pattern, so "##0.##E0" renders 12345 as "12.3E3". -
Localize.Number.to_string/2andto_parts/2format a pattern with quoted literal text, such as#,##0 'units', where the pattern lexer looped and exhausted memory. A pattern with a character no pattern syntax accepts, such as a trailing*, returns an error. -
Localize.Number.to_string/2keeps the whitespace a pattern ends with, as ICU does and asto_parts/2already did. A:wrapperfunction receives a quoted pattern character tagged:literal, which it never received. -
Localize.Number.resolve_currencies/2andresolve_currency/2match a currency name or code only as a whole word, so "100 US dolars" no longer resolves to the Argentine peso from the "ars" it ends with. -
-0.0keeps its sign,:max_fractional_digitsapplies to a scientific mantissa, and compact formatting keeps two significant digits below 1 and groups on ICU's MIN2 rule. -
Localize.Number.to_string/2no longer raises on a very small float with a large:max_fractional_digits—to_string(1.0e-308, max_fractional_digits: 400)raisedArithmeticErrorbecause reconstructing the value divided by a power of ten outside the double range. -
Compact number formatting in Swahili was wrong by a factor of ten, and of a thousand for some ranges. The compact divisor counts zeros in the pattern, which included the negative subpattern —
elfu 000;elfu -000read as six zeros rather than three. -
Plural selection now keeps a
Decimal's fraction operands.pluralize/3converted to a float first, and TR35 selects on the visible fraction digits and their value, so1.2was treated as "1.200" and choseotherwhere Serbian, Croatian and Bosnian requirefew. -
Localize.Unit.to_string/2selects the plural form of a rounded number by the digits the formatter displays, sohr0.0045 hours is "0,004 sata" rather than "0,004 sati". -
Unit preferences read the
-u-msand-u-mukeywords, in TR35's ordermu > ms > rg > (likely) region:en-u-rg-uszzzz-ms-metricgives celsius anden-US-u-rg-uszzzz-ms-uksystemimperial gallons. -
A unit whose quantity CLDR ships no preferences for falls back to base units rather than erroring:
preferred_units/2onamperegives[:ampere], oncandela-per-cubic-foot["candela-per-cubic-meter"]. Derived compound base units come back as strings, not atoms. -
A unit usage supplied as a string resolves instead of silently falling back to
:default. Every usage CLDR defines now has an interned atom, whichLocalize.Unit.Preference.known_usages/0lists, sousage: "fluid"returns imperial units foren-GBwhere it returned cubic inches. -
RBNF alternation branches are evaluated against the number rather than the remainder. A branch carrying a quotient substitution saw 0, so Russian's 201 spelled "нольсти" instead of "двести"; Ukrainian, Polish, Czech and others were affected the same way.
-
RBNF fraction digits spell with the current rule set, a fraction rule's bracketed integer part survives, plural selection uses the numerator, and the
>>>preceding-rule chain is built in full. -
RBNF optional rule text renders unconditionally where ICU would not have split the rule in two — its base value must be positive and an even multiple of its divisor — so Afrikaans spells the year 1100 "elf honderd nul".
-
RBNF rule selection implements ICU's rollback rule: a rule taking a remainder is passed over for the one below when the number divides exactly but the rule's own base value does not. Bulgarian's financial cardinals no longer spell a trailing "и нула".
-
Localize.Message.format/3selects a plural or ordinal variant by the number as its:number,:integer,:offsetor:percentfunction displays it, so Russian{2 :number minimumFractionDigits=1}isother. An:integeroperand given as aDecimalformats rather than raising. -
MessageFormat 2 literals keep all their code points, as TR35 requires, where the parser normalized them to NFC; keys still match a selector's value in NFC.
Localize.Message.Print.to_string/2quotes a literal that is not an MF2 name, so a canonical message parses back unchanged.