From 8908a1bda19f4cc6e3a2d3dcdcfcd654bb599388 Mon Sep 17 00:00:00 2001 From: Max Nikulin Date: Fri, 8 Apr 2022 23:10:50 +0700 Subject: [PATCH] org-macs.el: Introduce a helper for `encode-time' * lisp/org-macs.el (org-encode-time): New compatibility and convenience helper macro to allow a list for time components or separate arguments independently of Emacs version. * testing/lisp/test-org.el (org-test-with-timezone): New macro to ensure that some code is executed with certain TZ environment value and thus particular daylight saving time or other time shift rules are active. * testing/lisp/test-org.el (test-org/org-encode-time): Tests for various ways to call `org-encode-time'. Ensure recommended way to call `encode-time' for Emacs-27 and newer with hope to avoid bugs due to attempts to modernize the code similar to bug#54731. 6-elements list may be allowed as `encode-time' argument since Emacs-29, see bug#54764. --- lisp/org-macs.el | 35 ++++++++++++++++++++ testing/lisp/test-org.el | 71 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/lisp/org-macs.el b/lisp/org-macs.el index b84898776..7e5032c0f 100644 --- a/lisp/org-macs.el +++ b/lisp/org-macs.el @@ -1392,6 +1392,41 @@ nil, just return 0." (b (org-2ft b))) (and (> a 0) (> b 0) (\= a b)))) +(if (version< emacs-version "27.1") + (defmacro org-encode-time (&rest time) + (if (cdr time) + `(encode-time ,@time) + `(apply #'encode-time ,@time))) + (if (ignore-errors (with-no-warnings (encode-time '(0 0 0 1 1 1971)))) + (defmacro org-encode-time (&rest time) + (pcase (length time) ; Emacs-29 since d75e2c12eb + (1 `(encode-time ,@time)) + ((or 6 9) `(encode-time (list ,@time))) + (_ (error "`org-encode-time' may be called with 1, 6, or 9 arguments but %d given" + (length time))))) + (defmacro org-encode-time (&rest time) + (pcase (length time) + (1 `(encode-time ,@time)) + (6 `(encode-time (list ,@time nil -1 nil))) + (9 `(encode-time (list ,@time))) + (_ (error "`org-encode-time' may be called with 1, 6, or 9 arguments but %d given" + (length time))))))) +(put 'org-encode-time 'function-documentation + "Compatibility and convenience helper for `encode-time'. +May be called with 9 components list (SECONDS ... YEAR IGNORED DST ZONE) +as the recommended way since Emacs-27 or with 6 or 9 separate arguments +similar to the only possible variant for Emacs-26 and earlier. +6 elements list as the only argument causes wrong type argument till Emacs-29. + +Warning: use -1 for DST to guess the actual value, nil means no +daylight saving time and may be wrong at particular time. + +DST value is ignored prior to Emacs-27. Since Emacs-27 DST value matters +even when multiple arguments is passed to this macro and such +behavior is different from `encode-time'. See +Info node `(elisp)Time Conversion' for details and caveats, +preferably the latest version.") + (defun org-parse-time-string (s &optional nodefault) "Parse Org time string S. diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 692c9c007..10fcfc997 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -24,6 +24,20 @@ (eval-and-compile (require 'cl-lib)) + +;;; Helpers + +(defmacro org-test-with-timezone (tz &rest body) + "Evaluate BODY with TZ environment temporary set to the passed value." + (declare (indent 1)) + (org-with-gensyms (tz-saved) + `(let ((,tz-saved (getenv "TZ"))) + (unwind-protect + (progn + (setenv "TZ" ,tz) + ,@body) + (setenv "TZ" ,tz-saved))))) + ;;; Comments @@ -179,6 +193,63 @@ ;;; Date and time analysis +(ert-deftest test-org/org-encode-time () + "Test various ways to call `org-encode-time'" + (org-test-with-timezone "UTC" + ;; list as the sole argument + (should (string-equal + "2022-03-24 23:30:01" + (format-time-string + "%F %T" + (org-encode-time '(1 30 23 24 3 2022 nil -1 nil))))) + ;; SECOND...YEAR + (should (string-equal + "2022-03-24 23:30:02" + (format-time-string + "%F %T" + (org-encode-time 2 30 23 24 3 2022)))) + ;; SECOND...YEAR IGNORED DST ZONE + (should (string-equal + "2022-03-24 23:30:03" + (format-time-string + "%F %T" + (org-encode-time 3 30 23 24 3 2022 nil -1 nil)))) + ;; function call + (should (string-equal + "2022-03-24 23:30:04" + (format-time-string + "%F %T" + (org-encode-time (apply #'list 4 30 23 '(24 3 2022 nil -1 nil)))))) + ;; wrong number of arguments + (if (not (version< emacs-version "27.1")) + (should-error (string-equal + "2022-03-24 23:30:05" + (format-time-string + "%F %T" + (org-encode-time 5 30 23 24 3 2022 nil)))))) + ;; daylight saving time + (if (not (version< emacs-version "27.1")) + ;; DST value is not ignored for multiple arguments unlike for `encode-time' + (should (string-equal + "2022-04-01 00:30:06 +0200 CEST" + (format-time-string + "%F %T %z %Z" + (org-encode-time 6 30 23 31 3 2022 nil nil "Europe/Madrid") + "Europe/Madrid"))) + (should (string-equal + "2022-03-31 23:30:07 +0200 CEST" + (format-time-string + "%F %T %z %Z" + (org-encode-time 7 30 23 31 3 2022 nil t "Europe/Madrid") + "Europe/Madrid")))) + (org-test-with-timezone "Europe/Madrid" + ;; Standard time is not forced when DST is not specified + (should (string-equal + "2022-03-31 23:30:08" + (format-time-string + "%F %T" + (org-encode-time 8 30 23 31 3 2022)))))) + (ert-deftest test-org/org-read-date () "Test `org-read-date' specifications." ;; Parse ISO date with abbreviated year and month.