0

Problem

I have some org-mode tables with a column containing the duration (or length) of musical tracks:

|---+---------------------------------+-----------------------------------+----------|
| # | Artist                          | Title                             | Duration |
|---+---------------------------------+-----------------------------------+----------|
| 1 | Lorem ipsum dolor sit amet      | consectetuer adipiscing elit      |     7:32 |
| 2 | Donec hendrerit tempor tellus   | Donec pretium posuere tellus      |  1:09:27 |
| 3 | Proin quam nisl, tincidunt et   | mattis eget, convallis nec, purus |    10:15 |
| 4 | Cum sociis natoque penatibus et | magnis dis parturient montes      |     4:25 |
|---+---------------------------------+-----------------------------------+----------|

I'd like to convert the durations to start times. The result of the converstion should look like this:

|---+---------------------------------+-----------------------------------+------------|
| # | Artist                          | Title                             | Start time |
|---+---------------------------------+-----------------------------------+------------|
| 1 | Lorem ipsum dolor sit amet      | consectetuer adipiscing elit      |       0:00 |
| 2 | Donec hendrerit tempor tellus   | Donec pretium posuere tellus      |       7:32 |
| 3 | Proin quam nisl, tincidunt et   | mattis eget, convallis nec, purus |    1:16:59 |
| 4 | Cum sociis natoque penatibus et | magnis dis parturient montes      |    1:27:14 |
|---+---------------------------------+-----------------------------------+------------|

Note

In the duration input

  • Durations will be given in terms of hours, minutes, and seconds, separated by colons
  • leading zeroes may or may not be present (ie. a duration may look like "09:25" or "9:25")
  • the hours field may or may not be present (ie. a duration might look like "9:25" or "1:09:25" or even "0:09:25")

In the start time output

  • minutes and seconds should always be present, even if they're zero (ex: "0:00" and "0:32" are both correct)
  • if the left-most field is not a minute field and is 0, it and the colon following it should be omitted (ie. output "12:24", not "0:12:24")
  • a field should be padded with leading zeroes if there is another field to its left (ie. these are all correct: "1:09:26", "1:00:26", "1:00:00", "9:26", and these are wrong: "0:09:26", "00:09:26")

1 Answer 1

1

The following provides my-durations-to-start-times to convert durations to start-times, e.g.,

(my-durations-to-start-times '("7:32" "1:09:27" "10:15" "4:25"))
;; => ("0:00" "7:32" "1:16:59" "1:27:14")
(defun my-duration-to-seconds (duration)
  "Return number of seconds of DURATION string."
  (let ((seconds 0)
        (unit 1))
    (dolist (n (nreverse (mapcar #'string-to-number (split-string duration ":"))))
      (setq seconds (+ seconds (* n unit)))
      (setq unit (* unit 60)))
    seconds))

(cl-assert (= (my-duration-to-seconds "04") 4))
(cl-assert (= (my-duration-to-seconds "7:32") (+ (* 7 60) 32)))
(cl-assert (= (my-duration-to-seconds "1:09:27") 4167))

(defun my-format-seconds (seconds)
  (let* ((h (floor seconds 3600))
         (seconds (- seconds (* 3600 h)))
         (m (floor seconds 60))
         (seconds (- seconds (* 60 m)))
         (s seconds))
    (if (zerop h)
        (format "%d:%02d" m s)
      (format "%d:%02d:%02d" h m s))))

(cl-assert (string= (my-format-seconds (my-duration-to-seconds "0")) "0:00"))
(cl-assert (string= (my-format-seconds (my-duration-to-seconds "00:09:26")) "9:26"))

(defun my-durations-to-start-times (durations)
  (let ((start 0))
    (mapcar (lambda (duration)
              (prog1 (my-format-seconds start)
                (setq start (+ start (my-duration-to-seconds duration)))))
            durations)))

(cl-assert (equal (my-durations-to-start-times '("7:32" "1:09:27" "10:15" "4:25"))
                  '("0:00" "7:32" "1:16:59" "1:27:14")))

Use my-durations-to-start-times in org-mode table formula

|---+----------+------------|
|   | Duration | Start time |
|---+----------+------------|
| 1 |     7:32 |       0:00 |
| 2 |  1:09:27 |       7:32 |
| 3 |    10:15 |    1:16:59 |
| 4 |     4:25 |    1:27:14 |
|---+----------+------------|
#+TBLFM: $>='(nth (1- (string-to-number $1)) (my-durations-to-start-times '(@2$>>..@5$>>)))
3
  • Fantastic. Thank you! Not to look a gift horse in the mouth, but is it possible to just replace the durations in the Duration column with the respective start times rather than putting the start times in a separate column?
    – izkon
    Dec 4, 2018 at 7:41
  • 1
    @izkon I don't know. I don't have much experience with org-mode table formula. The above formula needs to call my-durations-to-start-times 4 times, instead of just 1 time, it assumes the Duration column doesn't change, thus simply replacing $>= with $>>= doesn't work.
    – xuchunyang
    Dec 4, 2018 at 8:09
  • To add to @xuchunyang's comment, it is better to construct a table using a source block, instead of trying to use a formula to modify the table in place. The latter method is fraught with lurking tripping hazards and usually leads to disaster.
    – NickD
    Dec 21, 2021 at 15:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.