3

I'm drowning in parenthesis here. I want to write a function which returns in a string the time and the effort estimate of the current clocked task.

I first looked in org-mode checking I wasn't re-doing the wheel, I found org-clock-get-clock-string which does what I want but it appends things I don't need making the string unusable for what I want. I took a look at the source code and tried to adapt it to do what I want, however, I fail miserably, I did this mess:

(defun shackra/task-clocked-time ()
  "Return a string with the clocked time and effort, if any"
  (let* ((clocked-time (org-clock-get-clocked-time))
         (h (floor clocked-time 60))
         (m (- clocked-time (* 60 h))))
    (if org-clock-effort
        (let* ((effort-in-minutes
                (org-duration-string-to-minutes org-clock-effort))
               (effort-h (floor effort-in-minutes 60))
               (effort-m (- effort-in-minutes (* effort-h 60)))
               (work-done-str (format org-time-clocksum-format h m))
               (effort-str (format org-time-clocksum-format effort-h effort-m))
               (clockstr (concat  " [%s/" effort-str
                                  "] (" (replace-regexp-in-string "%" "%%" org-clock-heading) ")"))))
      (format clockstr work-done-str))
    (format
     (concat "[" org-time-clocksum-format " (%s)]")
     h m org-clock-heading)))

but executing the function for testing yells this error:

Debugger entered--Lisp error: (wrong-type-argument stringp (:days "%dd " :hours "%d" :require-hours t :minutes ":%02d" :require-minutes t))
  format((:days "%dd " :hours "%d" :require-hours t :minutes ":%02d" :require-minutes t) 0 12)
  (let* ((effort-in-minutes (org-duration-string-to-minutes org-clock-effort)) (effort-h (floor effort-in-minutes 60)) (effort-m (- effort-in-minutes (* effort-h 60)))) (format org-time-clocksum-format h m))
  (if org-clock-effort (let* ((effort-in-minutes (org-duration-string-to-minutes org-clock-effort)) (effort-h (floor effort-in-minutes 60)) (effort-m (- effort-in-minutes (* effort-h 60)))) (format org-time-clocksum-format h m)))
  (let* ((clocked-time (org-clock-get-clocked-time)) (h (floor clocked-time 60)) (m (- clocked-time (* 60 h)))) (if org-clock-effort (let* ((effort-in-minutes (org-duration-string-to-minutes org-clock-effort)) (effort-h (floor effort-in-minutes 60)) (effort-m (- effort-in-minutes (* effort-h 60)))) (format org-time-clocksum-format h m))))
  shackra/task-clocked-time()
  eval((shackra/task-clocked-time) nil)
  eval-last-sexp-1(nil)
  eval-last-sexp(nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)

To be honest, I don't know what I'm doing and I neither know how to read the code of org-clock-get-clock-string (the lacks of documentation in that code kills me)

  • 1
    That error says: value of org-time-clocksum-format isn't string but alist, and format needs string as its first argument. – xuchunyang Feb 20 '16 at 4:36
2
+200

You can use org-minutes-to-clocksum-string to format your time.

Here is modified version of the code.

(defun shackra/task-clocked-time ()
  "Return a string with the clocked time and effort, if any"
  (interactive)
  (let* ((clocked-time (org-clock-get-clocked-time))
         (h (floor clocked-time 60))
         (m (- clocked-time (* 60 h)))
         (work-done-str (org-minutes-to-clocksum-string m)))
    (if org-clock-effort
        (let* ((effort-in-minutes
                (org-duration-string-to-minutes org-clock-effort))
               (effort-h (floor effort-in-minutes 60))
               (effort-m (- effort-in-minutes (* effort-h 60)))
               (effort-str (org-minutes-to-clocksum-string effort-m)))
          (format "[%s/%s (%s)" work-done-str effort-str org-clock-heading))
      (format "[%s (%s)]" work-done-str org-clock-heading))))

org-clock-get-clock-string uses org-time-clocksum-format to format your time the way you want. As the doc string says it can either be:

  1. a simple format string like "%d hours and %d minutes", or
  2. a plist like (:hours "%d hours and " :minutes "%d minutes" :require-minutes t).

Both generates "1 hours and 10 minutes" if you evaluate (org-clock-get-clock-string 70).

The format string used when creating CLOCKSUM lines. This is also used when Org mode generates a time duration.

The value can be a single format string containing two %-sequences, which will be filled with the number of hours and minutes in that order.

Alternatively, the value can be a plist associating any of the keys :years, :months, :weeks, :days, :hours or :minutes with format strings. The time duration is formatted using only the time components that are needed and concatenating the results. If a time unit in absent, it falls back to the next smallest unit.

The keys :require-years, :require-months, :require-days, :require-weeks, :require-hours, :require-minutes are also meaningful. A non-nil value for these keys indicates that the corresponding time component should always be included, even if its value is 0.

For example,

(:days "%dd" :hours "%d" :require-hours t :minutes ":%02d" :require-minutes t)

means durations longer than a day will be expressed in days, hours and minutes, and durations less than a day will always be expressed in hours and minutes (even for durations less than an hour).

The value

(:days "%dd" :minutes "%dm")

means durations longer than a day will be expressed in days and minutes, and durations less than a day will be expressed entirely in minutes (even for durations longer than an hour).


Here is line by line explanation of org-clock-get-clock-string (comment removed).

