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
This commit is contained in:
Ihor Radchenko 2024-05-17 14:43:04 +03:00
parent 288c7069c4
commit ee58259bc7
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
1 changed files with 104 additions and 103 deletions

View File

@ -324,109 +324,110 @@ When optional argument CANONICAL is non-nil, ignore
`org-duration-units' and use standard time units value. `org-duration-units' and use standard time units value.
Raise an error if expected format is unknown." Raise an error if expected format is unknown."
(pcase (or fmt org-duration-format) (if (< minutes 0) (concat "-" (org-duration-from-minutes (abs minutes) fmt canonical))
(`h:mm (pcase (or fmt org-duration-format)
(format "%d:%02d" (/ minutes 60) (mod minutes 60))) (`h:mm
(`h:mm:ss (format "%d:%02d" (/ minutes 60) (mod minutes 60)))
(let* ((whole-minutes (floor minutes)) (`h:mm:ss
(seconds (mod (* 60 minutes) 60))) (let* ((whole-minutes (floor minutes))
(format "%s:%02d" (seconds (mod (* 60 minutes) 60)))
(org-duration-from-minutes whole-minutes 'h:mm) (format "%s:%02d"
seconds))) (org-duration-from-minutes whole-minutes 'h:mm)
((pred atom) (error "Invalid duration format specification: %S" fmt)) seconds)))
;; Mixed format. Call recursively the function on both parts. ((pred atom) (error "Invalid duration format specification: %S" fmt))
((and duration-format ;; Mixed format. Call recursively the function on both parts.
(let `(special . ,(and mode (or `h:mm:ss `h:mm))) ((and duration-format
(assq 'special duration-format))) (let `(special . ,(and mode (or `h:mm:ss `h:mm)))
(let* ((truncated-format (assq 'special duration-format)))
;; Remove "special" mode from duration format in order to (let* ((truncated-format
;; recurse properly. Also remove units smaller or equal ;; Remove "special" mode from duration format in order to
;; to an hour since H:MM part takes care of it. ;; recurse properly. Also remove units smaller or equal
(cl-remove-if-not ;; to an hour since H:MM part takes care of it.
(lambda (pair) (cl-remove-if-not
(pcase pair (lambda (pair)
(`(,(and unit (pred stringp)) . ,_) (pcase pair
(> (org-duration--modifier unit canonical) 60)) (`(,(and unit (pred stringp)) . ,_)
(_ nil))) (> (org-duration--modifier unit canonical) 60))
duration-format)) (_ nil)))
(min-modifier ;smallest modifier above hour duration-format))
(and truncated-format (min-modifier ;smallest modifier above hour
(apply #'min (and truncated-format
(mapcar (lambda (p) (apply #'min
(org-duration--modifier (car p) canonical)) (mapcar (lambda (p)
truncated-format))))) (org-duration--modifier (car p) canonical))
(if (or (null min-modifier) (< minutes min-modifier)) truncated-format)))))
;; There is not unit above the hour or the smallest unit (if (or (null min-modifier) (< minutes min-modifier))
;; above the hour is too large for the number of minutes we ;; There is not unit above the hour or the smallest unit
;; need to represent. Use H:MM or H:MM:SS syntax. ;; above the hour is too large for the number of minutes we
(org-duration-from-minutes minutes mode canonical) ;; need to represent. Use H:MM or H:MM:SS syntax.
;; Represent minutes above hour using provided units and H:MM (org-duration-from-minutes minutes mode canonical)
;; or H:MM:SS below. ;; Represent minutes above hour using provided units and H:MM
(let* ((units-part (* min-modifier (/ (floor minutes) min-modifier))) ;; or H:MM:SS below.
(minutes-part (- minutes units-part)) (let* ((units-part (* min-modifier (/ (floor minutes) min-modifier)))
(compact (memq 'compact duration-format))) (minutes-part (- minutes units-part))
(concat (compact (memq 'compact duration-format)))
(org-duration-from-minutes units-part truncated-format canonical) (concat
(and (not compact) " ") (org-duration-from-minutes units-part truncated-format canonical)
(org-duration-from-minutes minutes-part mode)))))) (and (not compact) " ")
;; Units format. (org-duration-from-minutes minutes-part mode))))))
(duration-format ;; Units format.
(let* ((fractional (duration-format
(let ((digits (cdr (assq 'special duration-format)))) (let* ((fractional
(and digits (let ((digits (cdr (assq 'special duration-format))))
(or (wholenump digits) (and digits
(error "Unknown formatting directive: %S" digits)) (or (wholenump digits)
(format "%%.%df" digits)))) (error "Unknown formatting directive: %S" digits))
(selected-units (format "%%.%df" digits))))
(sort (cl-remove-if (selected-units
;; Ignore special format cells and compact option. (sort (cl-remove-if
(lambda (pair) ;; Ignore special format cells and compact option.
(pcase pair (lambda (pair)
((or `compact `(special . ,_)) t) (pcase pair
(_ nil))) ((or `compact `(special . ,_)) t)
duration-format) (_ nil)))
(lambda (a b) duration-format)
(> (org-duration--modifier (car a) canonical) (lambda (a b)
(org-duration--modifier (car b) canonical))))) (> (org-duration--modifier (car a) canonical)
(separator (if (memq 'compact duration-format) "" " "))) (org-duration--modifier (car b) canonical)))))
(cond (separator (if (memq 'compact duration-format) "" " ")))
;; Fractional duration: use first unit that is either required (cond
;; or smaller than MINUTES. ;; Fractional duration: use first unit that is either required
(fractional ;; or smaller than MINUTES.
(let* ((unit (car (fractional
(or (cl-find-if (let* ((unit (car
(lambda (pair) (or (cl-find-if
(pcase pair (lambda (pair)
(`(,u . ,req?) (pcase pair
(or req? (`(,u . ,req?)
(<= (org-duration--modifier u canonical) (or req?
minutes))))) (<= (org-duration--modifier u canonical)
selected-units) minutes)))))
;; Fall back to smallest unit. selected-units)
(org-last selected-units)))) ;; Fall back to smallest unit.
(modifier (org-duration--modifier unit canonical))) (org-last selected-units))))
(concat (format fractional (/ (float minutes) modifier)) unit))) (modifier (org-duration--modifier unit canonical)))
;; Otherwise build duration string according to available (concat (format fractional (/ (float minutes) modifier)) unit)))
;; units. ;; Otherwise build duration string according to available
((org-string-nw-p ;; units.
(org-trim ((org-string-nw-p
(mapconcat (org-trim
(lambda (units) (mapconcat
(pcase-let* ((`(,unit . ,required?) units) (lambda (units)
(modifier (org-duration--modifier unit canonical))) (pcase-let* ((`(,unit . ,required?) units)
(cond ((<= modifier minutes) (modifier (org-duration--modifier unit canonical)))
(let ((value (floor minutes modifier))) (cond ((<= modifier minutes)
(cl-decf minutes (* value modifier)) (let ((value (floor minutes modifier)))
(format "%s%d%s" separator value unit))) (cl-decf minutes (* value modifier))
(required? (concat separator "0" unit)) (format "%s%d%s" separator value unit)))
(t "")))) (required? (concat separator "0" unit))
selected-units (t ""))))
"")))) selected-units
;; No unit can properly represent MINUTES. Use the smallest ""))))
;; one anyway. ;; No unit can properly represent MINUTES. Use the smallest
(t ;; one anyway.
(pcase-let ((`((,unit . ,_)) (last selected-units))) (t
(concat "0" unit)))))))) (pcase-let ((`((,unit . ,_)) (last selected-units)))
(concat "0" unit)))))))))
;;;###autoload ;;;###autoload
(defun org-duration-h:mm-only-p (times) (defun org-duration-h:mm-only-p (times)