I'm a big fan of Emacs's org-mode. Over the past year, I've started using it for everything - tracking tasks, taking notes, and drafting all my reports, papers, and blog posts. Org-mode is the only task-tracking software that I've used for more then a week.
At work, I am required to produce a monthly status report. To automate part of the process, I figured out a way to have org-mode produce a list of the tasks completed during a specific month. Since I couldn't find a similar example through a Google search, I thought I would post my approach for the benefit of others (and as a reminder to myself!).
Below is an example org file containing completed tasks that I'll use
to illustrate the approach. The tracking closed items feature has
been configured to add a time-stamp when each task is transitioned to
the DONE
state. The header specifies a category, Foo
, that org
will associate with all of the tasks in the file.
#+Category: Foo * DONE Feed the dog CLOSED: [2010-04-30 ] * DONE Mow the lawn CLOSED: [2010-05-01 ] * DONE Take out the trash CLOSED: [2010-05-20 ] * DONE Pay the bills CLOSED: [2010-06-01 ]
First, configure org-mode's agenda feature and use the C-c [
command
to add the example file to the agenda files list.
At this point, a list of the tasks completed in May can be produced by
issuing the agenda tag matching command, C-c a m
, and giving it the
following match string:
CATEGORY="Foo"+TODO="DONE"+CLOSED>="[2010-05-01]"+CLOSED<="[2010-05-31]"
This should produce the following list (slightly reformatted to fit blog width):
Headlines with TAGS match: CATEGORY="Foo"+TODO="DONE"\ +CLOSED>="[2010-05-01]"+CLOSED<="[2010-05-31]" Press `C-u r' to search again with new search string Foo: DONE Mow the lawn Foo: DONE Take out the trash
Although this works, entering the search string is a cumbersome task. A better solution would avoid this step.
Agenda provides a way to define custom commands that can perform searches using pre-defined match strings. The following elisp code defines a custom command that performs the above tag search automatically.
(setq org-agenda-custom-commands `(("F" "Closed Last Month" tags (concat "CATEGORY=\"Foo\"" "+TODO=\"DONE\"" "+CLOSED>=\"[2010-05-01]\"" "+CLOSED<=\"[2010-05-30]\"")))
After eval-ing this command, typing C-c a F
will produce the same
list as above without having to enter the match string. This approach
is indeed better but uses a hard-coded match string. An even better
solution would generate the match string based on the current date.
Although the call to concat
in the example above programatically
generates the match string, it does so only when the setq
is
evaluated. If the setq
is in an initialization file
(e.g. ~/.emacs
) the match string will get generated based on the
date emacs was started and not the date on which the search is
performed. This could produce erroneous searches when using an Emacs
instance started before the turn of the month. In such cases, the
setq
could be manually re-evaluated to generate the correct match
string but an automatic solution would be best.
Unfortunately, org doesn't currently support providing a lambda to generate the match string at search time. For instance, this example:
(setq org-agenda-custom-commands `(("F" "Closed Last Month" tags (lambda () (concat "CATEGORY=\"Foo\"" "+TODO=\"DONE\"" "+CLOSED>=\"[2010-05-01]\"" "+CLOSED<=\"[2010-05-30]\"")))))
produces the error message "Wrong type argument: stringp, …". Patching org-mode to support lambdas for match strings is an option but I prefer to maintain the stock org-mode code.
Thanks to the near infinite hackability of emacs, it's possible to extend the stock org mode functionality without modifying it directly. The below elisp code defines two new interactive functions that call into org-mode to perform a tag search for a specific month.
(require 'calendar) (defun jtc-org-tasks-closed-in-month (&optional month year match-string) "Produces an org agenda tags view list of the tasks completed in the specified month and year. Month parameter expects a number from 1 to 12. Year parameter expects a four digit number. Defaults to the current month when arguments are not provided. Additional search criteria can be provided via the optional match-string argument " (interactive) (let* ((today (calendar-current-date)) (for-month (or month (calendar-extract-month today))) (for-year (or year (calendar-extract-year today)))) (org-tags-view nil (concat match-string (format "+CLOSED>=\"[%d-%02d-01]\"" for-year for-month) (format "+CLOSED<=\"[%d-%02d-%02d]\"" for-year for-month (calendar-last-day-of-month for-month for-year)))))) (defun jtc-foo-tasks-last-month () "Produces an org agenda tags view list of all the tasks completed last month with the Category Foo." (interactive) (let* ((today (calendar-current-date)) (for-month (calendar-extract-month today)) (for-year (calendar-extract-year today))) (calendar-increment-month for-month for-year -1) (jtc-org-tasks-closed-in-month for-month for-year "CATEGORY=\"Foo\"+TODO=\"DONE\"")))
The first function, jtc-org-tasks-closed-in-month
, generates an
appropriate query string and calls the internal org-mode agenda
function org-tags-view
. The function defaults to the current month
but takes optional arguments for the desired month and year. The
function also takes a match-string
argument that can be used to
provide additional match criteria.
The second function, jtc-foo-tasks-last-month
, calculates the prior
month and calls jtc-org-tasks-closed-in-month
with an additional
match string to limit the list to DONE
tasks from the category
Foo
. Executing jtc-foo-tasks-last-month
interactively
automatically produces a list of the tasks closed in the prior month.
For my purposes, this is close enough to the ideal solution. Using the
optional match-string
argument, I can re-use this solution to search
for tasks completed in other categories or with specific tags.
My typical work flow is to archive the closed tasks after my status
report is written. Org-mode's agenda makes this an easy task. First I
mark all of the tasks for a bulk operation by typing m
on each. Then
I perform a bulk archive by typing the command B $
. This will move
the closed tasks to an archive file, typically a file of the same name
with an added _archive
suffix.
Org-mode is a great productivity tool. Combined with Emacs's hackability, it's possible to create tools optimized for your particular work flow.
Addendum
I found that searching on CLOSED
date ranges didn't work in
org-mode version 6.34a. The problem appears to be fixed in the
6.36c release so be sure to have the right version if you want to
replicate this method.