(defun org-clock-get-clock-string ()

define the function named org-clock-get-clock-string

  (let ((clocked-time (org-clock-get-clocked-time)))

let bind (create a local variable) clocked-time initialized to the current clocked time in minutes.

    (if org-clock-effort

if the clocked item has an effort set, let bind 4 variables:

        (let* ((effort-in-minutes
                (org-duration-string-to-minutes org-clock-effort))

first, initialize effort-in-minutes as effort string to minutes.

               (work-done-str

Second, work-done-str. This is the time currently spent on the work. We want to put color on it, so

                (org-propertize

use org-propertize to do the work.

                 (org-minutes-to-clocksum-string clocked-time)

convert the clocked-time to string,

                 'face (if (and org-clock-task-overrun (not org-clock-task-overrun-text))
                           'org-mode-line-clock-overrun 'org-mode-line-clock)))

if it's been overrun, but not org-clock-task-overrun-text is set, put overrun face. Put mode line face, otherwise. That tells you that if you don't want to have different face on overran work, set org-clock-task-overrun-text to whatever it suits.

               (effort-str (org-minutes-to-clocksum-string effort-in-minutes))

Third, convert effort-in-minutes to string.

               (clockstr (org-propertize

Fourth, create string format with mode line face.

                          (concat  " [%s/" effort-str
                                   "] (" (replace-regexp-in-string "%" "%%" org-clock-heading) ")")
                          'face 'org-mode-line-clock)))

replace the % to %% in the heading because we are creating format string. % will mess up the format so convert it to literal %, which is double % %%.

          (format clockstr work-done-str))

do the actual format for the work with effort set.

      (org-propertize (concat "[" (org-minutes-to-clocksum-string clocked-time)
                              (format " (%s)" org-clock-heading) "]")
                      'face 'org-mode-line-clock))))

or without.

  • I wasn't sure that the format of the output string is what you intended for. Let me know if I misunderstood. – Yasushi Shoji Feb 21 '16 at 5:43
  • @yasusha-shoji It is fine. I still wonder how I'm suppose to use org-time-clocksum-format if I ever want to, though. – shackra Feb 21 '16 at 18:46
  • @shackra have your read the doc string? It's too long to quote here but basically you put format components in the list. That is, if you want time to be formatted with only minutes, (:minutes "%dm") will do. – Yasushi Shoji Feb 22 '16 at 0:00
2

Maybe, the following is sufficient for your task. You should be able to adapt it to your purpose since it is more concise than the original version.

(defun shackra/task-clocked-time ()
  "Return a string with the clocked time and effort, if any"
  (apply 'concat
     (org-minutes-to-clocksum-string (org-clock-get-clocked-time))
     (when org-clock-effort
         (list " [" (org-minutes-to-clocksum-string (org-duration-string-to-minutes org-clock-effort))"]")
       )))

It looks like you have used org-clock-get-clock-string from an older version of org-mode than you have running: You have (format org-time-clocksum-format h m) in shackra/task-clocked-time. This requires that org-clock-get-clock-string is a format string for the format-function. But, the error message says that org-time-clocksum-format is actually an alist.

Note that the actual tree version of org-clock-get-clock-string is there:

http://orgmode.org/cgit.cgi/org-mode.git/tree/lisp/org-clock.el#n662

The formatting of the duration string in dependence of org-time-clocksum-format is done in org-minutes-to-clocksum-string. This is a lengthy function. You can have a peep at it by using emacs' help system. You get the help for org-minutes-to-clocksum-string the same way as for any emacs-lisp function by typing C-h f, typing in the function name org-minutes-to-clocksum-string, and RET.

In emacs-version 25.1.50 with org-mode 8.3.2 the doc-string of org-time-clocksum-format says:

The format string used when creating CLOCKSUM lines.
This is also used when Org mode generates a time duration.

The value can be a single format string containing two
%-sequences, which will be filled with the number of hours and
minutes in that order.

Alternatively, the value can be a plist associating any of the
keys :years, :months, :weeks, :days, :hours or :minutes with
format strings.  The time duration is formatted using only the
time components that are needed and concatenating the results.
If a time unit in absent, it falls back to the next smallest
unit.

Your code only handles the first case and not the alternative. That is the main cause of the error in your case.

Further note: Emacs is a self-documenting editor. That is the reason why I do not start here to explain the code line-by-line. For each function in the code you can place point on the function name and use C-h f to get the corresponding help. Futhermore, there is a nice Lisp introduction in the info files (you get the info directory via C-h i). In the info directory press m for 'menu item' and input Emacs Lisp Intro RET to get the lisp introduction. There is also a complete lisp manual in the menu item Elisp.

  • Do the edit and make the fancy things without removing the simple code you wrote, as some people may just need something like that, me for instance wants to also know how to use a plist for formatting strings, their advantages and disadvantages, etc. – shackra Feb 21 '16 at 4:34
  • @shackra The correction was actually very simple and fits nicely in the simple code. Therefore, I 've updated the simple code. Furthermore, I found what your version problem with org-mode is. You picked an old git-tag from your internet search. I've given you the link to the current tree version of the function in the answer. Furthermore, I added all the pointers to the available emacs lisp help in the answer. From my perspective it does not make much sense to explain the code line-by-line. It is better to help you to get the already written excellent existing help. – Tobias Feb 21 '16 at 7:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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