CLDR 48 was released last week which has triggered new versions of several of the core ex_cldr libraries. Here’s a summary of newsworthy changes:
Ex_cldr
Bug Fixes
- Parsing language tags with the
und(unknown) language now correctly substituted for a known language name if one can be found inCldr.Locale.likely_subtags/0. For example:
iex> Cldr.validate_locale("und-TW")
{:ok, TestBackend.Cldr.Locale.new!("zh-Hant-TW")}
-
The
:gettext_locale_namefield of at:Cldr.LanguageTag.t/0is now set exactly as returned fromGettext.known_locale_names/1. Previously it was being tranformed to a BCP 47 format locale (replacing “_” with “-”). That is no longer the case. -
Territory containment was previously not considering nested containers and is now doing so. For example, territory “019” (Americas) includes “419” (Latin America and the Carribbean) but “419” was not appearing in the list for “019” since CLDR categorises “grouping” territories separately. This should not affect any consumers, only library writers.
Enhancements
-
Update to CLDR 48 data.
-
Add
Cldr.Locale.Match.best_match/2andCldr.Locale.Match.match_distance/3to implement the CLDR Language Matching algorithm. This is a more formal and testable approach that the previous mechanism. This new function will be use to implement better matching between a known CLDR locale name and supported Gettext locale names. Therefore it is possible that thegettext_locale_namefield of at:Cldr.LanguageTag.t/0may change with this release.
Cldr Numbers
-
Adds
Cldr.Number.to_ratio_string/3to format numbers as a rational string. Numbers can also be formatted as an integer and a ratio. Since different rendering systems (browsers, terminals etc) reproduce ratios with differing fidelity, there are options to format the ratio as a precomposed fraction, super- and subscript numerals separated by a fraction slash or normal numerals separated by a fraction slash. -
For number systems other than
Latn, the:prefer: :precomposedandprefer: :super_suboptions have no effect.
Examples
| Function call | Result |
|---|---|
Cldr.Number.to_ratio_string(0.75) |
{:ok, "3⁄4"} |
Cldr.Number.to_ratio_string(0.75, prefer: :super_sub) |
{:ok, "¹⁄₂"} |
Cldr.Number.to_ratio_string(0.75, prefer: :precomposed). |
{:ok, "½"} |
Cldr.Number.to_ratio_string(0.875, prefer: [:precomposed, :super_sub]) |
{:ok, "⅞"} |
Cldr.Number.to_ratio_string(3.875, prefer: [:precomposed, :super_sub]) |
{:ok, "3\u2060⅞"} |
Cldr.Number.to_ratio_string(0.923, prefer: [:precomposed, :super_sub]) |
{:ok, "⁹⁄₁₀"} |
Cldr Dates Times
Breaking Changes
- Changes in the implemementation of time zone formatting may result in different (but semantically the same) strings for formats that include time zone information. See Bug Fixes below.
Bug Fixes
-
Fixes the generation of time zone formats to be in accordance with CLDR Time Zone Names. This is particularly true of the following time zone formats:
- Generic non-location format (eg “Pacific Time”, “PT”)
- Generic partial location format (eg “Pacific Time (Canada)”)
- Generic location format (eg “France Time”, “Adelaide Time”)
- Specific non-location format (eg “Pacific Standard Time”, “PST”)
- Localized GMT format (eg “GMT+03:30”, “Гринуич+03:30”, “GMT+?”)
-
Fixes
Cldr.DateTime.Relative.to_string/3when the relative value is an integer of2or-2and the locale provides a specific word for that value. For example, in English there are words for 1 day in advance or behind (“today” and “yesterday”). When specifing 2 days ago, English has no specific word so the result is “2 days ago”. In German the result is “vorgestern”. Similary for the English “in 2 days”, the German result will be “übermorgen”.
Enhancements
-
Updates to CLDR 48 data.
-
Adds
Cldr.DateTime.Format.gmt_unknown_format/1to return a string representing an unknown GMT offset. -
Adds
style: :attoCldr.DateTime.Relative.to_string/2. This allows formatting of string like “tomorrow at 3:30 PM”. The default isstyle: :standard. -
Significant improvement in support of date time skeletons. Skeletons are a flexible way to express desired formatting in a locale-indepdendent way.
-
Support is added for the “j”, “J” and “C” input skeleton symbols which will be substituted with the locales preferred hour formats. These changes also improve implicit format generation derived from whatever date and time data is passed to the
Cldr.Date.to_string/3,Cldr.Time.to_string/3,Cldr.DateTime.to_string/3functions.
Cldr Calendars
Primarily bug fixes to the Julian calendar and enhancements to make the Julian calendar configurable (in history, the Julian calendar new year has celebrated at different times of year).
Bug Fixes
-
Fix
year/1,month/2anddays_in_month/2for Julian calendars. -
CLDR 48 fixes the dates for some Japanese eras.
Enhancements
-
Use CLDR 48 calendar data. The primary change for calendars is to the era dates for the Japanese calendar.
-
Adds
use Cldr.Calendar.Julian, new_year_starting_month_and_day: {month_of_year, day_of_month}to allow modelling Julian calendars that don’t start on January 1st. -
Adds
Cldr.Calendar.convert/2which converts either a date or a date range to another calendar.
Cldr Calendars Japanese
Bug Fixes
-
Several era dates were fixed in CLDR 48.
-
The
calendar_year/3callback was fixed to return the regnal year (year of the reign of the then-current emperor) as expected by date time formatting.
Other library updates
These are updates primarily to be compatible with CLDR 48
- ex_cldr_units
- ex_cldr_routes including REAME update to recognise the original work by @BartOtten
- ex_cldr_locale_display
- ex_cldr_person_names
- ex_cldr_territories
Evolution of ex_cldr
Work continues on localize as the “version 3” of ex_cldr. The objective is to get the initial release covering the core, numbers, calendars and date/times released in the first quarter of 2026.
It’s my intention to continue to maintain ex_cldr and friends for as long as there is usage and bug reports. However I hope this will be the last update for ex_cldr to use a new version of CLDR data so that I can focus more time on localize. That scenario can be revised if required around March 2026 with CLDR 49.






















