Monthly Reports with Org-Mode

Like a lot of people, I have to submit a monthly “bullet” report, listing the things I’ve done in the previous month.

Since I use Org-Mode for planning, scheduling, and organizing tool (or rather: I tend to throw a bunch of notes into a file and tell this love child of a day planner and a wiki to tell me what I should do next), I figured I should use that.

I could use the timeline feature (C-c a L), but that only works for the current buffer, and I want a report that covers all buffers, just like the agenda.

What I’ve done in the past is to use C-c a a to get the agenda view, go back a month, toggle displaying completed/archived/whatever items, and go through that to make my bullet list.

But I finally got around to encapsulating that into a single M-x bullet command:

; Make it easier to generate bullets for $BOSS
(defvar bullet-entry-types
  '(:closed)
  "Org-mode agenda types that we want to see in the monthly bullet report
See `org-agenda-entry-types'."
  )

(defun bullets ()
  "Show a list of achievements for the past month, for monthly reports.
Uses `org-agenda'.
"
  (interactive)
  (require 'org-agenda)
  ; All we're doing here, really, is calling `org-agenda' with
  ; arguments giving a start date and a number of days. But to do
  ; that, we need to figure out
  ; - the date of the first of last month
  ; - the number of days in last month
  (let* ((now (current-time))
	 ; Figure out when last month was. Assuming that I run this
	 ; close to the beginning of a month, then `now' minus two
	 ; weeks was some time in the previous month. We can use that
	 ; to extract the year and month that we're interested in.
	 (2weeks-ago
	  (time-subtract now
			 (days-to-time 14)))
	 ; We'll also need to know when the first of this month was,
	 ; to find out how long last month was. If today is the 12th
	 ; of the month, then the first of the month was `now' minus
	 ; 11 days.
	 (1st-of-this-month
	  (time-subtract now
			 (days-to-time
			  (- (nth 3 (decode-time now))
			     1))))
	 ; Ditto to find the first of last month.
	 (1st-of-last-month
	  (time-subtract 2weeks-ago
			 (days-to-time
			  (- (nth 3 (decode-time 2weeks-ago))
			     1))))
	 ; The length of last month is the difference (in days)
	 ; between the first of last month, and the first of this
	 ; month.
	 (len-last-month
	  (time-to-number-of-days
	   (time-subtract 1st-of-this-month
			  1st-of-last-month)))
	 (start-date (decode-time 1st-of-last-month))
	 (start-year (nth 5 start-date))	; Year number
	 (start-mon (nth 4 start-date))		; Month number
	 ; Restrict the agenda to only those types of entries we're
	 ; interested in. I think this takes advantage of dynamic
	 ; scoping, which is normally an abomination unto the lord,
	 ; but is useful here.
	 (org-agenda-entry-types bullet-entry-types)
	 )
    ; Create an agenda with the stuff we've prepared above
    (org-agenda-list nil
		     (format "%04d-%02d-01"
			     start-year
			     start-mon)
		     len-last-month)
    ))

I hope this proves useful to someone.