本文翻译自 http://doc.norang.ca/org-mode.html ,原文作者为Bernt Hansen 。由于原文较长,因此会分多篇文章来发布。转载请标记出处。
本节主要介绍org计时功能,org计时功能非常强大,可以做到对任何任务,任何时候
都能计时,通过计时可以掌握任务实际执行花多少时间,便于后续对任务评估。
1 计时
是的,我承认我非常喜欢计时功能,我对计时功能非常着迷。
工作中,所有的任务都会计时。org-mode让计时非常简单。我宁愿选择过多计时,也不希望 少用计时,并且我发现在emacs中养成计时习惯也非常简单。
通过计时,方便在一天结束回头看看在哪个任务上花的时间比较多,或者看在某个项目上花的时间 不够。同时也非常方便以后来评估一个事情花多长时间–你可以参考相似的任务来帮助你做 评估。
如果没有计时功能,很难来准确评估什么事情花多长时间。
我现在在每日工作开始或结束时候使用 punching in 以及 punching out 来启动计时。当我上班时候 我会执行punching in开始计时, 当吃午饭时执行punching out停止计时,吃完饭再punching in重新计时, 最后punching out 结束今日工作。这样punching in以及punching out之间的每一分钟都被org记录下来。
开始pounching in时,会对预定义的默认任务会启动计时,直到手动关闭计时。我发现默认的org-mode设置, 会使我在每天工作中一些时间没法记录下来(如果没有默认任务来收集关闭计时以及开始计时这段时间的话), 这儿一分钟,那儿一分钟,然后丢失的时间就被积累越来越多。比方说当你写个备忘时候,并且快速完成任务-这种 情况下时钟会被关闭。而好的备忘任务创建往往就是一分钟内完成,那么这个时间就没法收集起来。
我的计时设置工作方式如下:
- Punch in(开始计时)
- 通过 org-id 预定义一个任务,punch in时,对预定义任务进行计时,直到时钟结束
- 任务计时, 当任务进入DONE状态时候,停止计时
- 当任务完成后,自动停止计时,并且移到上层任务开始计时,或者回退到预定义的那个默认任务计时。
- 对其他需要工作任务进行计时
- punch out(停止计时)
我每天会重复对默认任务计时很多遍,但是由于现在实现了任务完成后,自动计时父任务,不需要这么来做了(因为 会自动给父任务计时). 所以我只有在punch in时候对默认任务启动一次计时。
当我 punch-in是指定一个 Project X 项目的一个任务,那么这个任务就变成默认任务,所有计时都会 记在该项目上直到我 punch out或者punch in其他任务。
我的org文件看上去如下:
todo.org:
#+begin_src org :exports src
,#+FILETAGS: PERSONAL
...
,* Tasks
,** Organization
:PROPERTIES:
:CLOCK_MODELINE_TOTAL: today
:ID: eb155a82-92b2-4f25-a3c6-0304591af2f9
:END:
...
#+end_src
当我正在做某个任务时候,只要对该任务开始计时。当停止时,计时会自动向上移动,对带有todo 关键字的父任务(如果有)开始计时,这样可以让计时器一直工作在该项目中,保证一直对项目计时。如果没有一个在todo状态父任务,那么计时器就会回到默认任务上,对默认任务计时,直到我punch out 或者手动对其他任务计时。当有突发任务发生,我会启动capture,捕获该任务,对突发任务计时, 直到执行C-c C-c关闭捕获任务。
这个功能对我来说非常好。
例如,考虑到以下的org文件:
#+begin_src org :exports src
,* TODO Project A
,** NEXT TASK 1
,** TODO TASK 2
,** TODO TASK 3
,* Tasks
,** TODO Some miscellaneous task
#+end_src
我会按照如下顺序处理该文件中的任务:
- 我通过快捷键 F9-I 开始一天工作并punch in默认任务 执行完成后会对 todo.org 文件中 Organization 中的带有默认id的任务开始计时。
- F12-SPC 来查看agenda视图 选择'TODO Some miscellaneous task' 作为下个任务,通过快捷键 I 开始计时,计时器 就会出现在该任务上。
- 当完成该任务后,通过快捷键 C-c C-t d 标记任务为完成状态。当完成该任务后,计时器会重新移回 Organization (默认)任务上上
- 当我要做 Project A 任务,对 Task 1 计时 当完成 Task 1 后,将任务标记为 DONE. 将会停止 Task 1 计时,计时器重新移到 Project A 。当需要做 Task 2 时,对它计时即可。
我在项目 Project A 以及子任务上花的总时间将会记录在 Project A 上。当我将 Project A 完成。计时器将会重新移动到默认的任务上。
1.1 计时设置
开始时,我们需要找到哪个任务默认启动计时,保证任务能持续运行。可以简单通过快捷键 F9 I 快速完成。你可以在任何地方执行。计时停止时,会将计时器移动到父任务上(如果有todo关键字) ,如果没有的话,移动到默认任务上。
当子任务标记 DONE 计时器依然工作这样可以保证这个项目计时一直进行。我可以从项目中选择下个任务 并且计时,这样也不会丢失时间。
我将计时信息,状态转换信息以及其他信息都记录在任务的 :LOGBOOK: 中。
我的org mode计时相关设置如下:
#+header: :tangle yes
#+begin_src emacs-lisp
;;
;; Resume clocking task when emacs is restarted
(org-clock-persistence-insinuate)
;;
;; Show lot of clocking history so it's easy to pick items off the C-F11 list
(setq org-clock-history-length 23)
;; Resume clocking task on clock-in if the clock is open
(setq org-clock-in-resume t)
;; Change tasks to NEXT when clocking in
(setq org-clock-in-switch-to-state 'bh/clock-in-to-next)
;; Separate drawers for clocking and logs
(setq org-drawers (quote ("PROPERTIES" "LOGBOOK")))
;; Save clock data and state changes and notes in the LOGBOOK drawer
(setq org-clock-into-drawer t)
;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration
(setq org-clock-out-remove-zero-time-clocks t)
;; Clock out when moving task to a done state
(setq org-clock-out-when-done t)
;; Save the running clock and all clock history when exiting Emacs, load it on startup
(setq org-clock-persist t)
;; Do not prompt to resume an active clock
(setq org-clock-persist-query-resume nil)
;; Enable auto clock resolution for finding open clocks
(setq org-clock-auto-clock-resolution (quote when-no-clock-is-running))
;; Include current clocking task in clock reports
(setq org-clock-report-include-clocking-task t)
(setq bh/keep-clock-running nil)
(defun bh/clock-in-to-next (kw)
"Switch a task from TODO to NEXT when clocking in.
Skips capture tasks, projects, and subprojects.
Switch projects and subprojects from NEXT back to TODO"
(when (not (and (boundp 'org-capture-mode) org-capture-mode))
(cond
((and (member (org-get-todo-state) (list "TODO"))
(bh/is-task-p))
"NEXT")
((and (member (org-get-todo-state) (list "NEXT"))
(bh/is-project-p))
"TODO"))))
(defun bh/find-project-task ()
"Move point to the parent (project) task if any"
(save-restriction
(widen)
(let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
(while (org-up-heading-safe)
(when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
(setq parent-task (point))))
(goto-char parent-task)
parent-task)))
(defun bh/punch-in (arg)
"Start continuous clocking and set the default task to the
selected task. If no task is selected set the Organization task
as the default task."
(interactive "p")
(setq bh/keep-clock-running t)
(if (equal major-mode 'org-agenda-mode)
;;
;; We're in the agenda
;;
(let* ((marker (org-get-at-bol 'org-hd-marker))
(tags (org-with-point-at marker (org-get-tags-at))))
(if (and (eq arg 4) tags)
(org-agenda-clock-in '(16))
(bh/clock-in-organization-task-as-default)))
;;
;; We are not in the agenda
;;
(save-restriction
(widen)
; Find the tags on the current task
(if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4))
(org-clock-in '(16))
(bh/clock-in-organization-task-as-default)))))
(defun bh/punch-out ()
(interactive)
(setq bh/keep-clock-running nil)
(when (org-clock-is-active)
(org-clock-out))
(org-agenda-remove-restriction-lock))
(defun bh/clock-in-default-task ()
(save-excursion
(org-with-point-at org-clock-default-task
(org-clock-in))))
(defun bh/clock-in-parent-task ()
"Move point to the parent (project) task if any and clock in"
(let ((parent-task))
(save-excursion
(save-restriction
(widen)
(while (and (not parent-task) (org-up-heading-safe))
(when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
(setq parent-task (point))))
(if parent-task
(org-with-point-at parent-task
(org-clock-in))
(when bh/keep-clock-running
(bh/clock-in-default-task)))))))
(defvar bh/organization-task-id "eb155a82-92b2-4f25-a3c6-0304591af2f9")
(defun bh/clock-in-organization-task-as-default ()
(interactive)
(org-with-point-at (org-id-find bh/organization-task-id 'marker)
(org-clock-in '(16))))
(defun bh/clock-out-maybe ()
(when (and bh/keep-clock-running
(not org-clock-clocking-in)
(marker-buffer org-clock-default-task)
(not org-clock-resolving-clocks-due-to-idleness))
(bh/clock-in-parent-task)))
(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append)
#+end_src
我曾经通过如下函数根据任务ID来对默认任务计时,但是现在,通过punch in以及punch out这种方式后, 我不需要这样来对默认任务计时了。通过 F9 SPC 调用 bh/clock-in-last-task 会将计时切回原来任务。
#+header: :tangle yes
#+begin_src emacs-lisp
(require 'org-id)
(defun bh/clock-in-task-by-id (id)
"Clock in a task by id"
(org-with-point-at (org-id-find id 'marker)
(org-clock-in nil)))
(defun bh/clock-in-last-task (arg)
"Clock in the interrupted task if there is one
Skip the default task and get the next one.
A prefix arg forces clock in of the default task."
(interactive "p")
(let ((clock-in-to-task
(cond
((eq arg 4) org-clock-default-task)
((and (org-clock-is-active)
(equal org-clock-default-task (cadr org-clock-history)))
(caddr org-clock-history))
((org-clock-is-active) (cadr org-clock-history))
((equal org-clock-default-task (car org-clock-history)) (cadr org-clock-history))
(t (car org-clock-history)))))
(widen)
(org-with-point-at clock-in-to-task
(org-clock-in nil))))
#+end_src
1.2 开始计时
当我开始以及继续一个任务时候,我会通过如下一种方式开始计时:
- C-c C-x C-i
- I 在agenda中想对任务计时
- I 标题行第一个字符的加速键(加速键含义后面会介绍)
- f9 I 当在agenda中对任务计时。
- f9 I 当在org文件的一个任务上时,想对它计时
1.2.1 设置默认计时任务
我有个默认的 ** Organization 任务在我的todo.org文件中,我将零散时间 统统记录在该任务中。这个是我每天开始工作通过 F9 I 计时第一个任务. 当重新组织 我的org文件,读取邮件,清理我的收件箱,或者做一些非项目相关的计划工作时,我 也会对默认任务计时。当在其他地方执行punch in时,都是对这个默认任务计时的。
如果我想修改默认计时任务,我只要访问在任意org buffer中的任务,通过=C-u C-u C-c C-x C-i= 来对它计时。这样新的任务将会用来记录零散时间。
也可以通过快捷键 C-u C-c C-x C-i d 来快速对默认任务计时。另一种方式是对项目 中任务重复执行停止计时,那么计时器也会像父任务上移动,直到完成最上层任务,那么 计时器又会回到默认任务上计时。
1.2.2 使用时钟历史来对老的任务重新计时
可以使用计时历史来对该任务重启计时或者直接跳转到计时过的老的任务上。我通常使用该功能对 中断的任务重新计时。
考虑到如下的场景:
- 你开始工作,并对 Task A 计时 (比如默认任务)
- 现在你被新来的任务打断,需要切换到 Task B 任务(比如编写我如何使用org-mode)
- 完成 Task B (完成编写我如何使用org-mode任务)
- 你想回到 Task A (默认任务)继续工作。
这很容易做到。
- 对任务 Task A 进行计时,继续工作
- 进行任务 Task B (或者新建任务)并计时
- 完成任务 Task B 后,执行快捷键 C-u C-c C-x C-i i
计时历史显示窗口就会显示出来,可以通过快捷键 [i] 来选择。 C-u C-c C-x C-i 命令执行后的时钟历史选择窗口如下
#+begin_example
Default Task
[d] norang Organization <-- Task B
The task interrupted by starting the last one
[i] norang Organization <-- Task B
Current Clocking Task
[c] org NEXT Document my use of org-mode <-- Task A
Recent Tasks
[1] org NEXT Document my use of org-mode <-- Task A
[2] norang Organization <-- Task B
...
[Z] org DONE Fix default section links <-- 35 clock task entries ago
#+end_example
1.3 对所有任务计时 - 创建新任务时开始计时
如果想对所有任务计时那么你需要将任何事情抽象成任务。对于计划项目来说这没问题,突发 事情无时无刻不发生,所以你得有个地方记录处理中断花的时间。
为了处理这种情况,我创建了capture 任务,来记录突发任务。下面是对这种任务处理流程:
- 你正在处理计时任务时突发事件发生
- 通过快捷键 C-c cj 创建快速capture任务。
- 写好任务标题
- 去做该任务(吃午饭,谁便什么事情)
- 重定向 C-c C-c, 计时器将会回复到之前的任务上
- 对其他任务计时或者继续当前任务。
这意味着你不需要知道任务的一些细节,比如任务在什么文件,具体在文件中哪个位置,只是继续完成这个事情。之后,当方便时,批量重定向组中的任务可以节省很多时间。
如果是一个不感兴趣的任务(像是coffee break) 我创建一个journal entry捕获,并且重定向到diary.org时间 树中。如果这个是一个需要跟踪并标记完成的任务,并且需要应用到某个项目中,我将创建捕获任务并且重定向到 合适的项目,而不是存到refile.org.
1.4 查找计时任务
为了找到之前的计时任务,可以通过如下方式(经常用的放在最前边):
- 通过计时历史来查看 C-u C-c C-x C-i 找到之前还没有完成的计时任务
- 从今日的agenda视图中选择任务 SCHEDULED 以及 DEADLINE 任务优先级最高
- 从agenda视图中选择在 NEXT 状态任务 完成这些任务,并标记任务状态为 DONE
- 从其他任务列表选择任务执行
- 使用agenda视图的过滤功能来选择继续工作的任务
对你所选择的任务punching in会限制agenda视图只显示这个项目相关任务,因此就可以一段时间聚焦在这件任务上。
1.5 编辑计时项
有时候为使得计时项更加能够反应真时耗时情况,需要编辑计时项。我发现我每周会编辑2-3个计时项。
有时候,由于我不在电脑旁边,我无法对该任务计时。这种情况,之前的任务依然在计时,但是我却 在做其他事情,这两个任务计时都会有问题。
我会记录下耗时情况,等我回来,我会对正确的任务计时,并且编辑计时开始结束时间,来纠正 错误计时。
为了快速找到计时项,可以使用agenda的日志模式。 F12 a l 显示今天的时间线。我使用该 功能快速定位时间线。F11可以到当前的计时任务,但是agenda日志模式更好,可以更快速找到并访问 老的计时项。
使用 F12 a l 打开agenda的日志模式,并且只显示计时时间。移动光标到需要编辑的计时线然后执行 TAB 然后你就会跳转到需编辑任务了。
为了编辑计时项,需要先将光标移动到编辑时间部分(使用键盘,不要使用鼠标-因为单击时间将会跳回到 agenda视图中)执行 S-<up arrow> 或者 S-<down arrow> 来改时间。
下面设置使得时间修改使用不连续的计时方法来改变(不会重复)
#+header: :tangle yes
#+begin_src emacs-lisp
(setq org-time-stamp-rounding-minutes (quote (1 1)))
#+end_src
通过shift 加方向箭头调整时间,也会同时调整总时间,非常方便。
我会经常查看任务是否有重叠当调整时间时候。agenda中有个新的视图来查看任务是否重叠 – 只要在没人agenda视图中执行 = v c= , 这样计时偏移以及重叠计时就会显示出来。
我希望我的计时项尽可能准确。
通过下面脚本可以显示1分钟内的计时偏差。
#+begin_src emacs-lisp
(setq org-agenda-clock-consistency-checks
(quote (:max-duration "4:00"
:min-duration 0
:max-gap 0
:gap-ok-around ("4:00"))))
#+end_src