3

Is there a way to check if an org header has overlapping clock entries, as here:

* Header with overlapping clock entries
:LOGBOOK:
CLOCK: [2018-05-08 Tue 01:12]--[2018-05-08 Tue 02:00] =>  0:48
CLOCK: [2018-05-08 Tue 00:01]--[2018-05-08 Tue 01:12] =>  1:11
CLOCK: [2018-05-08 Tue 00:00]--[2018-05-08 Tue 01:10] =>  1:10
:END:

The last two clock entries overlap in time.

(My ultimate goal is to force the overlapping clocks to be non-overlapping, such that all of the time logged [including overlapped time] is preserved. So e.g. if two duplicate 1-hour clock entries are recorded, then the total time logged should be 2 hours, with the entries modified/pushed around so that they are forced to be non-overlapping).

6
  • I was hoping that M-x org-lint would check for overlapping clocks, but unfortunately it does not.
    – NickD
    Commented Apr 23, 2022 at 12:42
  • Well, org checks for overlapping clocks when clocking in. So I am wondering how these overlapping clocks were obtained in the first place, as it might be easier/wiser to prevent getting overlapping clocks under a single heading. Could you enlighten us, @Anaphory (or @George)? Commented Jan 16, 2023 at 14:09
  • In my case, I use org for time tracking. Sometimes, I forget clocking in to a particular project and have to adjust my clock times manually, and inevitably, sometimes I mess that up and end up with 8 hours of work time clocked before lunch… (So mine are not under the same immediate heading, but under the same top-level heading, but I did not want to near-duplicate an already unanswered question.)
    – Anaphory
    Commented Jan 17, 2023 at 10:57
  • @dalanicolai Can you clarify what you mean by 'org checks for overlapping clocks when clocking in?' Org did not alert me nor make any changes to my clock entries when conflicting clocks were logged already. It also did not mind if I clocked in at a time that would cause a conflict (Like where I clocked out, then adjusted my clock out time to be 10 minutes ahead. If I tried to clock in before those 10 minutes, there were no problems doing that as it clocks in as expected.)
    – Malle Yeno
    Commented Jan 17, 2023 at 22:06
  • @MalleYeno Well, when under some headline, something is 'clocking' then when trying to clock in again under the same headline, org messages "Clock continues in 'headline title'". When clocking in under a different headline (say 'headline 2'), then org automatically stops the clocking under the headline that is 'clocking' while cloking in, and start under clocking under 'headline 2'. I am not a regular clocking user, but I just noticed this while checking. If it does not work like that at your site, then maybe we use different org-versions (my version is 9.6.1). Commented Jan 17, 2023 at 22:19

1 Answer 1

0

Maybe there is an easier way to do this, but this is what I came up with:

(defun org-entry-overlapping-clocks-p ()
  "Return non-nil when current entry has overlapping clocks.
The value returned in this case is a plist holding the start time,
end time, buffer, and position of the first (time-wise) overlapping
clock."
  (org-back-to-heading t)
  (let* ((clock-re (concat "^[ \t]*" org-clock-string))
         ;; could be improved by checking whether in correct drawer too
         (end (save-excursion (outline-next-heading) (point)))
         clocks)
    (while (re-search-forward clock-re end t)
      (let* ((raw-value (plist-get
                         (nth 1 (plist-get
                                 (nth 1 (org-element-at-point))
                                 :value))
                         :raw-value))
             (start-end (string-split raw-value "--"))
             (tstart (float-time (org-time-string-to-time (car start-end))))
             (tend (float-time
                    (if (nth 1 start-end)
                        (org-time-string-to-time (nth 1 start-end))
                      ;; use current time as end time for running clocks
                      (org-current-time)))))
        (push (list :tstart tstart :tend tend
                    :buffer (current-buffer) :char (point))
              clocks)))
    (setq clocks (sort clocks (lambda (x y) (< (plist-get x :tstart)
                                               (plist-get y :tstart)))))
    (catch 'overlap
      (dotimes (i (1- (length clocks)))
        (when (> (plist-get (nth i clocks) :tend)
                 (plist-get (nth (1+ i) clocks) :tstart))   
          (throw 'overlap (nth (1+ i) clocks)))))))
(defun org-find-overlapping-clock (scope)
  "Go to first overlapping clock found by `org-map-entries'.
SCOPE is as in `org-map-entries'."
  (interactive
   (list (intern (completing-read
                  "Scope: "
                  '("nil" "tree" "region" "region-start-level" "file"
                    "file-with-archives" "agenda" "agenda-with-archives")))))
  (let ((overlap (catch 'overlap 
                   (org-map-entries
                    (lambda ()
                      (let ((overlap (org-entry-overlapping-clocks-p)))
                        (when overlap
                          (throw 'overlap overlap))))
                    nil scope))))
    (if (car overlap)
        (progn
          (message "Overlapping clock found at position %s."
                   (plist-get overlap :char))
          (switch-to-buffer (plist-get overlap :buffer))
          (goto-char (plist-get overlap :char)))
      (message "No overlapping clock found."))))

Using the times and positions collected it is possible to automatically adjust the clock times so that overlap is avoided while retaining the total time logged, but that requires even more coding. org-clock-sum does double-count the overlapping clock times anyways as OP wants.

Your Answer

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

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