From fb718a57123036e40cb8b3b813d734e590a104fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Fri, 2 Oct 2020 18:50:50 +0200
Subject: [PATCH] Calc: fix formatting and parsing Unix time (bug#43759)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The number of days from epoch to Jan 1, 1970 that was used in parsing
and formatting Unix time was incorrect.  The previous fix
(in e368697ce36) was incomplete.

Reported by Vincent Belaïche.

* lisp/calc/calc-forms.el (math-unix-epoch): New constant.
(math-format-date-part, math-parse-standard-date, calcFunc-unixtime):
Use math-unix-epoch instead of a constant that is sometimes wrong.
---
 lisp/calc/calc-forms.el      | 13 ++++++++----
 test/lisp/calc/calc-tests.el | 41 ++++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+), 4 deletions(-)

diff --git a/lisp/calc/calc-forms.el b/lisp/calc/calc-forms.el
index 6d70126c09..a2f6696866 100644
--- a/lisp/calc/calc-forms.el
+++ b/lisp/calc/calc-forms.el
@@ -709,6 +709,10 @@ math-julian-date-beginning-int
   "The beginning of the Julian date calendar,
 as measured in the integer number of days before December 31, 1 BC (Gregorian).")
 
+(defconst math-unix-epoch 719163
+  "The beginning of Unix time: days from December 31, 1 BC (Gregorian)
+to Jan 1, 1970 AD.")
+
 (defun math-format-date-part (x)
   (cond ((stringp x)
 	 x)
@@ -730,7 +734,8 @@ math-format-date-part
                               (math-floor math-fd-date)
                               math-julian-date-beginning-int)))
 	((eq x 'U)
-	 (math-format-number (nth 1 (math-date-parts math-fd-date 719164))))
+	 (math-format-number (nth 1 (math-date-parts math-fd-date
+                                                     math-unix-epoch))))
         ((memq x '(IYYY Iww w))
          (progn
            (or math-fd-iso-dt
@@ -1173,7 +1178,7 @@ math-parse-standard-date
 		      (setq num (math-match-substring math-pd-str 0)
 			    math-pd-str (substring math-pd-str (match-end 0))
 			    num (math-date-to-dt
-				 (math-add 719164
+				 (math-add math-unix-epoch
 					   (math-div (math-read-number num)
 						     '(float 864 2))))
 			    hour (nth 3 num)
@@ -1434,11 +1439,11 @@ calcFunc-julian
 (defun calcFunc-unixtime (date &optional zone)
   (if (math-realp date)
       (progn
-	(setq date (math-add 719163 (math-div date '(float 864 2))))
+	(setq date (math-add math-unix-epoch (math-div date '(float 864 2))))
 	(list 'date (math-sub date (math-div (calcFunc-tzone zone date)
 					     '(float 864 2)))))
     (if (eq (car date) 'date)
-	(math-add (nth 1 (math-date-parts (nth 1 date) 719163))
+	(math-add (nth 1 (math-date-parts (nth 1 date) math-unix-epoch))
 		  (calcFunc-tzone zone date))
       (math-reject-arg date 'datep))))
 
diff --git a/test/lisp/calc/calc-tests.el b/test/lisp/calc/calc-tests.el
index 4dded007f7..6eb0788052 100644
--- a/test/lisp/calc/calc-tests.el
+++ b/test/lisp/calc/calc-tests.el
@@ -534,6 +534,47 @@ calc-business-days
       )
   ))
 
+(ert-deftest calc-unix-date ()
+  (cl-flet ((m (s) (math-parse-date s)))
+    ;; calcFunc-unixtime (command "t U") converts a date value to Unix time,
+    ;; and a number to a date.
+    (should (equal (m "1970-01-01") '(date 719163)))
+    (should (equal (calcFunc-unixtime (m "1970-01-01") 0) 0))
+    (should (equal (calc-tests--calc-to-number (cadr (calcFunc-unixtime 0 0)))
+                   (cadr (m "1970-01-01"))))
+    (should (equal (calcFunc-unixtime (m "2020-09-07") 0)
+                   (* (- (cadr (m "2020-09-07"))
+                         (cadr (m "1970-01-01")))
+                      86400)))
+    (should (equal (calcFunc-unixtime (m "1991-01-09 06:00") 0)
+                   663400800))
+    (should (equal (calc-tests--calc-to-number
+                    (cadr (calcFunc-unixtime 663400800 0)))
+                   726841.25))
+
+    (let* ((d-1970-01-01 (m "1970-01-01"))
+           (d-2020-09-07 (m "2020-09-07"))
+           (d-1991-01-09-0600 (m "1991-01-09 06:00"))
+           (calc-date-format '(U)))
+      ;; Test parsing Unix time.
+      (should (equal (calc-tests--calc-to-number
+                      (cadr (math-parse-date "0")))
+                     719163))
+      (should (equal (calc-tests--calc-to-number
+                      (cadr (math-parse-date "469324800")))
+                     (+ 719163 (/ 469324800 86400))))
+      (should (equal (calc-tests--calc-to-number
+                      (cadr (math-parse-date "663400800")))
+                     726841.25))
+
+      ;; Test formatting Unix time.
+      (should (equal (math-format-date d-1970-01-01) "0"))
+      (should (equal (math-format-date d-2020-09-07)
+                     (number-to-string (* (- (cadr d-2020-09-07)
+                                             (cadr d-1970-01-01))
+                                          86400))))
+      (should (equal (math-format-date d-1991-01-09-0600) "663400800")))))
+
 (provide 'calc-tests)
 ;;; calc-tests.el ends here
 
-- 
2.21.1 (Apple Git-122.3)

