summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIhor Radchenko <yantar92@posteo.net>2024-05-17 14:43:04 +0300
committerIhor Radchenko <yantar92@posteo.net>2024-05-17 14:43:04 +0300
commitee58259bc71cd98708b2be92727fbc98a615c013 (patch)
tree460340056f68df78b1a9802e8cea1a7156f54747
parent288c7069c4c6946628f4896870a5ece25ff7dad2 (diff)
org-duration-from-minutes: Accept negative durations
* lisp/org-duration.el (org-duration-from-minutes): Allow MINUTES argument to be negative. Reported-by: Raffael Stocker <r.stocker@mnet-mail.de> Link: https://orgmode.org/list/yplmzfsrqjw6.fsf@mnet-mail.de
-rw-r--r--lisp/org-duration.el207
1 files changed, 104 insertions, 103 deletions
diff --git a/lisp/org-duration.el b/lisp/org-duration.el
index 844a3b6..e7c49fe 100644
--- a/lisp/org-duration.el
+++ b/lisp/org-duration.el
@@ -324,109 +324,110 @@ When optional argument CANONICAL is non-nil, ignore
`org-duration-units' and use standard time units value.
Raise an error if expected format is unknown."
- (pcase (or fmt org-duration-format)
- (`h:mm
- (format "%d:%02d" (/ minutes 60) (mod minutes 60)))
- (`h:mm:ss
- (let* ((whole-minutes (floor minutes))
- (seconds (mod (* 60 minutes) 60)))
- (format "%s:%02d"
- (org-duration-from-minutes whole-minutes 'h:mm)
- seconds)))
- ((pred atom) (error "Invalid duration format specification: %S" fmt))
- ;; Mixed format. Call recursively the function on both parts.
- ((and duration-format
- (let `(special . ,(and mode (or `h:mm:ss `h:mm)))
- (assq 'special duration-format)))
- (let* ((truncated-format
- ;; Remove "special" mode from duration format in order to
- ;; recurse properly. Also remove units smaller or equal
- ;; to an hour since H:MM part takes care of it.
- (cl-remove-if-not
- (lambda (pair)
- (pcase pair
- (`(,(and unit (pred stringp)) . ,_)
- (> (org-duration--modifier unit canonical) 60))
- (_ nil)))
- duration-format))
- (min-modifier ;smallest modifier above hour
- (and truncated-format
- (apply #'min
- (mapcar (lambda (p)
- (org-duration--modifier (car p) canonical))
- truncated-format)))))
- (if (or (null min-modifier) (< minutes min-modifier))
- ;; There is not unit above the hour or the smallest unit
- ;; above the hour is too large for the number of minutes we
- ;; need to represent. Use H:MM or H:MM:SS syntax.
- (org-duration-from-minutes minutes mode canonical)
- ;; 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))
- (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
- (let* ((fractional
- (let ((digits (cdr (assq 'special duration-format))))
- (and digits
- (or (wholenump digits)
- (error "Unknown formatting directive: %S" digits))
- (format "%%.%df" digits))))
- (selected-units
- (sort (cl-remove-if
- ;; 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)))))
- (separator (if (memq 'compact duration-format) "" " ")))
- (cond
- ;; Fractional duration: use first unit that is either required
- ;; or smaller than MINUTES.
- (fractional
- (let* ((unit (car
- (or (cl-find-if
- (lambda (pair)
- (pcase pair
- (`(,u . ,req?)
- (or req?
- (<= (org-duration--modifier u canonical)
- minutes)))))
- selected-units)
- ;; Fall back to smallest unit.
- (org-last selected-units))))
- (modifier (org-duration--modifier unit canonical)))
- (concat (format fractional (/ (float minutes) modifier)) unit)))
- ;; Otherwise build duration string according to available
- ;; units.
- ((org-string-nw-p
- (org-trim
- (mapconcat
- (lambda (units)
- (pcase-let* ((`(,unit . ,required?) units)
- (modifier (org-duration--modifier unit canonical)))
- (cond ((<= modifier minutes)
- (let ((value (floor minutes modifier)))
- (cl-decf minutes (* value modifier))
- (format "%s%d%s" separator value unit)))
- (required? (concat separator "0" unit))
- (t ""))))
- selected-units
- ""))))
- ;; No unit can properly represent MINUTES. Use the smallest
- ;; one anyway.
- (t
- (pcase-let ((`((,unit . ,_)) (last selected-units)))
- (concat "0" unit))))))))
+ (if (< minutes 0) (concat "-" (org-duration-from-minutes (abs minutes) fmt canonical))
+ (pcase (or fmt org-duration-format)
+ (`h:mm
+ (format "%d:%02d" (/ minutes 60) (mod minutes 60)))
+ (`h:mm:ss
+ (let* ((whole-minutes (floor minutes))
+ (seconds (mod (* 60 minutes) 60)))
+ (format "%s:%02d"
+ (org-duration-from-minutes whole-minutes 'h:mm)
+ seconds)))
+ ((pred atom) (error "Invalid duration format specification: %S" fmt))
+ ;; Mixed format. Call recursively the function on both parts.
+ ((and duration-format
+ (let `(special . ,(and mode (or `h:mm:ss `h:mm)))
+ (assq 'special duration-format)))
+ (let* ((truncated-format
+ ;; Remove "special" mode from duration format in order to
+ ;; recurse properly. Also remove units smaller or equal
+ ;; to an hour since H:MM part takes care of it.
+ (cl-remove-if-not
+ (lambda (pair)
+ (pcase pair
+ (`(,(and unit (pred stringp)) . ,_)
+ (> (org-duration--modifier unit canonical) 60))
+ (_ nil)))
+ duration-format))
+ (min-modifier ;smallest modifier above hour
+ (and truncated-format
+ (apply #'min
+ (mapcar (lambda (p)
+ (org-duration--modifier (car p) canonical))
+ truncated-format)))))
+ (if (or (null min-modifier) (< minutes min-modifier))
+ ;; There is not unit above the hour or the smallest unit
+ ;; above the hour is too large for the number of minutes we
+ ;; need to represent. Use H:MM or H:MM:SS syntax.
+ (org-duration-from-minutes minutes mode canonical)
+ ;; 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))
+ (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
+ (let* ((fractional
+ (let ((digits (cdr (assq 'special duration-format))))
+ (and digits
+ (or (wholenump digits)
+ (error "Unknown formatting directive: %S" digits))
+ (format "%%.%df" digits))))
+ (selected-units
+ (sort (cl-remove-if
+ ;; 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)))))
+ (separator (if (memq 'compact duration-format) "" " ")))
+ (cond
+ ;; Fractional duration: use first unit that is either required
+ ;; or smaller than MINUTES.
+ (fractional
+ (let* ((unit (car
+ (or (cl-find-if
+ (lambda (pair)
+ (pcase pair
+ (`(,u . ,req?)
+ (or req?
+ (<= (org-duration--modifier u canonical)
+ minutes)))))
+ selected-units)
+ ;; Fall back to smallest unit.
+ (org-last selected-units))))
+ (modifier (org-duration--modifier unit canonical)))
+ (concat (format fractional (/ (float minutes) modifier)) unit)))
+ ;; Otherwise build duration string according to available
+ ;; units.
+ ((org-string-nw-p
+ (org-trim
+ (mapconcat
+ (lambda (units)
+ (pcase-let* ((`(,unit . ,required?) units)
+ (modifier (org-duration--modifier unit canonical)))
+ (cond ((<= modifier minutes)
+ (let ((value (floor minutes modifier)))
+ (cl-decf minutes (* value modifier))
+ (format "%s%d%s" separator value unit)))
+ (required? (concat separator "0" unit))
+ (t ""))))
+ selected-units
+ ""))))
+ ;; No unit can properly represent MINUTES. Use the smallest
+ ;; one anyway.
+ (t
+ (pcase-let ((`((,unit . ,_)) (last selected-units)))
+ (concat "0" unit)))))))))
;;;###autoload
(defun org-duration-h:mm-only-p (times)