瀏覽代碼

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.
Nicolas Goaziou 5 年之前
父節點
當前提交
2fde90aa2e
共有 3 個文件被更改,包括 45 次插入22 次删除
  1. 6 0
      etc/ORG-NEWS
  2. 27 20
      lisp/org-duration.el
  3. 12 2
      testing/lisp/test-org-duration.el

+ 6 - 0
etc/ORG-NEWS

@@ -42,6 +42,12 @@ possible via column view value edit or with =<C-c C-q>=.
 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

+ 27 - 20
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
 	    ""))))

+ 12 - 2
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 ()