Date formatting and i18n
Metanorma renders dates through a single extended strftime-style formatter shipped in the isodoc-i18n gem (IsoDoc::ExtendedDateFormatter) isodoc-i18n ≥ v1.5.1.
The formatter supports POSIX %E* / %O* modifiers for era years and alternative numbering systems on top of locale-aware month and day names, so a single format string can express anything from a plain Gregorian date to a Japanese regnal-year colophon or a Continental-European bibliographic date with a Roman-numeral month.
This page documents:
- the format-string grammar accepted by
IsoDoc::ExtendedDateFormatter, - how it is reached from the
<date value="…" format="…"/>presentation-XML element and from thedate_i18nLiquid filter, - the per-flavour YAML keys that hold the canonical format strings for each locale and rendering mode.
Format string grammar
Most strftime conversions work as in Ruby's Date#strftime — %Y, %m, %d, %-m, %-d, %H, %M, etc. — and pass through to the underlying date. On top of that, the formatter recognises three families of extended tokens:
| Token family | Meaning | Backed by |
%EY, %Ey, %EC | Era year. `%EY` is the full era+year string (e.g. `令和6`), `%Ey` is the era year alone (`6`), `%EC` is the era name alone (`令和`). | japanese_calendar gem for %JN / era_year. |
%Om, %Od, %OY, %Oy | Alternative numbering for date components. The *script* of the digits is controlled by an optional argument (see below) rather than the underlying month/day value, which is unchanged. | twitter_cldr spellout rules, the roman-numerals gem for Roman. |
%B, %b, %h, %A, %a, %P, %p | Localised month / weekday / am-pm names, sourced from CLDR for the active locale. | twitter_cldr calendar data. |
The %E* and %O* tokens take an optional square-bracket-delimited argument naming the numbering system or calendar:
| Argument | Effect |
numeric (or latn) | Plain Arabic numerals (6, 9, 30). Default when no argument is given. |
spellout | CLDR spellout: locale-appropriate spelled-out form (Japanese kanji `六`/`九`/`三十`, French `six`/`neuf`/`trente`, etc.). |
hanidec | Han ideographic digits (`〇`/`一`/`二`/…/`九`), positional rather than spelled out. |
roman, roman-lower | Roman numerals (IX, ix). |
cal=japanese / cal=gregorian | For `%E*` tokens, select the calendar backend. Defaults to `japanese` for the `ja` locale and `gregorian` otherwise. |
Arguments can be combined comma-separated, e.g. %EY{spellout, cal=japanese}. The first non-key=value argument is treated as the numbering system.
Calendar selection
The era family (%EY / %Ey / %EC) dispatches to a calendar backend selected via cal=. Currently implemented:
cal=japanese— Japanese era (Reiwa, Heisei, Shōwa, …), backed by thejapanese_calendargem. Returns the Gregorian year if the date predates Meiji.cal=gregorian— Gregorian year (%EYreturns the year as a number,%ECreturns an empty string).
The following CLDR calendar identifiers are reserved as extension points and will raise NotImplementedError when used today: roc (Republic of China / Minguo), buddhist (Thai), persian (Solar Hijri), islamic (Hijri), indian (Saka), hebrew. The framework dispatches on them so they can be implemented later without API changes.
If cal= is omitted, the default calendar is japanese when the active locale is ja and gregorian otherwise.
Worked examples
| Format string | Locale | Output (for 2024-09-30) |
| ”%Y-%m-%d“ | (any) | 2024-09-30 |
| ”%-d %B %Y“ | en | 30 September 2024 |
| ”%B %-d, %Y“ | en | September 30, 2024 |
| ”%-d.%Om[roman].%Y“ | en | 30.IX.2024 |
| ”%EY[numeric]年%-m月%-d日“ | ja | 令和6年9月30日 |
| ”%EY[spellout]年%Om[spellout]月%Od[spellout]日“ | ja | 令和六年九月三十日 |
| ”%EY[hanidec]年%Om[hanidec]月%Od[hanidec]日“ | ja | 令和六年九月三〇日 |
`<date>` element
Inside the presentation XML, the <date value="ISO-DATE" format="FMT"/> element renders by passing value and format to IsoDoc::I18n#date, which delegates to IsoDoc::ExtendedDateFormatter. The active locale and script come from the IsoDoc::I18n instance for the document being rendered, so <date value="2024-09-30" format="%EY[numeric]年%-m月%-d日"/> in a ja document produces 令和6年9月30日 automatically.
`date_i18n` Liquid filter
For Liquid templates inside flavour gems and Liquid snippets in authored documents, the same formatter is exposed as the date_i18n filter, registered globally on Liquid::Environment.default by IsoDoc::I18n.
Signature:
{{ value | date_i18n: format }}
{{ value | date_i18n: format, lang }}
{{ value | date_i18n: format, lang, calendar }}value— an ISO 8601 date string (YYYY-MM-DD,YYYY-MM, orYYYY). Returned unchanged ifnilor empty.format— the extended strftime format string.lang(optional) — override the active locale just for this call. When omitted, the locale is inherited from theIsoDoc::I18ninstance.calendar(optional) — override the calendar (”japanese“, ”gregorian“, …). When omitted, the calendar is derived from the locale.
Format strings inline cleanly inside Liquid string literals because the %E*[…] / %O*[…] argument syntax uses square brackets, which do not clash with Liquid's {{...}} template delimiters:
{{ revdate | date_i18n: "%-d.%Om[roman].%Y" }}
{{ revdate | date_i18n: "%EY[numeric]年%-m月%-d日", "ja" }}For non-trivial format strings, the convention in metanorma flavour gems is to centralise them in a YAML key and reference by name — see the per-flavour YAML keys section below.
Per-flavour YAML keys
Each Metanorma flavour gem ships a lib/isodoc/<flavour>/i18n-<lang>.yaml file with localisation data. Date format strings live under a date_format key with two branches keyed by rendering mode:
# Example: metanorma-jis/lib/isodoc/jis/i18n-ja.yaml
date_format:
default: # standalone rendering: kanji year, Arabic month/day
year: "%EY[spellout]年"
year_month: "%EY[spellout]年%-m月"
full: "%EY[spellout]年%-m月%-d日"
japanese_numbering: # autonumbering-style: japanese — full kanji
year: "%EY[spellout]年"
year_month: "%EY[spellout]年%Om[spellout]月"
full: "%EY[spellout]年%Om[spellout]月%Od[spellout]日"The inner year / year_month / full keys correspond to the three input arities (year only, year + month, full year-month-day).
The flavour's i18n class picks the right format based on the date arity and the active rendering mode, then calls IsoDoc::ExtendedDateFormatter.format(...).
Downstream flavours can override individual sub-trees via Ruby's Hash#deep_merge — for example, metanorma-plateau extends metanorma-jis and overrides only date_format.default to switch from the "spellout" kanji year (令和六年) to numeric Arabic year (令和6年), while inheriting the japanese_numbering sub-tree from JIS unchanged.
See also
- Localization — how
lib/isodoc/i18n-*.yamlfiles are loaded and merged across flavours. - Metadata and predefined text — how
revdateand other date-bearing metadata fields reach the rendering pipeline.