Org Mode date arithmetic

Whenever I need to get Emacs to prompt me for a date or time (even for non-Org things), I use org-read-date. I love its flexibility. It’s great to be able to say things like +3 for three days from now, fri for next Friday, +2tue for two Tuesdays from now, +1w for next week, and +1m for next month. It’s easy to use org-read-date in Emacs Lisp. Calling it with (org-read-date) (usually as an interactive argument, like so: (interactive (list (org-read-date)))) gives me a date like 2015-08-06 depending on what I type in.

I use org-read-date for non-interactive date calculations, too. For example, if I want to quickly get the Org-style date for tomorrow, I can use org-read-date‘s third parameter (from-string) like this:

(org-read-date nil nil "+1")

Here’s how to calculate relative dates based on a specified date. You can hard-code the base date or use another org-read-date to get it. In this example, I’m getting the Monday after 2015-08-31. Note the use of two + signs instead of just one.

(org-read-date nil nil "++mon" nil (org-time-string-to-time "2015-08-31"))

org-time-string-to-time converts a date or time string into the internal representation for time. You can then extract individual components (ex: month) with decode-time, or convert it to the number of seconds since the epoch with time-to-seconds. Alternatively, you can convert Org time strings directly to seconds with org-time-to-seconds.

If you’re working with days, you can convert time strings with org-time-string-to-absolute. For example, you can use this to calculate the number of days between two dates (including the first day but excluding the last day), like this:

(let ((start-date (org-read-date))
      (end-date (org-read-date)))
  (- (org-time-string-to-absolute end-date)
     (org-time-string-to-absolute start-date)))

To get the month, day, and year, you can use org-time-string-to-time and decode-time, or you can use org-time-string-to-seconds and calendar-gregorian-from-absolute.

To convert internal time representations into Org-style dates, I tend to use (format-time-string "%Y-%m-%d" ...). encode-time is useful for converting something to the internal time representation. If you’re working with absolute days, you can convert them to Gregorian, and then format the string.

So, to loop over all the days between the start and end date, you could use a pattern like this:

(let* ((start-date (org-read-date))
       (end-date (org-read-date))
       (current (org-time-string-to-absolute start-date))
       (end (org-time-string-to-absolute end-date))
  (while (< current end)
    (setq gregorian-date (calendar-gregorian-from-absolute current))
    (setq formatted-date
          (format "%04d-%02d-%02d"
                  (elt gregorian-date 2) ; month
                  (elt gregorian-date 0) ; day
                  (elt gregorian-date 1))) ; year
    ;; Do something here; ex:
    (message "%s" formatted-date)
    ;; Move to the next date
    (setq current (1+ current))))

Alternatively, you could use org-read-date with a default date, like this:

(let* ((start-date (org-read-date))
       (end-date (org-read-date))
       (current start-date))
  (while (string< current end-date)
    ;; Do something here; ex:
    (message "%s" current)
    ;; Move to the next date
    (setq current (org-read-date nil nil "++1" nil (org-time-string-to-time current)))))

There are probably more elegant ways to write this code, so if you can think of improvements, please feel free to share.

Anyway, hope that helps!

  • Nagora

    Thank you! If I use shift left-arrow, for example, so that a week-based clocktable is showing “thisweek-1” at the top, the caption line says something like: “Clock summary at [2015-08-06 Thu 18:33], for week 2015-W32” which is of no use to me at all, especially given that the very, very, very few times I need to work with year-weeks, they need to start on April 6th for the UK tax year, not the calendar year.

    It’s been driving me nuts every time my line manager asks for time spent on tasks by week – I have all the information in Org but it always takes me ages to work out which week I need to ask Org for!

    Your (interactive (list (org-read-date))) snippet as solved my problem. Hooray!