[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/datetime 28e31cbe1c 065/147: Add support for timezone name
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/datetime 28e31cbe1c 065/147: Add support for timezone names in timestamps formatted by the library. |
Date: |
Fri, 31 Jan 2025 07:00:03 -0500 (EST) |
branch: elpa/datetime
commit 28e31cbe1ca9dba54309ed31ae9cd250c3180149
Author: Paul Pogonyshev <pogonyshev@gmail.com>
Commit: Paul Pogonyshev <pogonyshev@gmail.com>
Add support for timezone names in timestamps formatted by the library.
---
Eldev | 16 +++-
datetime.el | 141 +++++++++++++++++++++++++-----
dev/HarvestData.java | 207 ++++++++++++++++++++++++++++++++++++++++-----
test/ProcessTimestamp.java | 3 +-
test/base.el | 2 +-
test/format.el | 13 ++-
test/parse.el | 2 +-
timezone-data.extmap | Bin 840168 -> 867924 bytes
timezone-name-data.extmap | Bin 0 -> 3388371 bytes
9 files changed, 330 insertions(+), 54 deletions(-)
diff --git a/Eldev b/Eldev
index f767e3d50c..e2f63a3813 100644
--- a/Eldev
+++ b/Eldev
@@ -40,12 +40,22 @@
:collect ":data"
(datetime--build-extmap target "--timezones"))
-(defun datetime--build-extmap (target &rest command-line)
+(eldev-defbuilder datetime-builder-timezone-name-data-extmap (source target)
+ :short-name "TZ-NAMES"
+ :message target
+ :source-files "/dev/HarvestData.class"
+ :targets "timezone-name-data.extmap"
+ :collect ":data"
+ (datetime--build-extmap target "--timezone-names" :share-values t
:compress-values t))
+
+(defun datetime--build-extmap (target command-line &rest extmap-flags)
(require 'extmap)
- (eldev-call-process "java" `("-cp" "dev" "HarvestData" ,@command-line)
+ (eldev-trace "Collecting data using the Java helper...")
+ (eldev-call-process "java" `("-cp" "dev" "HarvestData" ,@(eldev-listify
command-line))
(unless (= exit-code 0)
(signal 'eldev-error `("`HarvestData' tool exited with error code %d:
%s" ,exit-code ,(buffer-string))))
- (extmap-from-alist target (eldev-read-wholly (buffer-string)
"`HarvestData' output") :overwrite t)))
+ (eldev-trace "Building an extmap out of it...")
+ (apply #'extmap-from-alist target (eldev-read-wholly (buffer-string)
"`HarvestData' output") :overwrite t extmap-flags)))
;; Before testing we need to compile `test/ProcessTimestamp.java'.
diff --git a/datetime.el b/datetime.el
index faf8679a79..5e1f2690ab 100644
--- a/datetime.el
+++ b/datetime.el
@@ -1,13 +1,13 @@
;;; datetime.el --- Parsing, formatting and matching timestamps -*-
lexical-binding: t -*-
-;; Copyright (C) 2016-2019 Paul Pogonyshev
+;; Copyright (C) 2016-2020 Paul Pogonyshev
;; Author: Paul Pogonyshev <pogonyshev@gmail.com>
;; Maintainer: Paul Pogonyshev <pogonyshev@gmail.com>
;; Version: 0.6.6
;; Keywords: lisp, i18n
;; Homepage: https://github.com/doublep/datetime
-;; Package-Requires: ((emacs "24.1") (extmap "1.0"))
+;; Package-Requires: ((emacs "24.4") (extmap "1.1.1"))
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
@@ -55,7 +55,7 @@
;; Internally any date-time pattern is parsed to a list of value pairs
-;; (type . details). Type is a symbol, while details are either nil,
+;; (TYPE . DETAILS). Type is a symbol, while details are either nil,
;; another symbol or a number that represents minimum number of
;; characters in formatted number (left padded with zeros). The only
;; exception is "as-is" part: it is just a string, not a cons cell.
@@ -107,7 +107,12 @@
;; decimal-separator (PREFERRED)
;; either dot or comma;
;;
-;; timezone (?) -- currently not supported further than pattern parsing
+;; timezone (SYMBOL)
+;; abbreviated, full --- timezone name, as reported by Java
+;; (abbreviated is by far more useful, as full is too
+;; verbose for most usecases);
+;; rfc-822, iso-8601 -- currently not supported further than
+;; pattern parsing.
(require 'extmap)
@@ -129,7 +134,7 @@
;; obviously of `java' type.
;;
;; There are many fallbacks involved to reduce size:
-;; - for locale XX-YY value for any property defaults to that of
+;; - 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;
@@ -148,12 +153,33 @@
;; Extracted from Java using `dev/HarvestData.java'.
(defvar datetime--timezone-extmap (extmap-init (expand-file-name
"timezone-data.extmap" datetime--directory) :weak-data t :auto-reload t))
+;; Extracted from Java using `dev/HarvestData.java'.
+;;
+;; Fallbacks:
+;; - for locale XX-YY names defaults to those for locale XX;
+;; - for locale XX names default to those in English locale;
+;; - names themselves can be in several formats (individual values
+;; are always strings):
+;; FULL -- abbreviated name is taken from the English locale,
+;; no special for DST;
+;; (FULL-STD . FULL-DST) -- abbreviated names are taken from the
+;; English locale;
+;; [ABBREVIATED FULL] -- no special for DST;
+;; [ABBREVIATED-STD ABBREVIATED-DST FULL-STD FULL-DST].
+(defvar datetime--timezone-name-extmap (extmap-init (expand-file-name
"timezone-name-data.extmap" datetime--directory) :weak-data t :auto-reload t))
+
(defvar datetime--pattern-parsers '((parsed . (lambda (pattern options)
pattern))
(java . datetime--parse-java-pattern)))
(defvar datetime--pattern-formatters '((parsed . (lambda (parts options)
parts))
(java .
datetime--format-java-pattern)))
+(defvar datetime--last-conversion-was-in-dst nil)
+
+(defvar datetime--locale-timezone-name-lookup-cache nil)
+(defvar datetime--locale-timezone-name-lookup-cache-version 0)
+
+
;; `datetime-list-*' must be defined here, since they are used in
;; `defcustom' forms below.
(defun datetime-list-locales (&optional include-variants)
@@ -288,7 +314,7 @@ form:
(defmacro datetime--extend-as-is-part (parts text)
`(let ((text ,text))
(if (stringp (car ,parts))
- (setcar parts (concat (car ,parts) text))
+ (setf (car parts) (concat (car ,parts) text))
(push text ,parts))))
@@ -358,12 +384,16 @@ form:
(?m (cons 'minute num-repetitions))
(?s (cons 'second num-repetitions))
(?S (cons 'second-fractional num-repetitions))
- (?z (cons 'timezone 'general))
+ (?z (cons 'timezone (if (>= num-repetitions 4)
'full 'abbreviated)))
(?Z (cons 'timezone 'rfc-822))
(?X (cons 'timezone 'iso-8601))
(_
(error "Illegal pattern character `%c'" character)))
parts))
+ ;; FIXME: Optional pattern sections are currently treated the
same as
+ ;; mandatory (brackets are just discarded). May want to
treat them
+ ;; as optional at least for parsing purposes later.
+ ((or (= character ?\[) (= character ?\])))
(t
(if (and (or (= character ?.) (= character ?,))
(plist-get options :any-decimal-separator)
@@ -480,6 +510,9 @@ form:
(defsubst datetime--digits-format (num-repetitions)
(if (> num-repetitions 1) (format "%%0%dd" num-repetitions) "%d"))
+(defsubst datetime--format-escape-string (string)
+ (replace-regexp-in-string "%" "%%" string t t))
+
(defun datetime-float-formatter (type pattern &rest options)
"Return a function that formats date-time expressed as a float.
The returned function accepts single argument---a floating-point
@@ -516,7 +549,7 @@ to this function.
format-arguments)
(dolist (part (datetime--parse-pattern type pattern options))
(if (stringp part)
- (push (replace-regexp-in-string "%" "%%" part t t) format-parts)
+ (push (datetime--format-escape-string part) format-parts)
(let ((type (car part))
(details (cdr part)))
(pcase type
@@ -536,7 +569,7 @@ to this function.
(error "Formatting `%s' is currently not implemented"
type))
format-arguments)
(when (eq details 'always-two-digits)
- (setcar format-arguments `(mod ,(car format-arguments) 100))))
+ (setf (car format-arguments) `(mod ,(car format-arguments)
100))))
(`year-for-week
(error "Formatting `%s' is currently not implemented" type))
(`month
@@ -606,7 +639,18 @@ to this function.
(let ((scale (expt 10 details)))
(push `(mod (* time ,scale) ,scale) format-arguments)))
(`timezone
- (signal 'datetime-unsupported-timezone nil))
+ (pcase details
+ ((or `abbreviated `full)
+ (let* ((name (datetime-locale-timezone-name locale
timezone nil (eq details 'full)))
+ (dst-name (pcase timezone-data
+ (`(,_constant-offset) name)
+ (_ (datetime-locale-timezone-name locale
timezone t (eq details 'full))))))
+ (if (string= name dst-name)
+ (push (datetime--format-escape-string name) format-parts)
+ (push "%s" format-parts)
+ (push `(if datetime--last-conversion-was-in-dst ,dst-name
,name) format-arguments))))
+ (_
+ (signal 'datetime-unsupported-timezone details))))
(_ (error "Unexpected value %s" type))))))
;; 400 is the size of Gregorian calendar leap year loop.
(let* ((days-in-400-years datetime--gregorian-days-in-400-years)
@@ -686,8 +730,12 @@ to this function.
(while (and (>= offset-in-year (car year-transitions))
(setq offset (cadr year-transitions)
year-transitions (cddr year-transitions))))))
+ ;; Floating-point offset is our internal mark of a transition to
DST. Its value
+ ;; is really an integer anyway.
+ (setf datetime--last-conversion-was-in-dst (floatp offset))
(+ date-time offset))
;; Offset before the very first transition.
+ (setf datetime--last-conversion-was-in-dst nil)
(+ date-time (car (aref all-year-transitions 0))))))
;; 146097 is the value of `datetime--gregorian-days-in-400-years'.
@@ -707,7 +755,7 @@ to this function.
(num-years (length all-year-transitions))
transitions)
(when (>= year-offset num-years)
- (setcar (cdr timezone-data) (setq all-year-transitions (vconcat
all-year-transitions (make-vector (max (1+ (- year-offset num-years)) (/
num-years 2) 10) nil)))))
+ (setf (cadr timezone-data) (setq all-year-transitions (vconcat
all-year-transitions (make-vector (max (1+ (- year-offset num-years)) (/
num-years 2) 10) nil)))))
(let ((year (+ (nth 2 timezone-data) year-offset))
(year-base (+ (nth 0 timezone-data) (* year-offset
datetime--average-seconds-in-year))))
(dolist (rule (nth 3 timezone-data))
@@ -726,15 +774,17 @@ to this function.
(setq year-day (if (< day-of-month 0) (- year-day (mod (-
day-of-week current-weekday) 7)) (+ year-day (mod (- day-of-week
current-weekday) 7))))))
(when (plist-get rule :end-of-day)
(setq year-day (1+ year-day)))
- (push (- (+ (datetime--start-of-day year year-day) (plist-get rule
:time))
- (pcase (plist-get rule :time-definition)
- (`utc 0)
- (`standard (plist-get rule :standard-offset))
- (`wall offset-before)
- (type (error "Unhandled time definition type `%s'"
type)))
- year-base)
+ (push (round (- (+ (datetime--start-of-day year year-day) (plist-get
rule :time))
+ (pcase (plist-get rule :time-definition)
+ (`utc 0)
+ (`standard (plist-get rule :standard-offset))
+ (`wall offset-before)
+ (type (error "Unhandled time definition type
`%s'" type)))
+ year-base))
transitions)
- (push (plist-get rule :after) transitions))))
+ (let ((after (plist-get rule :after)))
+ ;; Mark transitions to DST by making offset a float.
+ (push (if (plist-get rule :dst) (float after) after)
transitions)))))
(aset all-year-transitions year-offset (nreverse transitions))))
@@ -1584,20 +1634,67 @@ Supported fields:
(:eras datetime--english-eras)
(:am-pm datetime--english-am-pm)))))
+(defun datetime-locale-timezone-name (locale timezone dst &optional full)
+ "Get name of TIMEZONE in given LOCALE.
+For timezones that don't have daylight saving time, parameter DST
+is ignored.
+
+By default, abbreviated name (like \"UTC\") suitable for use in
+date-time strings is returned. However, if FULL is non-nil, a
+non-abbreviated name (e.g. \"Coordinated Universal Time\") is
+returned instead."
+ ;; See `datetime--timezone-name-extmap' for description of fallbacks.
+ (let ((names (plist-get (extmap-get datetime--timezone-name-extmap locale t)
timezone)))
+ (cond ((vectorp names)
+ (aref names (if (= (length names) 4)
+ (+ (if full 2 0) (if dst 1 0))
+ (if full 1 0))))
+ ((consp names)
+ (if full
+ (if dst (cdr names) (car names))
+ (datetime-locale-timezone-name 'en timezone dst)))
+ ((stringp names)
+ (if full
+ names
+ (datetime-locale-timezone-name 'en timezone nil)))
+ (t
+ (let ((locale-data (extmap-get datetime--locale-extmap locale t)))
+ (when locale-data
+ (datetime-locale-timezone-name (or (plist-get locale-data
:parent) 'en) timezone dst full)))))))
+
(defun datetime-locale-database-version ()
"Return locale database version, a simple integer.
This version will be incremented each time locale database of the
package is updated. It can be used e.g. to invalidate caches you
-create based on locales `datetime' knows about."
+create based on locales `datetime' knows about.
+
+Note that this database doesn't include timezone names. See
+`datetime-timezone-name-database-version'."
4)
(defun datetime-timezone-database-version ()
"Return timezone database version, a simple integer.
This version will be incremented each time timezone database of the
package is updated. It can be used e.g. to invalidate caches you
-create based on timezones `datetime' knows about and their rules."
- 4)
+create based on timezones `datetime' knows about and their rules.
+
+Locale-specific timezone names are contained in a different
+database. See `datetime-timezone-name-database-version'."
+ 5)
+
+(defun datetime-timezone-name-database-version ()
+ "Return timezone name database version, a simple integer.
+This version will be incremented each time timezone name database
+of the package is updated. It can be used e.g. to invalidate
+caches.
+
+This database includes only locale-specific timezone names.
+Other locale-specific data as well as locale-independent data
+about timezones is contained in different databases. See
+`datetime-locale-database-version' and
+`datetime-timezone-database-version'."
+ 1)
(provide 'datetime)
diff --git a/dev/HarvestData.java b/dev/HarvestData.java
index 5456eeb20d..4221be4b5b 100644
--- a/dev/HarvestData.java
+++ b/dev/HarvestData.java
@@ -24,26 +24,48 @@ public class HarvestData
if (Arrays.asList (args).contains ("--timezones"))
printTimezoneData ();
+
+ if (Arrays.asList (args).contains ("--timezone-names"))
+ printTimezoneNameData ();
}
- protected static void printLocaleData () throws Exception
+
+ protected static List <Locale> getAllLocales ()
{
List <Locale> locales = new ArrayList <> (Arrays.asList
(Locale.getAvailableLocales ()));
+
+ locales.removeIf ((locale) -> {
+ // This way we discard a few locales that can otherwise lead
to duplicate keys
+ // because of use of toLanguageTag(). E.g. `no_NO_NY' is
problematic.
+ if (locale.getVariant ().length () > 0)
+ return true;
+
+ if (!Chronology.ofLocale (locale).getId ().equals ("ISO")) {
+ // Ignore such locales for now.
+ return true;
+ }
+
+ return false;
+ });
+
locales.sort ((a, b) -> a.toLanguageTag ().compareToIgnoreCase
(b.toLanguageTag ()));
+ return locales;
+ }
- Map <Locale, Map <String, String>> data = new LinkedHashMap <> ();
+ protected static List <ZoneId> getAllTimezones ()
+ {
+ List <ZoneId> timezones = ZoneId.getAvailableZoneIds ().stream ().map
((id) -> ZoneId.of (id)).collect (Collectors.toList ());
+ timezones.sort ((a, b) -> a.getId ().compareToIgnoreCase (b.getId ()));
+ return timezones;
+ }
- for (Locale locale : locales) {
- // This way we discard a few locales that can otherwise lead to
duplicate keys
- // because of use of toLanguageTag(). E.g. `no_NO_NY' is
problematic.
- if (locale.getVariant ().length () > 0)
- continue;
+ protected static void printLocaleData () throws Exception
+ {
+ Map <Locale, Map <String, String>> data = new LinkedHashMap <> ();
+
+ for (Locale locale : getAllLocales ()) {
Chronology chronology = Chronology.ofLocale (locale);
- if (!chronology.getId ().equals ("ISO")) {
- // Ignore such locales for now.
- continue;
- }
Map <String, String> map = new LinkedHashMap <> ();
data.put (locale, map);
@@ -135,10 +157,8 @@ public class HarvestData
}
}
- for (Locale locale : locales) {
- if (data.containsKey (locale))
- removeUnnecessaryLocaleData (data, locale);
- }
+ for (Locale locale : getAllLocales ())
+ removeUnnecessaryLocaleData (data, locale);
System.out.println ("(");
for (Map.Entry <Locale, Map <String, String>> entry : data.entrySet ())
@@ -177,7 +197,7 @@ public class HarvestData
protected static void removeUnnecessaryLocaleData (Map <Locale, Map
<String, String>> data, Locale locale)
{
Map <String, String> locale_data = data.get (locale);
- Locale parent = new Locale (locale.getLanguage ());
+ Locale parent = new Locale (locale.getLanguage ());
Map <String, String> parent_data;
if (Objects.equals (locale, parent))
@@ -218,14 +238,12 @@ public class HarvestData
locale_data.remove (main_key);
}
+
protected static void printTimezoneData () throws Exception
{
- List <ZoneId> timezones = ZoneId.getAvailableZoneIds ().stream ().map
((id) -> ZoneId.of (id)).collect (Collectors.toList ());
- timezones.sort ((a, b) -> a.getId ().compareToIgnoreCase (b.getId ()));
-
Map <ZoneId, List <Object>> data = new LinkedHashMap <> ();
- for (ZoneId timezone : timezones) {
+ for (ZoneId timezone : getAllTimezones ()) {
ZoneRules rules = timezone.getRules ();
if (rules.isFixedOffset ())
@@ -246,13 +264,19 @@ public class HarvestData
for (ZoneOffsetTransition transition : transitions) {
int year_offset = (int) ((transition.getInstant
().getEpochSecond () - base) / AVERAGE_SECONDS_IN_YEAR);
if ((transition.getInstant ().getEpochSecond () + 1 -
base) % AVERAGE_SECONDS_IN_YEAR < 1)
- System.err.println (String.format ("*Warning*:
timezone '%s', offset transition at %s would be a potential rounding error",
timezone.getId (), transition.getInstant ()));
+ System.err.printf ("*Warning*: timezone '%s', offset
transition at %s would be a potential rounding error\n", timezone.getId (),
transition.getInstant ());
while (year_offset >= transition_data.size ())
transition_data.add (new ArrayList <> (Arrays.asList
(last_offset)));
transition_data.get (year_offset).add
(transition.getInstant ().getEpochSecond () - (base + year_offset *
AVERAGE_SECONDS_IN_YEAR));
- transition_data.get (year_offset).add (last_offset =
transition.getOffsetAfter ().getTotalSeconds ());
+ last_offset = transition.getOffsetAfter ().getTotalSeconds
();
+
+ // Floating-point offset is our internal mark of a
transition to DST.
+ // Java is over-eager to convert ints to float for us, so
we format
+ // them as strings manually now and add '.0' if
appropriate.
+ boolean to_dst = !Objects.equals
(transition.getOffsetAfter (), rules.getStandardOffset (transition.getInstant
()));
+ transition_data.get (year_offset).add (String.format
(to_dst ? "%d.0" : "%d", last_offset));
}
List <Object> transition_rule_data = new ArrayList <> ();
@@ -288,6 +312,9 @@ public class HarvestData
rule.put (":before", String.valueOf
(transition_rule.getOffsetBefore ().getTotalSeconds ()));
rule.put (":after", String.valueOf
(transition_rule.getOffsetAfter ().getTotalSeconds ()));
+ if (!Objects.equals (transition_rule.getOffsetAfter (),
transition_rule.getStandardOffset ()))
+ rule.put (":dst", "t");
+
transition_rule_data.add (toLispPlist (rule, false));
}
@@ -306,6 +333,141 @@ public class HarvestData
System.out.println (")");
}
+
+ protected static void printTimezoneNameData () throws Exception
+ {
+ Map <Locale, Map <String, String[]>> data = new LinkedHashMap <> ();
+
+ for (Locale locale : getAllLocales ()) {
+ Map <String, String[]> map = new LinkedHashMap <> ();
+
+ DateTimeFormatter abbreviation_retriever =
DateTimeFormatter.ofPattern ("z", locale);
+ DateTimeFormatter full_name_retriever =
DateTimeFormatter.ofPattern ("zzzz", locale);
+
+ for (ZoneId timezone : getAllTimezones ()) {
+ ZonedDateTime dummy_date = ZonedDateTime.ofInstant
(Instant.ofEpochSecond (0), timezone);
+ String[] names = new String[] {
abbreviation_retriever.format (dummy_date), null,
+ full_name_retriever
.format (dummy_date), null };
+ ZoneRules rules = timezone.getRules ();
+
+
+ if (!rules.isFixedOffset ()) {
+ // They are probably already ordered, but I cannot find a
confirmation in
+ // the documentation.
+ List <ZoneOffsetTransition> transitions = new ArrayList
<> (rules.getTransitions ());
+ transitions.sort ((a, b) -> a.getInstant ().compareTo
(b.getInstant ()));
+
+ Instant switch_to_dst = null;
+ for (ZoneOffsetTransition transition : transitions) {
+ if (rules.isDaylightSavings (transition.getInstant ())
&& !rules.isDaylightSavings (Instant.ofEpochSecond (transition.getInstant
().getEpochSecond () - 1))) {
+ switch_to_dst = transition.getInstant ();
+ break;
+ }
+ }
+
+ // I would give a warning, but this seems to be a frequent
occasion.
+ // Maybe it's supposed to be like that.
+ if (switch_to_dst != null) {
+ ZonedDateTime dst = ZonedDateTime.ofInstant
(switch_to_dst, timezone);
+ ZonedDateTime std = ZonedDateTime.ofInstant
(Instant.ofEpochSecond (switch_to_dst.getEpochSecond () - 1), timezone);
+
+ names = new String[] { abbreviation_retriever.format
(std),
+ abbreviation_retriever.format
(dst),
+ full_name_retriever .format
(std),
+ full_name_retriever .format
(dst) };
+
+ if (Objects.equals (names[0], names[1]) &&
Objects.equals (names[2], names[3])) {
+ // Another not quite understandable, but frequent
thing.
+ // There seem to also be some timezone/locale
pairs where
+ // abbreviations match, but full names don't.
Those we
+ // ignore.
+ names[1] = names[3] = null;
+ }
+ }
+ }
+
+ map.put (timezone.getId (), names);
+ }
+
+ data.put (locale, map);
+ }
+
+ for (Locale locale : getAllLocales ())
+ removeUnnecessaryTimezoneNameData (data, locale);
+
+ // Many timezones in Java are broken in that instants formatted/parsed
in them get
+ // shifted around. Not much we can do about this, but at least we'll
keep the
+ // list internally so that we can avoid testing in them.
+ List <String> broken = new ArrayList <> ();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern
("yyyy-MM-dd HH:mm:ss z");
+ Instant[] check_at = { Instant.from (formatter.parse
("2020-01-01 00:00:00 UTC")),
+ Instant.from (formatter.parse
("2020-07-01 00:00:00 UTC")) };
+
+ for (ZoneId timezone : getAllTimezones ()) {
+ if (Arrays.stream (check_at)
+ .anyMatch ((instant) -> !Objects.equals (instant, Instant.from
(formatter.parse (formatter.format (ZonedDateTime.ofInstant (instant,
timezone)))))))
+ broken.add (timezone.getId ());
+ }
+
+ System.out.println ("(");
+
+ for (Map.Entry <Locale, Map <String, String[]>> entry : data.entrySet
()) {
+ Map <String, String> values = new LinkedHashMap <> ();
+ for (Map.Entry <String, String[]> name_entry : entry.getValue
().entrySet ()) {
+ String[] names = name_entry.getValue ();
+
+ // See description of fallbacks in `datetime.el'.
+ values.put (name_entry.getKey (), (names[0] != null ||
names[1] != null
+ ? (names[3] != null
+ ? String.format ("[%s %s
%s %s]", quoteString (names[0]), quoteString (names[1]), quoteString
(names[2]), quoteString (names[3]))
+ : String.format ("[%s
%s]", quoteString (names[0]), quoteString
(names[2])))
+ : (names[3] != null
+ ? String.format ("(%s .
%s)", quoteString (names[2]), quoteString (names[3]))
+ : quoteString
(names[2]))));
+ }
+
+ System.out.println (toLispPlist (entry.getKey ().toLanguageTag (),
values, false));
+ }
+
+ if (!broken.isEmpty ()) {
+ broken.add (0, ":broken");
+ System.out.println (toLispList (broken));
+ }
+
+ System.out.println (")");
+ }
+
+ protected static void removeUnnecessaryTimezoneNameData (Map <Locale, Map
<String, String[]>> data, Locale locale)
+ {
+ if (Objects.equals (locale, Locale.ENGLISH))
+ return;
+
+ Map <String, String[]> locale_data = data.get (locale);
+ Map <String, String[]> english_data = data.get (Locale.ENGLISH);
+ Locale parent = new Locale (locale.getLanguage
());
+ Map <String, String[]> parent_data;
+
+ if (Objects.equals (locale, parent))
+ parent_data = Collections.emptyMap ();
+ else {
+ removeUnnecessaryTimezoneNameData (data, parent);
+ parent_data = data.get (parent);
+ }
+
+ // Discard abbreviated names that match those for English locale and
leave only
+ // the full names.
+ locale_data.entrySet ().stream ().forEach ((entry) -> {
+ String[] names = entry.getValue ();
+ if (Objects.equals (names[0], english_data.get (entry.getKey
()) [0]) && Objects.equals (names[1], english_data.get (entry.getKey ()) [1]))
+ names[0] = names[1] = null;
+ });
+
+ // Fall back to the parent locale where possible.
+ locale_data.entrySet ().removeIf ((entry) -> (Objects .equals
(entry.getValue (), english_data.get (entry.getKey ()))
+ || Objects.equals
(entry.getValue (), parent_data .get (entry.getKey ()))));
+ }
+
+
protected static String toLispList (List <?> list)
{
if (list == null || list.isEmpty ())
@@ -364,6 +526,7 @@ public class HarvestData
return string != null ? String.format ("\"%s\"", string.replaceAll
("\\\\", "\\\\").replaceAll ("\"", "\\\"")) : "nil";
}
+
protected static boolean isLeapYear (int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
diff --git a/test/ProcessTimestamp.java b/test/ProcessTimestamp.java
index 16202b688f..200ccd194b 100644
--- a/test/ProcessTimestamp.java
+++ b/test/ProcessTimestamp.java
@@ -43,7 +43,7 @@ public class ProcessTimestamp
switch (command) {
case "format":
System.out.println (DateTimeFormatter.ofPattern (pattern,
locale)
- .format (LocalDateTime.ofInstant
(Instant.ofEpochSecond ((long) Math.floor (timestamp),
+ .format (ZonedDateTime.ofInstant
(Instant.ofEpochSecond ((long) Math.floor (timestamp),
(int) Math.floor ((timestamp - Math.floor (timestamp)) *
1_000_000_000)),
timezone)));
break;
@@ -53,6 +53,7 @@ public class ProcessTimestamp
.parseCaseInsensitive ()
// Commented out since it
triggers bugs in obscure locales in Java.
// We don't use this for
testing anyway.
+ // See:
https://bugs.openjdk.java.net/browse/JDK-8211306
// .parseLenient ()
.appendPattern (pattern));
diff --git a/test/base.el b/test/base.el
index e4ffb0e1a3..269341cc9e 100644
--- a/test/base.el
+++ b/test/base.el
@@ -1,6 +1,6 @@
;;; -*- lexical-binding: t -*-
-;; Copyright (C) 2018-2019 Paul Pogonyshev
+;; Copyright (C) 2018-2020 Paul Pogonyshev
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
diff --git a/test/format.el b/test/format.el
index deb14f52a1..2e49763195 100644
--- a/test/format.el
+++ b/test/format.el
@@ -1,6 +1,6 @@
;;; -*- lexical-binding: t -*-
-;; Copyright (C) 2018-2019 Paul Pogonyshev
+;; Copyright (C) 2018-2020 Paul Pogonyshev
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
@@ -56,9 +56,8 @@
(dolist (locale (datetime-list-locales t))
(dolist (variant '(:short :medium :long :full))
(let ((pattern (datetime-locale-date-time-pattern locale variant)))
- (unless (datetime-pattern-includes-timezone-p 'java pattern)
- (datetime--test-set-up-formatter 'UTC locale pattern
- (datetime--test-formatter now))))))))
+ (datetime--test-set-up-formatter 'UTC locale pattern
+ (datetime--test-formatter now)))))))
(ert-deftest datetime-test-formatting-various-timestamps-1 ()
(datetime--test-set-up-formatter 'UTC 'en "yyyy-MM-dd HH:mm:ss.SSS"
@@ -114,3 +113,9 @@
(datetime--test-set-up-formatter 'Australia/Hobart 'en "yyyy-MM-dd
HH:mm:ss.SSS"
;; Rule-based transition on 2014-10-05.
(datetime--test-formatter-around-transition 1412438400)))
+
+(ert-deftest datetime-test-formatting-with-timezone-name-1 ()
+ (datetime--test-set-up-formatter 'Europe/Berlin 'en "yyyy-MM-dd HH:mm:ss z"
+ ;; Rule-based transition on 2014-10-26. Should also result in
+ ;; timezone name changing between CEST and CET.
+ (datetime--test-formatter-around-transition 1414285200)))
diff --git a/test/parse.el b/test/parse.el
index b8fcf95e89..edf8b81127 100644
--- a/test/parse.el
+++ b/test/parse.el
@@ -1,6 +1,6 @@
;;; -*- lexical-binding: t -*-
-;; Copyright (C) 2018-2019 Paul Pogonyshev
+;; Copyright (C) 2018-2020 Paul Pogonyshev
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
diff --git a/timezone-data.extmap b/timezone-data.extmap
index bfc9e17e68..0d40d943bf 100644
Binary files a/timezone-data.extmap and b/timezone-data.extmap differ
diff --git a/timezone-name-data.extmap b/timezone-name-data.extmap
new file mode 100644
index 0000000000..ab910b2085
Binary files /dev/null and b/timezone-name-data.extmap differ
- [nongnu] elpa/datetime 8a1d0ba701 140/147: Improve on commit 8cfa779 further; again, important mostly for Windows., (continued)
- [nongnu] elpa/datetime 8a1d0ba701 140/147: Improve on commit 8cfa779 further; again, important mostly for Windows., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime c6eb2d262c 145/147: Disable CI on Windows, apparently there are problems with either GitHub setup on used Emacs binary, in any case, not related to us., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime adbbc9342d 019/147: Fix `.travis.yml' syntax after commit 0ef21a1., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime be2d3a4606 032/147: Add Emacs 26 environment for Travis CI., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 178befd488 042/147: Release version 0.6.1., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 6a2b667178 049/147: Implement proper completion when customizing `datetime-locale' and `datetime-timezone'., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime eda5e55db4 054/147: Fix `Eldev' so that `.extmap' files are included into generate package., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 5da90e55e4 055/147: Regenerate timezone and locale extmaps using OpenJDK 11.0.4 (build 11.0.4+11-post-Debian-1deb10u1); change internal format of date-time-rule as needed by new data., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime f6ec1ac75a 063/147: Fix Travis CI badge image URL., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 55297bf409 062/147: Release version 0.6.6., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 28e31cbe1c 065/147: Add support for timezone names in timestamps formatted by the library.,
ELPA Syncer <=
- [nongnu] elpa/datetime 248227412d 073/147: Explicitly define when a function became obsolete, required by Emacs 28 (issue #4)., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 276f9d4304 074/147: Release version 0.7., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 3f89791b97 075/147: Experimental: add another GitHub workflow to automatically detect when 'extmap' files should be upgraded., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime c0fc355711 076/147: Allow to trigger this manually too., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 32f62080f4 081/147: Fix building of parsing regexp in case there are run-together numeric groups in the pattern., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime afd711ce3d 083/147: Update copyright notices to include 2022., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 10d55deb5b 082/147: Also test on Emacs 28.1., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime cdbf529935 093/147: Post-release version bump., ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 8b23c11eef 096/147: Bump actions/setup-java from 2 to 3, ELPA Syncer, 2025/01/31
- [nongnu] elpa/datetime 2f8037f0ba 103/147: Implement timezone offset formatting (currently, only 'Z' in a Java pattern)., ELPA Syncer, 2025/01/31