diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 5e11704fd..0f836d946 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -42,6 +42,12 @@ possible via column view value edit or with ==. See [[*~org-columns-toggle-or-columns-quit~]] ** Miscellaneous +*** Duration can be read and written in compact form + +~org-duration-to-minutes~ understands =1d3h5min= as a duration, +whereas ~org-duration-from-minutes~ can output this compact form if +the duration format contains the symbol ~compact~. + *** Fontify whole TODO headlines This feature is the same as ~org-fontify-done-headline~, but for TODO diff --git a/lisp/org-duration.el b/lisp/org-duration.el index 155bfae6c..a49e2c919 100644 --- a/lisp/org-duration.el +++ b/lisp/org-duration.el @@ -28,14 +28,16 @@ ;; - 3:12 ;; - 1:23:45 ;; - 1y 3d 3h 4min +;; - 1d3h5min ;; - 3d 13:35 ;; - 2.35h ;; ;; More accurately, it consists of numbers and units, as defined in -;; variable `org-duration-units', separated with white spaces, and -;; a "H:MM" or "H:MM:SS" part. White spaces are tolerated between the -;; number and its relative unit. Variable `org-duration-format' -;; controls durations default representation. +;; variable `org-duration-units', possibly separated with white +;; spaces, and an optional "H:MM" or "H:MM:SS" part, which always +;; comes last. White spaces are tolerated between the number and its +;; relative unit. Variable `org-duration-format' controls durations +;; default representation. ;; ;; The library provides functions allowing to convert a duration to, ;; and from, a number of minutes: `org-duration-to-minutes' and @@ -122,8 +124,7 @@ are specified here. Units with a zero value are skipped, unless REQUIRED? is non-nil. In that case, the unit is always used. -Eventually, the list can contain one of the following special -entries: +The list can also contain one of the following special entries: (special . h:mm) (special . h:mm:ss) @@ -139,6 +140,10 @@ entries: first one required or with a non-zero integer part. If there is no such unit, the smallest one is used. +Eventually, if the list contains the symbol `compact', the +duration is expressed in a compact form, without any white space +between units. + For example, ((\"d\" . nil) (\"h\" . t) (\"min\" . t)) @@ -172,7 +177,6 @@ a 2-digits fractional part, of \"d\" unit. A duration shorter than a day uses \"h\" unit instead." :group 'org-time :group 'org-clock - :version "26.1" :package-version '(Org . "9.1") :type '(choice (const :tag "Use H:MM" h:mm) @@ -191,7 +195,8 @@ than a day uses \"h\" unit instead." (const h:mm)) (cons :tag "Use both units and H:MM:SS" (const special) - (const h:mm:ss)))))) + (const h:mm:ss)) + (const :tag "Use compact form" compact))))) ;;; Internal variables and functions @@ -249,13 +254,10 @@ When optional argument CANONICAL is non-nil, refer to org-duration-units)) t))) (setq org-duration--full-re - (format "\\`[ \t]*%s\\(?:[ \t]+%s\\)*[ \t]*\\'" - org-duration--unit-re - org-duration--unit-re)) + (format "\\`\\(?:[ \t]*%s\\)+[ \t]*\\'" org-duration--unit-re)) (setq org-duration--mixed-re - (format "\\`[ \t]*\\(?1:%s\\(?:[ \t]+%s\\)*\\)[ \t]+\ + (format "\\`\\(?1:\\([ \t]*%s\\)+\\)[ \t]*\ \\(?2:[0-9]+\\(?::[0-9][0-9]\\)\\{1,2\\}\\)[ \t]*\\'" - org-duration--unit-re org-duration--unit-re))) ;;;###autoload @@ -353,10 +355,11 @@ Raise an error if expected format is unknown." ;; Represent minutes above hour using provided units and H:MM ;; or H:MM:SS below. (let* ((units-part (* min-modifier (/ (floor minutes) min-modifier))) - (minutes-part (- minutes units-part))) + (minutes-part (- minutes units-part)) + (compact (memq 'compact duration-format))) (concat (org-duration-from-minutes units-part truncated-format canonical) - " " + (and (not compact) " ") (org-duration-from-minutes minutes-part mode)))))) ;; Units format. (duration-format @@ -368,12 +371,16 @@ Raise an error if expected format is unknown." (format "%%.%df" digits)))) (selected-units (sort (cl-remove-if - ;; Ignore special format cells. - (lambda (pair) (pcase pair (`(special . ,_) t) (_ nil))) + ;; Ignore special format cells and compact option. + (lambda (pair) + (pcase pair + ((or `compact `(special . ,_)) t) + (_ nil))) duration-format) (lambda (a b) (> (org-duration--modifier (car a) canonical) - (org-duration--modifier (car b) canonical)))))) + (org-duration--modifier (car b) canonical))))) + (separator (if (memq 'compact duration-format) "" " "))) (cond ;; Fractional duration: use first unit that is either required ;; or smaller than MINUTES. @@ -402,8 +409,8 @@ Raise an error if expected format is unknown." (cond ((<= modifier minutes) (let ((value (floor minutes modifier))) (cl-decf minutes (* value modifier)) - (format " %d%s" value unit))) - (required? (concat " 0" unit)) + (format "%s%d%s" separator value unit))) + (required? (concat separator "0" unit)) (t "")))) selected-units "")))) diff --git a/testing/lisp/test-org-duration.el b/testing/lisp/test-org-duration.el index f44d47d53..981c00f03 100644 --- a/testing/lisp/test-org-duration.el +++ b/testing/lisp/test-org-duration.el @@ -121,7 +121,15 @@ (should (equal "0.5min" (let ((org-duration-format '(("h" . nil) ("min" . nil) (special . 1)))) - (org-duration-from-minutes 0.5))))) + (org-duration-from-minutes 0.5)))) + ;; Handle compact form. + (should (equal "0h50min" + (let ((org-duration-format '(("h" . t) ("min" . t) compact))) + (org-duration-from-minutes 50)))) + (should (equal "1d0:10" + (let ((org-duration-format + '(("d" . nil) (special . h:mm) compact))) + (org-duration-from-minutes (+ (* 24 60) 10)))))) (ert-deftest test-org-duration/p () "Test `org-duration-p' specifications." @@ -130,7 +138,9 @@ (should (org-duration-p "123:12")) (should (org-duration-p "1:23:45")) (should (org-duration-p "3d 3h 4min")) + (should (org-duration-p "3d3h4min")) (should (org-duration-p "3d 13:35")) + (should (org-duration-p "3d13:35")) (should (org-duration-p "2.35h")) ;; Handle custom units, but return nil for unknown units. (should-not (org-duration-p "1minute")) @@ -146,7 +156,7 @@ (should-not (org-duration-p "3::12")) (should-not (org-duration-p "3:2")) (should-not (org-duration-p "3:12:4")) - ;; Return nil in mixed mode if H:MM:SS part is not last. + ;; Return nil in mixed mode if H:MM:SS part is not the last one. (should-not (org-duration-p "3d 13:35 13h"))) (ert-deftest test-org-duration/h:mm-only-p ()