branch: elpa/datetime commit 6c62a5ed7fe3bc1e244a5673bc7f7dd33b6a84fd Author: Paul Pogonyshev <pogonys...@gmail.com> Commit: Paul Pogonyshev <pogonys...@gmail.com>
Add support to all the various era specifications (short, full, narrow) in Java patterns. --- datetime.el | 70 +++++++++++++++++++++++++++++++++------------------ dev/HarvestData.java | 26 +++++++++++-------- locale-data.extmap | Bin 349522 -> 367805 bytes test/base.el | 14 ++++++++--- test/format.el | 15 +++++++++++ 5 files changed, 85 insertions(+), 40 deletions(-) diff --git a/datetime.el b/datetime.el index 146ffeee0f..71bb8028ad 100644 --- a/datetime.el +++ b/datetime.el @@ -68,7 +68,7 @@ ;; In all cases these should be seen as internals and can be changed ;; in a future library versions without prior notice. ;; -;; era (full | abbreviated) --- AD or BC +;; era (short | full | narrow) --- AD or BC ;; ;; year (add-century-when-parsing | always-two-digits | NUMBER) ;; - add-century-when-parsing: format as-is, but when parsing add @@ -138,7 +138,9 @@ ;; - for locale XX-YY value for any property defaults to that for ;; locale XX; ;; - `:decimal-separator' defaults to dot; -;; - `:eras' and `:am-pm' default to English version; +;; - both `:eras-full' and `:eras-narrow' fall back to +;; `:eras-short'; +;; - `:eras-short' and `:am-pm' default to English version; ;; - month/dayweek standalone abbreviations or names default to ;; the corresponding context-aware property; ;; - for day period strings, both `:full' and `:narrow' variants @@ -353,11 +355,11 @@ form: (setq scan (1+ scan) num-repetitions (1+ num-repetitions))) (push (pcase character - ((or ?G ?a) - (cons (pcase character - (?G 'era) - (?a 'am-pm)) - (if (>= num-repetitions 4) 'full 'abbreviated))) + (?G (cons 'era (pcase num-repetitions + ((or 1 2 3) 'short) + (4 'full) + (5 'narrow) + (_ (error "Pattern character `%c' must come in 1-5 repetitions" character))))) ((or ?y ?Y) (cons (if (= character ?y) 'year 'year-for-week) (pcase num-repetitions @@ -381,6 +383,9 @@ form: (?d (cons 'day-in-month num-repetitions)) (?F (cons 'weekday-in-month num-repetitions)) (?u (cons 'weekday num-repetitions)) + (?a (cons 'am-pm (pcase num-repetitions + (1 'abbreviated) + (_ (error "Pattern character `%c' must come in exactly 1 repetition" character))))) (?H (cons 'hour-0-23 num-repetitions)) (?k (cons 'hour-1-24 num-repetitions)) (?K (cons 'hour-am-pm-0-11 num-repetitions)) @@ -447,7 +452,10 @@ form: (let* ((type (car part)) (details (cdr part)) (string (pcase type - (`era "G") + (`era (pcase details + (`short "G") + (`full "GGGG") + (`narrow "GGGGG"))) ((or `year `year-for-week) (let ((base (if (eq type 'year) ?y ?Y))) (pcase details @@ -688,7 +696,7 @@ to this function. (`era (setq need-year t) (push "%s" format-parts) - (push `(aref ,(datetime-locale-field locale :eras) (if (> year 0) 1 0)) format-arguments)) + (push `(aref ,(datetime-locale-field locale (datetime--era-field details)) (if (> year 0) 1 0)) format-arguments)) (`year (setq need-year t) (push (pcase details @@ -1130,9 +1138,10 @@ unless specified otherwise. (let* ((type (car part)) (details (cdr part))) (cons (pcase type - (`era (when (or validating (null era-part-indices)) - (push part-index era-part-indices)) - (datetime-locale-field locale :eras)) + (`era (let ((field (datetime--era-field details))) + (when (or validating (null era-part-indices)) + (push (cons part-index field) era-part-indices)) + (datetime-locale-field locale field))) (`year (when (or validating (null year-part-indices)) (push (cons part-index details) year-part-indices)) @@ -1307,7 +1316,7 @@ unless specified otherwise. (or (plist-get 'year defaults) 1970))) (era-correction (when (and year-computation era-part-indices) (datetime--parser-computation pattern "era" validating nil nil - (era-part-indices (datetime--parser-era-correction locale :eras downcased) t)))) + (era-part-indices (datetime--parser-era-correction locale nil downcased) t)))) (month-computation (or (datetime--parser-computation pattern "month" validating 0 11 (month-number-part-indices (datetime--parser-int-computation t)) (month-name-part-indices (datetime--parser-string-index-computation locale nil downcased) t)) @@ -1400,7 +1409,7 @@ unless specified otherwise. (_ (datetime--parser-int-computation (car argument))))) (defun datetime--parser-era-correction (argument locale field downcased) - (datetime--parser-string-if-computation argument locale field downcased `(setq year (- 1 year)) nil)) + (datetime--parser-string-if-computation argument locale (or field (cdr argument)) downcased `(setq year (- 1 year)) nil)) (defun datetime--parser-hour-1-24-computation (argument) `(let ((hour-1-24 ,(datetime--parser-int-computation argument))) @@ -1431,7 +1440,7 @@ unless specified otherwise. (defun datetime--parser-string-if-computation (argument locale field downcased if-first-form if-second-form) (let ((strings (datetime-locale-field locale field))) (unless (= (length strings) 2) - (error "Must be called only for two-string fields")) + (error "Must be called only for two-string fields, called for `%S' instead" strings)) `(if (string= (match-string ,(if (consp argument) (car argument) argument) string) ,(if downcased (downcase (aref strings 0)) (aref strings 0))) ,if-first-form @@ -1575,7 +1584,7 @@ specified otherwise. (details (cdr part)) (regexp (pcase type (`era - (datetime-locale-field locale :eras)) + (datetime-locale-field locale (datetime--era-field details))) ((or `year `year-for-week) (cond ((and (plist-get options :only-4-digit-years) (eq details 4)) (rx (= 4 (any "0-9")))) @@ -1897,17 +1906,20 @@ separated by a space, for quite a few locales it is different." (or (plist-get locale-data field) ;; See `datetime--locale-extmap' for description of fallbacks. (pcase field - (:month-standalone-abbr (plist-get locale-data :month-context-abbr)) - (:month-standalone-names (plist-get locale-data :month-context-names)) - (:weekday-standalone-abbr (plist-get locale-data :weekday-context-abbr)) - (:weekday-standalone-names (plist-get locale-data :weekday-context-names))))) + ((or :eras-full :eras-narrow) (plist-get locale-data :eras-short)) + (:month-standalone-abbr (plist-get locale-data :month-context-abbr)) + (:month-standalone-names (plist-get locale-data :month-context-names)) + (:weekday-standalone-abbr (plist-get locale-data :weekday-context-abbr)) + (:weekday-standalone-names (plist-get locale-data :weekday-context-names))))) (defun datetime-locale-field (locale field) "Get a FIELD of data for the LOCALE. Supported fields: :decimal-separator - :eras + :eras-short (also old alias :eras) + :eras-full + :eras-narrow :month-context-abbr :month-context-names :weekday-context-abbr @@ -1920,14 +1932,22 @@ Supported fields: ;; Additionally `:day-periods', `:date-patterns', `:time-patterns' and ;; `:date-time-pattern-rule' are supported for internal use. (let ((data (extmap-get datetime--locale-extmap locale t))) + (pcase field + (:eras (setf field :eras-short))) (or (datetime--do-get-locale-field data field) (let ((parent (plist-get data :parent))) (when parent (datetime--do-get-locale-field (extmap-get datetime--locale-extmap parent) field))) (pcase field - (:decimal-separator ?.) - (:eras datetime--english-eras) - (:am-pm datetime--english-am-pm))))) + (:decimal-separator ?.) + ((or :eras-short :eras-full :eras-narrow) datetime--english-eras) + (:am-pm datetime--english-am-pm))))) + +(defun datetime--era-field (details) + (pcase-exhaustive details + (`short :eras-short) + (`full :eras-full) + (`narrow :eras-narrow))) (defun datetime-locale-timezone-name (locale timezone dst &optional full) "Get name of TIMEZONE in given LOCALE. @@ -1966,7 +1986,7 @@ create based on locales `datetime' knows about. Note that this database doesn't include timezone names. See `datetime-timezone-name-database-version'." - 6) + 7) (defun datetime-timezone-database-version () "Return timezone database version, a simple integer. diff --git a/dev/HarvestData.java b/dev/HarvestData.java index cb242de90f..1b462edc0d 100644 --- a/dev/HarvestData.java +++ b/dev/HarvestData.java @@ -75,16 +75,18 @@ public class HarvestData data.put (locale, map); map.put (":decimal-separator", String.format ("?%c", DecimalStyle.of (locale).getDecimalSeparator ())); - map.put (":eras", toLispVector (getNames (locale, ChronoField.ERA, "G", 0, 1))); - map.put (":month-context-abbr", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "MMM", 1, 12))); - map.put (":month-context-names", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "MMMM", 1, 12))); - map.put (":weekday-context-abbr", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "EEE", 1, 7))); - map.put (":weekday-context-names", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "EEEE", 1, 7))); - map.put (":month-standalone-abbr", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "LLL", 1, 12))); - map.put (":month-standalone-names", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "LLLL", 1, 12))); - map.put (":weekday-standalone-abbr", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "ccc", 1, 7))); - map.put (":weekday-standalone-names", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "cccc", 1, 7))); - map.put (":am-pm", toLispVector (getNames (locale, ChronoField.AMPM_OF_DAY, "a", 0, 1))); + map.put (":eras-short", toLispVector (getNames (locale, ChronoField.ERA, "G", 0, 1))); + map.put (":eras-full", toLispVector (getNames (locale, ChronoField.ERA, "GGGG", 0, 1))); + map.put (":eras-narrow", toLispVector (getNames (locale, ChronoField.ERA, "GGGGG", 0, 1))); + map.put (":month-context-abbr", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "MMM", 1, 12))); + map.put (":month-context-names", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "MMMM", 1, 12))); + map.put (":weekday-context-abbr", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "EEE", 1, 7))); + map.put (":weekday-context-names", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "EEEE", 1, 7))); + map.put (":month-standalone-abbr", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "LLL", 1, 12))); + map.put (":month-standalone-names", toLispVector (getNames (locale, ChronoField.MONTH_OF_YEAR, "LLLL", 1, 12))); + map.put (":weekday-standalone-abbr", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "ccc", 1, 7))); + map.put (":weekday-standalone-names", toLispVector (getNames (locale, ChronoField.DAY_OF_WEEK, "cccc", 1, 7))); + map.put (":am-pm", toLispVector (getNames (locale, ChronoField.AMPM_OF_DAY, "a", 0, 1))); map.put (":day-periods", findDayPeriodData (locale)); Map <String, String> date_patterns = toPatternPlist ((style) -> DateTimeFormatterBuilder.getLocalizedDateTimePattern (style, null, chronology, locale)); @@ -239,7 +241,9 @@ public class HarvestData } - private static Map <String, String> LOCALE_FALLBACK_KEYS = Map.of (":month-standalone-abbr", ":month-context-abbr", + private static Map <String, String> LOCALE_FALLBACK_KEYS = Map.of (":eras-full", ":eras-short", + ":eras-narrow", ":eras-short", + ":month-standalone-abbr", ":month-context-abbr", ":month-standalone-names", ":month-context-names", ":weekday-standalone-abbr", ":weekday-context-abbr", ":weekday-standalone-names", ":weekday-context-names"); diff --git a/locale-data.extmap b/locale-data.extmap index 015a56a6f4..973f236528 100644 Binary files a/locale-data.extmap and b/locale-data.extmap differ diff --git a/test/base.el b/test/base.el index ef17b8ca09..202a6d18cc 100644 --- a/test/base.el +++ b/test/base.el @@ -131,7 +131,9 @@ (ert-deftest datetime-locale-database-sanity () (dolist (locale (datetime-list-locales t)) (let ((decimal-separator (datetime-locale-field locale :decimal-separator)) - (eras (datetime-locale-field locale :eras)) + (eras-short (datetime-locale-field locale :eras-short)) + (eras-full (datetime-locale-field locale :eras-full)) + (eras-narrow (datetime-locale-field locale :eras-narrow)) (month-context-abbr (datetime-locale-field locale :month-context-abbr)) (month-context-names (datetime-locale-field locale :month-context-names)) (weekday-context-abbr (datetime-locale-field locale :weekday-context-abbr)) @@ -144,7 +146,9 @@ (ert-info ((format "\ locale = %S decimal-separator = %S -eras = %S +eras-short = %S +eras-full = %S +eras-narrow = %S month-context-abbr = %S month-context-names = %S weekday-context-abbr = %S @@ -154,14 +158,16 @@ month-standalone-names = %S weekday-standalone-abbr = %S weekday-standalone-names = %S am-pm = %S" - locale decimal-separator eras + locale decimal-separator eras-short eras-full eras-narrow month-context-abbr month-context-names weekday-context-abbr weekday-context-names month-standalone-abbr month-standalone-names weekday-standalone-abbr weekday-standalone-names am-pm)) (should (memq decimal-separator '(?. ?, ?٫))) - (dolist (entry `((,eras 2) + (dolist (entry `((,eras-short 2) + (,eras-full 2) + (,eras-narrow 2) (,month-context-abbr 12) (,month-context-names 12) (,weekday-context-abbr 7) diff --git a/test/format.el b/test/format.el index c25a345061..59c608d475 100644 --- a/test/format.el +++ b/test/format.el @@ -171,4 +171,19 @@ (datetime--test-formatter 0))) +(ert-deftest datetime-formatting-java-specifiers () + ;; Loop over all supported Java specifiers and make sure we produce the same results for + ;; them as the Java benchmark. To make it somewhat faster, combine multiple elements + ;; into one pattern where easily possible, + (dolist (entry '(("G GG GGG GGGG GGGGG" era t))) ; Java (as of 17) allows at most five repetitions. + (let ((pattern (nth 0 entry)) + (unit (nth 1 entry)) + (locale-specific (nth 2 entry))) + (dolist (locale (if locale-specific (datetime-list-locales t) '(en))) + (datetime--test-set-up-formatter 'UTC locale pattern + (datetime--test-formatter (pcase unit + (`era '(0 -100000000000)) + (_ (error "Unknown unit `%s'" unit))))))))) + + (provide 'test/format)