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

    Fix parsing (most likely also formatting) of future timestamps in certain 
timezones.
---
 datetime.el   | 64 +++++++++++++++++++++++++++++++++--------------------------
 test/parse.el |  8 ++++++++
 2 files changed, 44 insertions(+), 28 deletions(-)

diff --git a/datetime.el b/datetime.el
index 5e1f2690ab..202cf4803f 100644
--- a/datetime.el
+++ b/datetime.el
@@ -757,34 +757,42 @@ to this function.
     (when (>= year-offset num-years)
       (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))
-        (let* ((month           (plist-get rule :month))
-               (day-of-month    (plist-get rule :day-of-month))
-               (effective-month (if (< day-of-month 0) month (1- month)))
-               (day-of-week     (plist-get rule :day-of-week))
-               (year-day        (+ (aref 
datetime--gregorian-cumulative-month-days effective-month)
-                                   (if (and (>= effective-month 2) 
(datetime--gregorian-leap-year-p year)) 1 0)
-                                   day-of-month -1))
-               (offset-before   (plist-get rule :before)))
-          (unless transitions
-            (push offset-before transitions))
-          (when day-of-week
-            (let ((current-weekday (% (+ year-day (aref 
datetime--gregorian-first-day-of-year (mod year 400))) 7)))
-              (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 (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)
-          (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)))))
+          (year-base (+ (nth 0 timezone-data) (* year-offset 
datetime--average-seconds-in-year)))
+          (rules     (nth 3 timezone-data)))
+      (if rules
+          (dolist (rule rules)
+            (let* ((month           (plist-get rule :month))
+                   (day-of-month    (plist-get rule :day-of-month))
+                   (effective-month (if (< day-of-month 0) month (1- month)))
+                   (day-of-week     (plist-get rule :day-of-week))
+                   (year-day        (+ (aref 
datetime--gregorian-cumulative-month-days effective-month)
+                                       (if (and (>= effective-month 2) 
(datetime--gregorian-leap-year-p year)) 1 0)
+                                       day-of-month -1))
+                   (offset-before   (plist-get rule :before)))
+              (unless transitions
+                (push offset-before transitions))
+              (when day-of-week
+                (let ((current-weekday (% (+ year-day (aref 
datetime--gregorian-first-day-of-year (mod year 400))) 7)))
+                  (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 (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)
+              (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))))
+        ;; No transition rules.  Take the offset after the last historical 
transition.
+        (let ((k (length all-year-transitions)))
+          (while (null transitions)
+            (let ((historic-transitions (aref all-year-transitions (setf k (1- 
k)))))
+              (when historic-transitions
+                (setf transitions `(,(car (last historic-transitions))))))))))
     (aset all-year-transitions year-offset (nreverse transitions))))
 
 
diff --git a/test/parse.el b/test/parse.el
index 4e6166f779..9a0d589b6a 100644
--- a/test/parse.el
+++ b/test/parse.el
@@ -138,3 +138,11 @@
   (datetime--test-set-up-parser 'UTC 'en "dd 'of' MMMM '(month' M')'"
     (datetime--test-parser "12 of March (month 3)")
     (should-error (funcall datetime--test-parser "12 of March (month 1)") 
:type 'datetime-invalid-string)))
+
+
+(ert-deftest datetime-parsing-future-timestamp-1 ()
+  ;; Real failure: would cause an exception on certain timezones
+  ;; without transition rules (e.g. `Africa/Algiers').
+  (dolist (timezone (datetime-list-timezones))
+    (datetime--test-set-up-parser timezone 'en "yyyy-MM-dd HH:mm:ss"
+      (datetime--test-parser '("2100-01-01 00:00:00")))))

Reply via email to