Org Mode date arithmetic
Posted: - Modified: | emacs, orgWhenever 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)) gregorian-date formatted-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!