branch: elpa/datetime
commit 8cfa779858187c6149cf0b4048005f59ebac271b
Author: Paul Pogonyshev <pogonys...@gmail.com>
Commit: Paul Pogonyshev <pogonys...@gmail.com>

    Improve system timezone determining; especially important for Windows.
---
 datetime.el          |  10 +++++++---
 dev/HarvestData.java |  46 +++++++++++++++++++++++++++++++++++++++++++++-
 test/base.el         |  23 +++++++++++++++++++++++
 timezone-data.extmap | Bin 900579 -> 902807 bytes
 4 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/datetime.el b/datetime.el
index c7bd600817..7b61037f69 100644
--- a/datetime.el
+++ b/datetime.el
@@ -305,8 +305,12 @@ form:
                                        "?"))))
       (if (extmap-contains-key datetime--timezone-extmap system-timezone)
           system-timezone
-        (error "Failed to determine system timezone%s; consider customizing 
`datetime-timezone' variable"
-               (if (eq system-timezone '\?) "" (format-message " (found raw 
value: `%s')" system-timezone)))))))
+        (let* ((aliases (extmap-get datetime--timezone-extmap :aliases t))
+               (entry   (assq system-timezone aliases)))
+          (if entry
+              (cdr entry)
+            (error "Failed to determine system timezone%s; consider 
customizing `datetime-timezone' variable"
+                   (if (eq system-timezone '\?) "" (format-message " (found 
raw value: `%s')" system-timezone)))))))))
 
 
 (defun datetime--parse-pattern (type pattern options)
@@ -2064,7 +2068,7 @@ 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'."
-  7)
+  8)
 
 (defun datetime-timezone-name-database-version ()
   "Return timezone name database version, a simple integer.
diff --git a/dev/HarvestData.java b/dev/HarvestData.java
index 4b681db9d8..59a71c4d0e 100644
--- a/dev/HarvestData.java
+++ b/dev/HarvestData.java
@@ -325,7 +325,13 @@ public class HarvestData
 
     protected static void printTimezoneData () throws Exception
     {
-        Map <ZoneId, List <Object>>  data = new LinkedHashMap <> ();
+        var        data                                  = new LinkedHashMap 
<ZoneId, List <Object>> ();
+        var        aliases                               = new LinkedHashMap 
<String, Set <ZoneId>> ();
+        var        timezones_with_matching_abbreviations = new HashSet 
<ZoneId> ();
+        var        abbreviation_retriever                = 
DateTimeFormatter.ofPattern ("z", Locale.ENGLISH);
+        var        utc_formatter                         = 
DateTimeFormatter.ofPattern ("yyyy-MM-dd HH:mm:ss z");
+        Instant[]  abbreviations_at                      = { Instant.from 
(utc_formatter.parse ("2020-01-01 00:00:00 UTC")),
+                                                             Instant.from 
(utc_formatter.parse ("2020-07-01 00:00:00 UTC")) };
 
         for (ZoneId timezone : getAllTimezones ()) {
             ZoneRules  rules = timezone.getRules ();
@@ -434,12 +440,50 @@ public class HarvestData
                 zone_data.add (toLispList (transition_rule_data));
 
                 data.put (timezone, zone_data);
+
+                for (var at : abbreviations_at) {
+                    var  abbreviation = abbreviation_retriever.format 
(ZonedDateTime.ofInstant (at, timezone));
+                    if (abbreviation.equals (timezone.getId ()))
+                        timezones_with_matching_abbreviations.add (timezone);
+                    else
+                        aliases.computeIfAbsent (abbreviation, __ -> new 
HashSet <> ()).add (timezone);
+                }
             }
         }
 
+        // When computing timezone alias map we use these rules:
+        // - if timezone identifier matches its normal abbreviation, its DST 
abbreviation
+        //   becomes an alias (example: "CEST" becomes an alias for "CET");
+        // - else both abbreviations become an alias for the timezone, but 
only if they
+        //   don't clash with anything else (example: "EGT" and "EGST" are 
both aliases
+        //   for "America/Scoresbysund").
+        var  timezones_with_conflicting_aliases = new HashSet <ZoneId> ();
+        for (var timezones : aliases.values ()) {
+            if (timezones.size () > 1)
+                timezones_with_conflicting_aliases.addAll (timezones);
+        }
+
+        timezones_with_conflicting_aliases.removeAll 
(timezones_with_matching_abbreviations);
+
+        for (var it = aliases.values ().iterator (); it.hasNext ();) {
+            var  with_this_alias = it.next ();
+            with_this_alias.removeAll (timezones_with_conflicting_aliases);
+            if (with_this_alias.size () != 1)
+                it.remove ();
+        }
+
         System.out.println ("(");
+
         for (Map.Entry <ZoneId, List <Object>> entry : data.entrySet ())
             System.out.format ("(%s\n %s)\n", entry.getKey (), entry.getValue 
().stream ().map (String::valueOf).collect (Collectors.joining ("\n ")));
+
+        // Aliases don't go into names: they are relatively few and are not 
used for
+        // formatting or parsing, currently only when determining OS timezone.
+        System.out.format ("(:aliases\n %s)\n",
+                           aliases.entrySet ().stream ()
+                           .map ((entry) -> String.format ("(%s . %s)", 
entry.getKey (), entry.getValue ().iterator ().next ().getId ()))
+                           .collect (Collectors.joining ("\n ")));
+
         System.out.println (")");
     }
 
diff --git a/test/base.el b/test/base.el
index c20b1e5df9..c45682a075 100644
--- a/test/base.el
+++ b/test/base.el
@@ -89,6 +89,24 @@
      (datetime--test-set-up-parser datetime--test-timezone 
datetime--test-locale datetime--test-pattern
        ,@body)))
 
+;; Copied from Eldev source code, see documentation there.
+(defmacro datetime--advised (spec &rest body)
+  (declare (indent 1) (debug (sexp body)))
+  (let ((symbol   (nth 0 spec))
+        (where    (nth 1 spec))
+        (function (nth 2 spec))
+        (props    (nthcdr 3 spec))
+        (fn       (make-symbol "$fn")))
+    `(let ((,fn ,function))
+       (when ,fn
+         (if (advice-member-p ,fn ,symbol)
+             (setf ,fn nil)
+           (advice-add ,symbol ,where ,fn ,@props)))
+       (unwind-protect
+           ,(macroexp-progn body)
+         (when ,fn
+           (advice-remove ,symbol ,fn))))))
+
 (defun datetime--test (command times)
   (unless (listp times)
     (setq times (list times)))
@@ -211,5 +229,10 @@ am-pm                     = %S"
             (dotimes (k length)
               (should (stringp (aref value k))))))))))
 
+(ert-deftest datetime--determine-system-timezone ()
+  (datetime--advised ('current-time-zone :override (lambda () '(7200 "CEST")))
+    (let ((system-type 'this-system-type-is-not-specialcase))
+      (should (eq (datetime--determine-system-timezone) 'CET)))))
+
 
 (provide 'test/base)
diff --git a/timezone-data.extmap b/timezone-data.extmap
index 731fd92781..fbac550399 100644
Binary files a/timezone-data.extmap and b/timezone-data.extmap differ

Reply via email to