diff --git a/deps.edn b/deps.edn index f2fd153..909a1b0 100644 --- a/deps.edn +++ b/deps.edn @@ -20,6 +20,7 @@ :jolt/provides {jolt.time ["java.time.format.DateTimeFormatter" "java.time.format.DateTimeFormatterBuilder" "java.time.format.FormatStyle" + "java.time.format.TextStyle" "java.time.ZoneOffset" "java.time.ZoneId" "java.time.ZonedDateTime" diff --git a/src/jolt/time/fmt.clj b/src/jolt/time/fmt.clj index 862d2f8..3c5fe71 100644 --- a/src/jolt/time/fmt.clj +++ b/src/jolt/time/fmt.clj @@ -189,33 +189,83 @@ ;; formatting, which is this library (RFC 0008). Carry the constants and ;; getDefault/setDefault the surface needs (tick asks for Locale/getDefault); the ;; base default is English. +;; +;; A Locale is its language, script, country and variant. Its :id, the key its +;; data is registered under, is the BCP-47-style join of the first three +;; ("en-US", "zh-Hant-TW", "" for ROOT); jolt core narrows a missed id to its +;; language. It prints as the JDK's toString does: language_COUNTRY_VARIANT, then +;; _#Script ("zh_TW_#Hant", "sr__#Latn"). +(defn- mk-locale [lang script country variant] + (impl/value :jolt.time/locale + {:id (str/join "-" (remove empty? [lang script country])) + :lang lang :script script :country country :variant variant})) +(defn- lf [l k] (or (impl/field l k) "")) +(defn- locale-of + ([lang] (locale-of lang "" "")) + ([lang country] (locale-of lang country "")) + ([lang country variant] + (mk-locale (str/lower-case (str lang)) "" (str/upper-case (str country)) (str variant)))) +(defn- bcp-variant? [v] (boolean (re-matches #"(?i)[0-9a-z]{5,8}|[0-9][0-9a-z]{3}" v))) +(defn- parse-tag [tag] + (let [[lang & more] (str/split (str tag) #"-") + lang (str/lower-case (or lang "")) + lang (if (= lang "und") "" lang)] + (loop [xs more script "" country "" variants []] + (let [x (first xs)] + (cond + (nil? x) (mk-locale lang script country (str/join "_" variants)) + (and (= "x" (str/lower-case x)) (= "lvariant" (some-> (second xs) str/lower-case))) + (mk-locale lang script country (str/join "_" (concat variants (drop 2 xs)))) + (and (empty? script) (empty? country) (empty? variants) (re-matches #"(?i)[a-z]{4}" x)) + (recur (rest xs) (str (str/upper-case (subs x 0 1)) (str/lower-case (subs x 1))) country variants) + (and (empty? country) (empty? variants) (re-matches #"(?i)[a-z]{2}|[0-9]{3}" x)) + (recur (rest xs) script (str/upper-case x) variants) + (bcp-variant? x) (recur (rest xs) script country (conj variants x)) + ;; an extension or private-use section: not modeled, ends the parse + :else (mk-locale lang script country (str/join "_" variants))))))) +(defn- locale-string [l] + (let [lang (lf l :lang) script (lf l :script) country (lf l :country) variant (lf l :variant)] + (str lang + (when (or (seq country) (seq variant) (seq script)) (str "_" country)) + (when (seq variant) (str "_" variant)) + (when (seq script) (str "_#" script))))) +(defn- locale-tag [l] + (let [lang (lf l :lang) variant (lf l :variant) + base (str/join "-" (remove empty? [(if (empty? lang) "und" lang) (lf l :script) (lf l :country)]))] + (cond (empty? variant) base + (every? bcp-variant? (str/split variant #"_")) (str base "-" (str/replace variant "_" "-")) + :else (str base "-x-lvariant-" (str/replace variant "_" "-"))))) (statics! ["Locale" "java.util.Locale"] - {"getDefault" (fn [& _] (impl/value :jolt.time/locale {:id "en"})) + {"getDefault" (fn [& _] (locale-of "en")) "setDefault" (fn [_] nil) - "forLanguageTag" (fn [tag] (impl/value :jolt.time/locale {:id (str tag)})) - "ENGLISH" (impl/value :jolt.time/locale {:id "en"}) "US" (impl/value :jolt.time/locale {:id "en-US"}) - "FRENCH" (impl/value :jolt.time/locale {:id "fr"}) "FRANCE" (impl/value :jolt.time/locale {:id "fr-FR"}) - "GERMAN" (impl/value :jolt.time/locale {:id "de"}) "GERMANY" (impl/value :jolt.time/locale {:id "de-DE"}) - "ITALIAN" (impl/value :jolt.time/locale {:id "it"}) "ITALY" (impl/value :jolt.time/locale {:id "it-IT"}) - "JAPANESE" (impl/value :jolt.time/locale {:id "ja"}) "JAPAN" (impl/value :jolt.time/locale {:id "ja-JP"}) - "KOREAN" (impl/value :jolt.time/locale {:id "ko"}) "KOREA" (impl/value :jolt.time/locale {:id "ko-KR"}) - "CHINESE" (impl/value :jolt.time/locale {:id "zh"}) "CHINA" (impl/value :jolt.time/locale {:id "zh-CN"}) - "SIMPLIFIED_CHINESE" (impl/value :jolt.time/locale {:id "zh-CN"}) "TRADITIONAL_CHINESE" (impl/value :jolt.time/locale {:id "zh-TW"}) - "UK" (impl/value :jolt.time/locale {:id "en-GB"}) "CANADA" (impl/value :jolt.time/locale {:id "en-CA"}) - "CANADA_FRENCH" (impl/value :jolt.time/locale {:id "fr-CA"}) "ROOT" (impl/value :jolt.time/locale {:id ""})}) -;; the two-arg ctor joins language and country like the constants do ("de" "DE" -;; -> "de-DE"); the one-arg ctor's argument is a *language* subtag, so "en_US" -;; stays malformed and resolves to ROOT at lookup time -(defn- locale-ctor [lang & r] - (let [lang (str/lower-case (str lang))] - (if (and (seq r) (seq (str (first r)))) - (impl/value :jolt.time/locale {:id (str lang "-" (str/upper-case (str (first r))))}) - (impl/value :jolt.time/locale {:id lang})))) -(__register-class-ctor! "Locale" locale-ctor) -(__register-class-ctor! "java.util.Locale" locale-ctor) + "forLanguageTag" parse-tag + "of" locale-of + "ENGLISH" (locale-of "en") "US" (locale-of "en" "US") + "FRENCH" (locale-of "fr") "FRANCE" (locale-of "fr" "FR") + "GERMAN" (locale-of "de") "GERMANY" (locale-of "de" "DE") + "ITALIAN" (locale-of "it") "ITALY" (locale-of "it" "IT") + "JAPANESE" (locale-of "ja") "JAPAN" (locale-of "ja" "JP") + "KOREAN" (locale-of "ko") "KOREA" (locale-of "ko" "KR") + "CHINESE" (locale-of "zh") "CHINA" (locale-of "zh" "CN") + "SIMPLIFIED_CHINESE" (locale-of "zh" "CN") "TRADITIONAL_CHINESE" (locale-of "zh" "TW") + "UK" (locale-of "en" "GB") "CANADA" (locale-of "en" "CA") + "CANADA_FRENCH" (locale-of "fr" "CA") "ROOT" (locale-of "")}) +;; (Locale. lang), (Locale. lang country), (Locale. lang country variant). The +;; one-arg ctor's argument is a *language* subtag, so "en_US" stays malformed +;; and resolves to ROOT at lookup time +(__register-class-ctor! "Locale" locale-of) +(__register-class-ctor! "java.util.Locale" locale-of) (impl/register-type! :jolt.time/locale - {:eq (fn [a b] (= (impl/field a :id) (impl/field b :id))) :hash (fn [l] (hash (impl/field l :id))) - :str (fn [l] (impl/field l :id)) :cmp nil :classes #{"java.util.Locale" "Locale"}}) + {:eq (fn [a b] (and (= (impl/field a :id) (impl/field b :id)) (= (lf a :variant) (lf b :variant)))) + :hash (fn [l] (hash [(impl/field l :id) (lf l :variant)])) + :str locale-string :cmp nil :classes #{"java.util.Locale" "Locale"}}) +(__register-class-methods! :jolt.time/locale + {"toString" locale-string + "toLanguageTag" locale-tag + "getLanguage" (fn [l] (lf l :lang)) + "getScript" (fn [l] (lf l :script)) + "getCountry" (fn [l] (lf l :country)) + "getVariant" (fn [l] (lf l :variant))}) ;; jolt core's java.text.SimpleDateFormat renders MMM/MMMM/EEE/EEEE through a ;; :date-names extension point and carries ROOT alone, because core has no locale @@ -240,20 +290,35 @@ ;; library keeps working against a jolt that has only some of them. (defn- register-point! [reg point entries] (try - (doseq [[id data] entries :when (not= id "")] - (reg point id data)) + ;; under the id and, for language-COUNTRY, under the Locale's printed form + ;; too ("en_US"): a jolt before 0.9 looks the data up by that + (doseq [[id data] entries :when (not= id "") + k (distinct [id (str/replace id "-" "_")])] + (reg point k data)) true (catch Exception e - (if (re-find #"no extension point" (or (ex-message e) "")) + ;; an undeclared point, or a field an older jolt's point does not declare + ;; (the caller falls back to the fields it has) + (if (re-find #"no extension point|has unknown field" (or (ex-message e) "")) false (throw e))))) (defn- register-locale-points! [] (let [reg host/register-extension!] - (register-point! reg :date-names - (for [[id spec] ld/locales] - [id {:months (:months spec) :months-short (:months-short spec) - :days (:days spec) :days-short (:days-short spec)}])) + ;; the narrow and standalone forms ride on :date-names: a jolt whose point + ;; does not declare those fields refuses the entry, so it falls back to the + ;; four it always had + (when-not (register-point! reg :date-names + (for [[id spec] ld/locales] + [id (merge {:months (:months spec) :months-short (:months-short spec) + :days (:days spec) :days-short (:days-short spec)} + (get-in ld/display-names [id :text]))])) + (register-point! reg :date-names + (for [[id spec] ld/locales] + [id {:months (:months spec) :months-short (:months-short spec) + :days (:days spec) :days-short (:days-short spec)}]))) + (register-point! reg :field-names + (for [[id spec] ld/display-names] [id {:names (:field-names spec)}])) (register-point! reg :number-symbols nd/number-symbols) (register-point! reg :currency-data nd/currency) true)) @@ -262,6 +327,33 @@ ;; has to run once. (defonce ^:private locale-points-registered (boolean (register-locale-points!))) +;; TextStyle: the six styles a text field renders in, in JDK declaration order, +;; declared through jolt.host/register-enum! as FormatStyle is below. Month, +;; DayOfWeek and ChronoField's getDisplayName take one (core java.time), and read +;; this library's names through the :date-names and :field-names points. +(def ^:private text-style-names ["FULL" "FULL_STANDALONE" "SHORT" "SHORT_STANDALONE" "NARROW" "NARROW_STANDALONE"]) +(def ^:private text-style-ordinal (zipmap text-style-names (range))) +(def ^:private text-styles + (mapv (fn [s] (impl/value :jolt.time/text-style {:style s})) text-style-names)) +(defn- text-style [n] (nth text-styles (text-style-ordinal n))) +(defn- text-style-name [s] (impl/field s :style)) +(defn- standalone? [s] (str/ends-with? (text-style-name s) "_STANDALONE")) +(impl/register-type! :jolt.time/text-style + {:eq (fn [a b] (= (text-style-name a) (text-style-name b))) :hash (fn [s] (hash (text-style-name s))) + :str text-style-name + :class "java.time.format.TextStyle" + :enum? true + :cmp (fn [a b] (- (text-style-ordinal (text-style-name a)) (text-style-ordinal (text-style-name b)))) + :classes #{"java.time.format.TextStyle" "TextStyle" + "java.lang.Comparable" "Comparable" "java.lang.Enum" "Enum"}}) +(__register-class-methods! :jolt.time/text-style + {"isStandalone" (fn [s] (standalone? s)) + "asStandalone" (fn [s] (if (standalone? s) s (text-style (str (text-style-name s) "_STANDALONE")))) + "asNormal" (fn [s] (if (standalone? s) (text-style (subs (text-style-name s) 0 (- (count (text-style-name s)) 11))) s))}) +(if-let [register-enum! (resolve 'jolt.host/register-enum!)] + (register-enum! "java.time.format.TextStyle" (mapv vector text-style-names text-styles)) + (statics! ["TextStyle" "java.time.format.TextStyle"] (zipmap text-style-names text-styles))) + ;; FormatStyle is an enum: its constants in JDK declaration order, declared ;; through jolt.host/register-enum! (values, valueOf, ordinal, compareTo, ;; EnumSet, the #object print form). An older jolt without register-enum! diff --git a/src/jolt/time/locale_data.clj b/src/jolt/time/locale_data.clj index bfdc883..a22f65e 100644 --- a/src/jolt/time/locale_data.clj +++ b/src/jolt/time/locale_data.clj @@ -5,7 +5,7 @@ :months-short, :days, :days-short name tables (months January-first, days Monday-first). Ids are BCP-47-ish \"lang-COUNTRY\"; \"\" is ROOT. Every value is as the reference JVM renders it, including ROOT's wide month/day names, which - CLDR abbreviates ("Mar", "Sat") rather than spelling out.") + CLDR abbreviates (\"Mar\", \"Sat\") rather than spelling out.") (def locales {"" @@ -625,3 +625,220 @@ (defn months [id full?] (get-in locales [(resolve-id id) (if full? :months :months-short)])) (defn days [id full?] (get-in locales [(resolve-id id) (if full? :days :days-short)])) + +;; TextStyle's other forms and ChronoField's names, per locale id, measured from +;; the reference JVM (JDK 21 CLDR): :text holds the NARROW forms and those +;; *_STANDALONE forms that differ from the format ones (months January-first, +;; days Monday-first); :field-names the ChronoField display names that differ +;; from the field's toString. ROOT's are core's own. +(def display-names +{"de" + {:text + {:months-narrow ["J" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["M" "D" "M" "D" "F" "S" "S"], + :months-short-standalone + ["Jan" + "Feb" + "Mär" + "Apr" + "Mai" + "Jun" + "Jul" + "Aug" + "Sep" + "Okt" + "Nov" + "Dez"], + :days-short-standalone ["Mo" "Di" "Mi" "Do" "Fr" "Sa" "So"]}, + :field-names + {"AMPM_OF_DAY" "Tageshälfte", + "DAY_OF_WEEK" "Wochentag", + "ERA" "Epoche", + "HOUR_OF_DAY" "Stunde", + "MINUTE_OF_HOUR" "Minute", + "MONTH_OF_YEAR" "Monat", + "SECOND_OF_MINUTE" "Sekunde", + "YEAR" "Jahr"}}, + "en" + {:text + {:months-narrow ["J" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["M" "T" "W" "T" "F" "S" "S"]}, + :field-names + {"AMPM_OF_DAY" "AM/PM", + "DAY_OF_WEEK" "day of the week", + "ERA" "era", + "HOUR_OF_DAY" "hour", + "MINUTE_OF_HOUR" "minute", + "MONTH_OF_YEAR" "month", + "SECOND_OF_MINUTE" "second", + "YEAR" "year"}}, + "en-GB" + {:text + {:months-narrow ["J" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["M" "T" "W" "T" "F" "S" "S"]}, + :field-names + {"AMPM_OF_DAY" "am/pm", + "DAY_OF_WEEK" "day of the week", + "ERA" "era", + "HOUR_OF_DAY" "hour", + "MINUTE_OF_HOUR" "minute", + "MONTH_OF_YEAR" "month", + "SECOND_OF_MINUTE" "second", + "YEAR" "year"}}, + "en-US" + {:text + {:months-narrow ["J" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["M" "T" "W" "T" "F" "S" "S"]}, + :field-names + {"AMPM_OF_DAY" "AM/PM", + "DAY_OF_WEEK" "day of the week", + "ERA" "era", + "HOUR_OF_DAY" "hour", + "MINUTE_OF_HOUR" "minute", + "MONTH_OF_YEAR" "month", + "SECOND_OF_MINUTE" "second", + "YEAR" "year"}}, + "es" + {:text + {:months-narrow ["E" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["L" "M" "X" "J" "V" "S" "D"]}, + :field-names + {"AMPM_OF_DAY" "a. m./p. m.", + "DAY_OF_WEEK" "día de la semana", + "ERA" "era", + "HOUR_OF_DAY" "hora", + "MINUTE_OF_HOUR" "minuto", + "MONTH_OF_YEAR" "mes", + "SECOND_OF_MINUTE" "segundo", + "YEAR" "año"}}, + "fr" + {:text + {:months-narrow ["J" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["L" "M" "M" "J" "V" "S" "D"]}, + :field-names + {"AMPM_OF_DAY" "cadran", + "DAY_OF_WEEK" "jour de la semaine", + "ERA" "ère", + "HOUR_OF_DAY" "heure", + "MINUTE_OF_HOUR" "minute", + "MONTH_OF_YEAR" "mois", + "SECOND_OF_MINUTE" "seconde", + "YEAR" "année"}}, + "it" + {:text + {:months-narrow ["G" "F" "M" "A" "M" "G" "L" "A" "S" "O" "N" "D"], + :days-narrow ["L" "M" "M" "G" "V" "S" "D"]}, + :field-names + {"AMPM_OF_DAY" "AM/PM", + "DAY_OF_WEEK" "giorno della settimana", + "ERA" "era", + "HOUR_OF_DAY" "ora", + "MINUTE_OF_HOUR" "minuto", + "MONTH_OF_YEAR" "mese", + "SECOND_OF_MINUTE" "secondo", + "YEAR" "anno"}}, + "ja" + {:text + {:months-narrow ["1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12"], + :days-narrow ["月" "火" "水" "木" "金" "土" "日"]}, + :field-names + {"AMPM_OF_DAY" "午前/午後", + "DAY_OF_WEEK" "曜日", + "ERA" "時代", + "HOUR_OF_DAY" "時", + "MINUTE_OF_HOUR" "分", + "MONTH_OF_YEAR" "月", + "SECOND_OF_MINUTE" "秒", + "YEAR" "年"}}, + "ko" + {:text + {:months-narrow + ["1월" "2월" "3월" "4월" "5월" "6월" "7월" "8월" "9월" "10월" "11월" "12월"], + :days-narrow ["월" "화" "수" "목" "금" "토" "일"]}, + :field-names + {"AMPM_OF_DAY" "오전/오후", + "DAY_OF_WEEK" "요일", + "ERA" "연호", + "HOUR_OF_DAY" "시", + "MINUTE_OF_HOUR" "분", + "MONTH_OF_YEAR" "월", + "SECOND_OF_MINUTE" "초", + "YEAR" "년"}}, + "nl" + {:text + {:months-narrow ["J" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["M" "D" "W" "D" "V" "Z" "Z"]}, + :field-names + {"AMPM_OF_DAY" "a.m./p.m.", + "DAY_OF_WEEK" "dag van de week", + "ERA" "tijdperk", + "HOUR_OF_DAY" "uur", + "MINUTE_OF_HOUR" "minuut", + "MONTH_OF_YEAR" "maand", + "SECOND_OF_MINUTE" "seconde", + "YEAR" "jaar"}}, + "pt" + {:text + {:months-narrow ["J" "F" "M" "A" "M" "J" "J" "A" "S" "O" "N" "D"], + :days-narrow ["S" "T" "Q" "Q" "S" "S" "D"]}, + :field-names + {"AMPM_OF_DAY" "AM/PM", + "DAY_OF_WEEK" "dia da semana", + "ERA" "era", + "HOUR_OF_DAY" "hora", + "MINUTE_OF_HOUR" "minuto", + "MONTH_OF_YEAR" "mês", + "SECOND_OF_MINUTE" "segundo", + "YEAR" "ano"}}, + "ru" + {:text + {:months-narrow ["Я" "Ф" "М" "А" "М" "И" "И" "А" "С" "О" "Н" "Д"], + :days-narrow ["П" "В" "С" "Ч" "П" "С" "В"], + :months-standalone + ["январь" + "февраль" + "март" + "апрель" + "май" + "июнь" + "июль" + "август" + "сентябрь" + "октябрь" + "ноябрь" + "декабрь"], + :months-short-standalone + ["янв." + "февр." + "март" + "апр." + "май" + "июнь" + "июль" + "авг." + "сент." + "окт." + "нояб." + "дек."]}, + :field-names + {"AMPM_OF_DAY" "AM/PM", + "DAY_OF_WEEK" "день недели", + "ERA" "эра", + "HOUR_OF_DAY" "час", + "MINUTE_OF_HOUR" "минута", + "MONTH_OF_YEAR" "месяц", + "SECOND_OF_MINUTE" "секунда", + "YEAR" "год"}}, + "zh" + {:text + {:months-narrow ["1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12"], + :days-narrow ["一" "二" "三" "四" "五" "六" "日"]}, + :field-names + {"AMPM_OF_DAY" "上午/下午", + "DAY_OF_WEEK" "工作日", + "ERA" "纪元", + "HOUR_OF_DAY" "小时", + "MINUTE_OF_HOUR" "分钟", + "MONTH_OF_YEAR" "月", + "SECOND_OF_MINUTE" "秒", + "YEAR" "年"}}}) diff --git a/src/jolt/time/number_data.clj b/src/jolt/time/number_data.clj index 2ef2521..0536526 100644 --- a/src/jolt/time/number_data.clj +++ b/src/jolt/time/number_data.clj @@ -14,7 +14,7 @@ (def number-symbols {"" {:decimal-sep "." :grouping-sep ","} "de" {:decimal-sep "," :grouping-sep "."} - "de-CH" {:decimal-sep "." :grouping-sep "'"} + "de-CH" {:decimal-sep "." :grouping-sep "’"} "de-DE" {:decimal-sep "," :grouping-sep "."} "en" {:decimal-sep "." :grouping-sep ","} "en-GB" {:decimal-sep "." :grouping-sep ","} @@ -36,12 +36,55 @@ "ru" {:decimal-sep "," :grouping-sep " "} "ru-RU" {:decimal-sep "," :grouping-sep " "} "zh" {:decimal-sep "." :grouping-sep ","} - "zh-CN" {:decimal-sep "." :grouping-sep ","}}) + "zh-CN" {:decimal-sep "." :grouping-sep ","} + ;; a country whose symbols differ from its language's, measured from JDK 21: + ;; core falls a missed lang-COUNTRY back to the language (the :parent rule) + "de-AT" {:decimal-sep ",", :grouping-sep " "} + "de-LI" {:decimal-sep ".", :grouping-sep "’"} + "en-AT" {:decimal-sep ",", :grouping-sep "."} + "en-BE" {:decimal-sep ",", :grouping-sep "."} + "en-CH" {:decimal-sep ".", :grouping-sep "’"} + "en-DE" {:decimal-sep ",", :grouping-sep "."} + "en-DK" {:decimal-sep ",", :grouping-sep "."} + "en-FI" {:decimal-sep ",", :grouping-sep " "} + "en-NL" {:decimal-sep ",", :grouping-sep "."} + "en-SE" {:decimal-sep ",", :grouping-sep " "} + "en-SI" {:decimal-sep ",", :grouping-sep "."} + "en-ZA" {:decimal-sep ",", :grouping-sep " "} + "es-BR" {:decimal-sep ".", :grouping-sep ","} + "es-BZ" {:decimal-sep ".", :grouping-sep ","} + "es-CR" {:decimal-sep ",", :grouping-sep " "} + "es-CU" {:decimal-sep ".", :grouping-sep ","} + "es-DO" {:decimal-sep ".", :grouping-sep ","} + "es-GT" {:decimal-sep ".", :grouping-sep ","} + "es-HN" {:decimal-sep ".", :grouping-sep ","} + "es-MX" {:decimal-sep ".", :grouping-sep ","} + "es-NI" {:decimal-sep ".", :grouping-sep ","} + "es-PA" {:decimal-sep ".", :grouping-sep ","} + "es-PE" {:decimal-sep ".", :grouping-sep ","} + "es-PR" {:decimal-sep ".", :grouping-sep ","} + "es-SV" {:decimal-sep ".", :grouping-sep ","} + "es-US" {:decimal-sep ".", :grouping-sep ","} + "fr-CA" {:decimal-sep ",", :grouping-sep " "} + "fr-LU" {:decimal-sep ",", :grouping-sep "."} + "fr-MA" {:decimal-sep ",", :grouping-sep "."} + "it-CH" {:decimal-sep ".", :grouping-sep "’"} + "pt-AO" {:decimal-sep ",", :grouping-sep " "} + "pt-CH" {:decimal-sep ",", :grouping-sep " "} + "pt-CV" {:decimal-sep ",", :grouping-sep " "} + "pt-GQ" {:decimal-sep ",", :grouping-sep " "} + "pt-GW" {:decimal-sep ",", :grouping-sep " "} + "pt-LU" {:decimal-sep ",", :grouping-sep " "} + "pt-MO" {:decimal-sep ",", :grouping-sep " "} + "pt-MZ" {:decimal-sep ",", :grouping-sep " "} + "pt-PT" {:decimal-sep ",", :grouping-sep " "} + "pt-ST" {:decimal-sep ",", :grouping-sep " "} + "pt-TL" {:decimal-sep ",", :grouping-sep " "}}) (def currency {"" {:symbol "¤", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} "de" {:symbol "¤", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} - "de-CH" {:symbol "CHF", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep "'"} + "de-CH" {:symbol "CHF", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep "’"} "de-DE" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} "en" {:symbol "¤", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} "en-GB" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} @@ -63,7 +106,216 @@ "ru" {:symbol "¤", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} "ru-RU" {:symbol "₽", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} "zh" {:symbol "¤", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} - "zh-CN" {:symbol "¥", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","}}) + "zh-CN" {:symbol "¥", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + ;; every other country of these languages, measured from JDK 21 the same way + "de-AT" {:symbol "€", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "de-BE" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "de-IT" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "de-LI" {:symbol "CHF", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep "’"} + "de-LU" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "en-AE" {:symbol "AED", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-AG" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-AI" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-AS" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-AT" {:symbol "€", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "en-AU" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-BB" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-BE" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "en-BI" {:symbol "FBu", :symbol-sep "", :symbol-first? true, :frac-digits 0, :decimal-sep ".", :grouping-sep ","} + "en-BM" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-BS" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-BW" {:symbol "P", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-BZ" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-CA" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-CC" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-CH" {:symbol "CHF", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep "’"} + "en-CK" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-CM" {:symbol "FCFA", :symbol-sep "", :symbol-first? true, :frac-digits 0, :decimal-sep ".", :grouping-sep ","} + "en-CX" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-CY" {:symbol "€", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-DE" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "en-DG" {:symbol "¤", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-DK" {:symbol "kr.", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "en-DM" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-ER" {:symbol "Nfk", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-FI" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "en-FJ" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-FK" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-FM" {:symbol "US$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-GD" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-GG" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-GH" {:symbol "GH₵", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-GI" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-GM" {:symbol "D", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-GU" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-GY" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-HK" {:symbol "HK$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-IE" {:symbol "€", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-IL" {:symbol "₪", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-IM" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-IN" {:symbol "₹", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-IO" {:symbol "US$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-JE" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-JM" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-KE" {:symbol "Ksh", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-KI" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-KN" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-KY" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-LC" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-LR" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-LS" {:symbol "LSL", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MG" {:symbol "Ar", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MH" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MO" {:symbol "MOP$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MP" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MS" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MT" {:symbol "€", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MU" {:symbol "Rs", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MV" {:symbol "Rf", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MW" {:symbol "MK", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-MY" {:symbol "RM", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-NA" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-NF" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-NG" {:symbol "₦", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-NL" {:symbol "€", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "en-NR" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-NU" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-NZ" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-PG" {:symbol "K", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-PH" {:symbol "₱", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-PK" {:symbol "Rs", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-PN" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-PR" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-PW" {:symbol "US$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-RW" {:symbol "RF", :symbol-sep "", :symbol-first? true, :frac-digits 0, :decimal-sep ".", :grouping-sep ","} + "en-SB" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SC" {:symbol "SR", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SD" {:symbol "SDG", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SE" {:symbol "kr", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "en-SG" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SH" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SI" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "en-SL" {:symbol "Le", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SS" {:symbol "£", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SX" {:symbol "XCG", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-SZ" {:symbol "E", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-TC" {:symbol "US$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-TK" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-TO" {:symbol "T$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-TT" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-TV" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-TZ" {:symbol "TSh", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-UG" {:symbol "USh", :symbol-sep "", :symbol-first? true, :frac-digits 0, :decimal-sep ".", :grouping-sep ","} + "en-UM" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-VC" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-VG" {:symbol "US$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-VI" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-VU" {:symbol "VT", :symbol-sep "", :symbol-first? true, :frac-digits 0, :decimal-sep ".", :grouping-sep ","} + "en-WS" {:symbol "WS$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-ZA" {:symbol "R", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "en-ZM" {:symbol "K", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "en-ZW" {:symbol "ZWG", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-AR" {:symbol "$", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-BO" {:symbol "Bs", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-BR" {:symbol "R$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-BZ" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-CL" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 0, :decimal-sep ",", :grouping-sep "."} + "es-CO" {:symbol "$", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-CR" {:symbol "₡", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "es-CU" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-DO" {:symbol "RD$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-EA" {:symbol "¤", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-EC" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-GQ" {:symbol "FCFA", :symbol-sep "", :symbol-first? true, :frac-digits 0, :decimal-sep ",", :grouping-sep "."} + "es-GT" {:symbol "Q", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-HN" {:symbol "L", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-IC" {:symbol "¤", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-MX" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-NI" {:symbol "C$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-PA" {:symbol "B/.", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-PE" {:symbol "S/", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-PH" {:symbol "₱", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-PR" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-PY" {:symbol "Gs.", :symbol-sep " ", :symbol-first? true, :frac-digits 0, :decimal-sep ",", :grouping-sep "."} + "es-SV" {:symbol "C", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-US" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "es-UY" {:symbol "$", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "es-VE" {:symbol "Bs.S", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "fr-BE" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-BF" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-BI" {:symbol "FBu", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-BJ" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-BL" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-CA" {:symbol "$", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-CD" {:symbol "FC", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-CF" {:symbol "FCFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-CG" {:symbol "FCFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-CH" {:symbol "CHF", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ".", :grouping-sep " "} + "fr-CI" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-CM" {:symbol "FCFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-DJ" {:symbol "Fdj", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-DZ" {:symbol "DA", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-GA" {:symbol "FCFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-GF" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-GN" {:symbol "FG", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-GP" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-GQ" {:symbol "FCFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-HT" {:symbol "G", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-KM" {:symbol "CF", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-LU" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "fr-MA" {:symbol "MAD", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "fr-MC" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-MF" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-MG" {:symbol "Ar", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-ML" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-MQ" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-MR" {:symbol "UM", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-MU" {:symbol "Rs", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-NC" {:symbol "FCFP", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-NE" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-PF" {:symbol "FCFP", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-PM" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-RE" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-RW" {:symbol "RF", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-SC" {:symbol "SR", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-SN" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-SY" {:symbol "LS", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "fr-TD" {:symbol "FCFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-TG" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-TN" {:symbol "DT", :symbol-sep " ", :symbol-first? false, :frac-digits 3, :decimal-sep ",", :grouping-sep " "} + "fr-VU" {:symbol "VT", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-WF" {:symbol "FCFP", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "fr-YT" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "it-CH" {:symbol "SFr.", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep "’"} + "it-SM" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "it-VA" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "ko-KP" {:symbol "KPW", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "nl-AW" {:symbol "Afl.", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "nl-BE" {:symbol "€", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "nl-BQ" {:symbol "$", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "nl-CW" {:symbol "XCG", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "nl-SR" {:symbol "$", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "nl-SX" {:symbol "XCG", :symbol-sep " ", :symbol-first? true, :frac-digits 2, :decimal-sep ",", :grouping-sep "."} + "pt-AO" {:symbol "Kz", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-CH" {:symbol "CHF", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-CV" {:symbol "​", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-GQ" {:symbol "FCFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "pt-GW" {:symbol "F CFA", :symbol-sep " ", :symbol-first? false, :frac-digits 0, :decimal-sep ",", :grouping-sep " "} + "pt-LU" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-MO" {:symbol "MOP$", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-MZ" {:symbol "MTn", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-PT" {:symbol "€", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-ST" {:symbol "Db", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "pt-TL" {:symbol "US$", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "ru-BY" {:symbol "Br", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "ru-KG" {:symbol "сом", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "ru-KZ" {:symbol "₸", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "ru-MD" {:symbol "L", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "ru-UA" {:symbol "₴", :symbol-sep " ", :symbol-first? false, :frac-digits 2, :decimal-sep ",", :grouping-sep " "} + "zh-HK" {:symbol "HK$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "zh-MO" {:symbol "MOP$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "zh-SG" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","} + "zh-TW" {:symbol "$", :symbol-sep "", :symbol-first? true, :frac-digits 2, :decimal-sep ".", :grouping-sep ","}}) (defn symbols-for "Number symbols for a locale id, narrowed the same way the pattern tables are." diff --git a/test/jolt/time/fmt_test.clj b/test/jolt/time/fmt_test.clj index 709d860..f38e38c 100644 --- a/test/jolt/time/fmt_test.clj +++ b/test/jolt/time/fmt_test.clj @@ -213,3 +213,82 @@ ;; still drives the localized formatters (is (= "Mar 5, 2020" (.format (DateTimeFormatter/ofLocalizedDate (FormatStyle/valueOf "MEDIUM")) (LocalDate/of 2020 3 5)))))) + +;; TextStyle is an enum, and drives Month / DayOfWeek / ChronoField +;; .getDisplayName through the names this library registers. The display names +;; need a jolt whose java.time reads them (the :field-names point); an older one +;; still gets the TextStyle constants. +(defn- field-names-point? [] + (try (jolt.host/extension-value :field-names "") true (catch Exception _ false))) +(deftest text-style-enum + (is (= "SHORT" (str java.time.format.TextStyle/SHORT))) + (when (resolve 'jolt.host/register-enum!) + (is (= ["FULL" "FULL_STANDALONE" "SHORT" "SHORT_STANDALONE" "NARROW" "NARROW_STANDALONE"] + (mapv #(.name %) (java.time.format.TextStyle/values)))) + (is (= [false true false true false true] (mapv #(.isStandalone %) (java.time.format.TextStyle/values)))) + (is (= java.time.format.TextStyle/SHORT_STANDALONE (.asStandalone java.time.format.TextStyle/SHORT))) + (is (= java.time.format.TextStyle/NARROW (.asNormal java.time.format.TextStyle/NARROW_STANDALONE))) + (is (identical? java.time.format.TextStyle/FULL (java.time.format.TextStyle/valueOf "FULL"))) + (is (.isEnum java.time.format.TextStyle)))) +(deftest locale-prints-as-the-jdk + (is (= "en_US" (str java.util.Locale/US))) + (is (= "" (str java.util.Locale/ROOT))) + (is (= "en-US" (.toLanguageTag java.util.Locale/US))) + (is (= "und" (.toLanguageTag java.util.Locale/ROOT))) + (is (= ["en" "US"] [(.getLanguage java.util.Locale/US) (.getCountry java.util.Locale/US)])) + ;; locale data still resolves for a Locale printed with an underscore + (is (= "décembre" (.format (java.text.SimpleDateFormat. "MMMM" java.util.Locale/FRENCH) + (java.util.Date. (* 1000 60 60 24 340)))))) +(deftest display-names-by-style-and-locale + (when (field-names-point?) + (let [m java.time.Month/MARCH d java.time.DayOfWeek/SATURDAY + styles [java.time.format.TextStyle/FULL java.time.format.TextStyle/SHORT java.time.format.TextStyle/NARROW + java.time.format.TextStyle/FULL_STANDALONE java.time.format.TextStyle/SHORT_STANDALONE] + month (fn [l] (mapv #(.getDisplayName m % l) styles)) + dow (fn [l] (mapv #(.getDisplayName d % l) (take 3 styles))) + field (fn [f l] (.getDisplayName f l))] + ;; every value as JDK 21 renders it + (is (= ["Mar" "Mar" "3" "Mar" "Mar"] (month java.util.Locale/ROOT))) + (is (= ["March" "Mar" "M" "March" "Mar"] (month java.util.Locale/US))) + (is (= ["mars" "mars" "M" "mars" "mars"] (month java.util.Locale/FRENCH))) + (is (= ["März" "März" "M" "März" "Mär"] (month java.util.Locale/GERMAN))) + (is (= ["3月" "3月" "3" "3月" "3月"] (month java.util.Locale/JAPANESE))) + (is (= ["Saturday" "Sat" "S"] (dow java.util.Locale/US))) + (is (= ["samedi" "sam." "S"] (dow java.util.Locale/FRENCH))) + (is (= ["土曜日" "土" "土"] (dow java.util.Locale/JAPANESE))) + (is (= ["zaterdag" "za" "Z"] (dow (java.util.Locale. "nl")))) + (is (= "Day of the Week" (field java.time.temporal.ChronoField/DAY_OF_WEEK java.util.Locale/ROOT))) + (is (= "day of the week" (field java.time.temporal.ChronoField/DAY_OF_WEEK java.util.Locale/US))) + (is (= "jour de la semaine" (field java.time.temporal.ChronoField/DAY_OF_WEEK java.util.Locale/FRENCH))) + (is (= "Tageshälfte" (field java.time.temporal.ChronoField/AMPM_OF_DAY java.util.Locale/GERMAN))) + (is (= "year" (field java.time.temporal.ChronoField/YEAR java.util.Locale/US))) + (is (= "Year" (field java.time.temporal.ChronoField/YEAR java.util.Locale/ROOT))) + (is (= "EpochDay" (field java.time.temporal.ChronoField/EPOCH_DAY java.util.Locale/FRENCH)))))) + +;; A Locale is language, script, country and variant, printed and tagged as the +;; JDK does (every value as JDK 21 renders it). +(deftest locale-parts + (let [row (fn [l] [(str l) (.toLanguageTag l) (.getLanguage l) (.getCountry l) (.getVariant l) (.getScript l)])] + (is (= ["zh_TW_#Hant" "zh-Hant-TW" "zh" "TW" "" "Hant"] (row (java.util.Locale/forLanguageTag "zh-Hant-TW")))) + (is (= ["" "und" "" "" "" ""] (row (java.util.Locale/forLanguageTag "und")))) + (is (= ["en_US_POSIX" "en-US-POSIX" "en" "US" "POSIX" ""] (row (java.util.Locale. "en" "US" "POSIX")))) + (is (= ["de_CH_x" "de-CH-x-lvariant-x" "de" "CH" "x" ""] (row (java.util.Locale. "de" "CH" "x")))) + (is (= ["sr__#Latn" "sr-Latn" "sr" "" "" "Latn"] (row (java.util.Locale/forLanguageTag "sr-Latn")))) + (is (= ["es_419" "es-419" "es" "419" "" ""] (row (java.util.Locale/forLanguageTag "es-419")))) + (is (= ["en_US" "en-US" "en" "US" "" ""] (row (java.util.Locale. "EN" "us")))) + (is (= ["fr_CA" "fr-CA" "fr" "CA" "" ""] (row (java.util.Locale/of "fr" "CA")))) + (is (= (java.util.Locale. "en" "US" "POSIX") (java.util.Locale/forLanguageTag "en-US-POSIX"))) + (is (not= (java.util.Locale. "en" "US" "POSIX") java.util.Locale/US)))) + +;; A country falls back to its language for names, and carries its own number +;; and currency symbols where the JDK's differ (JDK 21). +(deftest country-locales + (when (field-names-point?) + (is (= "März" (.getDisplayName java.time.Month/MARCH java.time.format.TextStyle/FULL java.util.Locale/GERMANY))) + (is (= "Jahr" (.getDisplayName java.time.temporal.ChronoField/YEAR (java.util.Locale. "de" "AT")))) + (is (= "1\u00a0234\u00a0567,50" (String/format (java.util.Locale. "de" "AT") "%,.2f" (to-array [1234567.5])))) + (is (= "1’234’567.50" (String/format (java.util.Locale. "de" "CH") "%,.2f" (to-array [1234567.5])))) + (is (= "1,234,567.50" (String/format (java.util.Locale. "es" "MX") "%,.2f" (to-array [1234567.5])))) + (is (= "€\u00a01.234,50" (.format (java.text.NumberFormat/getCurrencyInstance (java.util.Locale. "de" "AT")) 1234.5))) + (is (= "$1,234.50" (.format (java.text.NumberFormat/getCurrencyInstance (java.util.Locale. "es" "MX")) 1234.5))) + (is (= "CHF\u00a01’234.50" (.format (java.text.NumberFormat/getCurrencyInstance (java.util.Locale. "de" "CH")) 1234.5)))))