org-duration: Read and write duration in compact form

* lisp/org-duration.el (org-duration-format): Add `compact' symbol.
(org-duration-set-regexps): Make white space between duration parts
optional
(org-duration-from-minutes): Handle `compact' symbol.
* testing/lisp/test-org-duration.el (test-org-duration/from-minutes):
(test-org-duration/p): Add tests.
This commit is contained in:
Nicolas Goaziou 2020-01-04 18:39:05 +01:00
parent 2fbbc23368
commit 2fde90aa2e
3 changed files with 45 additions and 22 deletions

View File

@ -42,6 +42,12 @@ possible via column view value edit or with =<C-c C-q>=.
See [[*~org-columns-toggle-or-columns-quit~]] See [[*~org-columns-toggle-or-columns-quit~]]
** Miscellaneous ** 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 *** Fontify whole TODO headlines
This feature is the same as ~org-fontify-done-headline~, but for TODO This feature is the same as ~org-fontify-done-headline~, but for TODO

View File

@ -28,14 +28,16 @@
;; - 3:12 ;; - 3:12
;; - 1:23:45 ;; - 1:23:45
;; - 1y 3d 3h 4min ;; - 1y 3d 3h 4min
;; - 1d3h5min
;; - 3d 13:35 ;; - 3d 13:35
;; - 2.35h ;; - 2.35h
;; ;;
;; More accurately, it consists of numbers and units, as defined in ;; More accurately, it consists of numbers and units, as defined in
;; variable `org-duration-units', separated with white spaces, and ;; variable `org-duration-units', possibly separated with white
;; a "H:MM" or "H:MM:SS" part. White spaces are tolerated between the ;; spaces, and an optional "H:MM" or "H:MM:SS" part, which always
;; number and its relative unit. Variable `org-duration-format' ;; comes last. White spaces are tolerated between the number and its
;; controls durations default representation. ;; relative unit. Variable `org-duration-format' controls durations
;; default representation.
;; ;;
;; The library provides functions allowing to convert a duration to, ;; The library provides functions allowing to convert a duration to,
;; and from, a number of minutes: `org-duration-to-minutes' and ;; 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. Units with a zero value are skipped, unless REQUIRED? is non-nil.
In that case, the unit is always used. In that case, the unit is always used.
Eventually, the list can contain one of the following special The list can also contain one of the following special entries:
entries:
(special . h:mm) (special . h:mm)
(special . h:mm:ss) (special . h:mm:ss)
@ -139,6 +140,10 @@ entries:
first one required or with a non-zero integer part. If there first one required or with a non-zero integer part. If there
is no such unit, the smallest one is used. 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, For example,
((\"d\" . nil) (\"h\" . t) (\"min\" . t)) ((\"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." than a day uses \"h\" unit instead."
:group 'org-time :group 'org-time
:group 'org-clock :group 'org-clock
:version "26.1"
:package-version '(Org . "9.1") :package-version '(Org . "9.1")
:type '(choice :type '(choice
(const :tag "Use H:MM" h:mm) (const :tag "Use H:MM" h:mm)
@ -191,7 +195,8 @@ than a day uses \"h\" unit instead."
(const h:mm)) (const h:mm))
(cons :tag "Use both units and H:MM:SS" (cons :tag "Use both units and H:MM:SS"
(const special) (const special)
(const h:mm:ss)))))) (const h:mm:ss))
(const :tag "Use compact form" compact)))))
;;; Internal variables and functions ;;; Internal variables and functions
@ -249,13 +254,10 @@ When optional argument CANONICAL is non-nil, refer to
org-duration-units)) org-duration-units))
t))) t)))
(setq org-duration--full-re (setq org-duration--full-re
(format "\\`[ \t]*%s\\(?:[ \t]+%s\\)*[ \t]*\\'" (format "\\`\\(?:[ \t]*%s\\)+[ \t]*\\'" org-duration--unit-re))
org-duration--unit-re
org-duration--unit-re))
(setq org-duration--mixed-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]*\\'" \\(?2:[0-9]+\\(?::[0-9][0-9]\\)\\{1,2\\}\\)[ \t]*\\'"
org-duration--unit-re
org-duration--unit-re))) org-duration--unit-re)))
;;;###autoload ;;;###autoload
@ -353,10 +355,11 @@ Raise an error if expected format is unknown."
;; Represent minutes above hour using provided units and H:MM ;; Represent minutes above hour using provided units and H:MM
;; or H:MM:SS below. ;; or H:MM:SS below.
(let* ((units-part (* min-modifier (/ (floor minutes) min-modifier))) (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 (concat
(org-duration-from-minutes units-part truncated-format canonical) (org-duration-from-minutes units-part truncated-format canonical)
" " (and (not compact) " ")
(org-duration-from-minutes minutes-part mode)))))) (org-duration-from-minutes minutes-part mode))))))
;; Units format. ;; Units format.
(duration-format (duration-format
@ -368,12 +371,16 @@ Raise an error if expected format is unknown."
(format "%%.%df" digits)))) (format "%%.%df" digits))))
(selected-units (selected-units
(sort (cl-remove-if (sort (cl-remove-if
;; Ignore special format cells. ;; Ignore special format cells and compact option.
(lambda (pair) (pcase pair (`(special . ,_) t) (_ nil))) (lambda (pair)
(pcase pair
((or `compact `(special . ,_)) t)
(_ nil)))
duration-format) duration-format)
(lambda (a b) (lambda (a b)
(> (org-duration--modifier (car a) canonical) (> (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 (cond
;; Fractional duration: use first unit that is either required ;; Fractional duration: use first unit that is either required
;; or smaller than MINUTES. ;; or smaller than MINUTES.
@ -402,8 +409,8 @@ Raise an error if expected format is unknown."
(cond ((<= modifier minutes) (cond ((<= modifier minutes)
(let ((value (floor minutes modifier))) (let ((value (floor minutes modifier)))
(cl-decf minutes (* value modifier)) (cl-decf minutes (* value modifier))
(format " %d%s" value unit))) (format "%s%d%s" separator value unit)))
(required? (concat " 0" unit)) (required? (concat separator "0" unit))
(t "")))) (t ""))))
selected-units selected-units
"")))) ""))))

View File

@ -121,7 +121,15 @@
(should (equal "0.5min" (should (equal "0.5min"
(let ((org-duration-format (let ((org-duration-format
'(("h" . nil) ("min" . nil) (special . 1)))) '(("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 () (ert-deftest test-org-duration/p ()
"Test `org-duration-p' specifications." "Test `org-duration-p' specifications."
@ -130,7 +138,9 @@
(should (org-duration-p "123:12")) (should (org-duration-p "123:12"))
(should (org-duration-p "1:23:45")) (should (org-duration-p "1:23:45"))
(should (org-duration-p "3d 3h 4min")) (should (org-duration-p "3d 3h 4min"))
(should (org-duration-p "3d3h4min"))
(should (org-duration-p "3d 13:35")) (should (org-duration-p "3d 13:35"))
(should (org-duration-p "3d13:35"))
(should (org-duration-p "2.35h")) (should (org-duration-p "2.35h"))
;; Handle custom units, but return nil for unknown units. ;; Handle custom units, but return nil for unknown units.
(should-not (org-duration-p "1minute")) (should-not (org-duration-p "1minute"))
@ -146,7 +156,7 @@
(should-not (org-duration-p "3::12")) (should-not (org-duration-p "3::12"))
(should-not (org-duration-p "3:2")) (should-not (org-duration-p "3:2"))
(should-not (org-duration-p "3:12:4")) (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"))) (should-not (org-duration-p "3d 13:35 13h")))
(ert-deftest test-org-duration/h:mm-only-p () (ert-deftest test-org-duration/h:mm-only-p ()