1

I often have cause to plan aspects of events/work in my life as much as a year out. Consequently, I would like to generate a yearly org-agenda view which has the following properties:

  1. Shows not the current year from start to finish, but the span of one year beginning with the current date up to the equivalent date the following year.
  2. Items scheduled/deadlined/dated are broken out into groups by month, with each group labeled with the name of the month.
  3. Dates that have no items do not appear on the agenda at all.
  4. Items that I enter using org-anniversary such as birthdays appear.

The requirement of grouping, coupled with the fact that I already use org-super-agenda led me to try applying it to this case. I've written some small helper functions that can produce dates shifted from today by day or year, and can produce the first and last day of shifted months.

(defun years-from-today (&optional num)
"Return this day on this month NUM years from now as time.
If NUM is nil, return this day on this month next year as time."
(let* ((num (or num 1))
       (now (decode-time (current-time)))
         (day (nth 3 now))
         (month (nth 4 now))
     (year (+ num (nth 5 now))))
    (encode-time 0 0 0 day month year)))

(defun first-day-of-month (&optional num)
"Return the first day of the month NUM months from now as time.
If NUM is nil, return the first day of this month as time."
(let* ((num (or num 0))
       (now (decode-time (current-time)))
         (month (+ num (nth 4 now)))
         (year (nth 5 now)))
    (encode-time 0 0 0 1 month year)))

(defun last-day-of-month (&optional num)
"Return the last day of the month NUM months from now as time.
If NUM is nil, return the last day of this month as time."
(let* ((num (or num 0))
       (now (decode-time (current-time)))
         (month (+ num (nth 4 now)))
         (year (nth 5 now))
         (last-day-of-month (calendar-last-day-of-month month year)))
    (encode-time 0 0 0 last-day-of-month month year)))

(defun days-from-today (&optional num)
"Return the date NUM days from now. If NUM is nil, 
 return this day on this month next year as time."
(let* ((num (or num 0))
       (now (decode-time (current-time)))
         (day (+ num (nth 3 now)))
         (month (nth 4 now))
         (year (nth 5 now)))
    (encode-time 0 0 0 day month year)))

Passing the result of any of these to (format-time-string "%Y-%m-%d" (func)) will yield date strings in the format org-super-agenda requires. The idea was to use these functions to supply date ranges to the :scheduled or :deadlined selectors in org-super-agenda-groups on a view of type agenda. I set the variable org-agenda-show-all-dates to nil and I write the following as a proof-of-concept for just the current month:

(setq org-agenda-custom-commands
      '(("u" "Upcoming Year"
     ((agenda "" ((org-agenda-span 'year)
              (org-agenda-show-all-dates nil)
              (org-super-agenda-groups
               '((:name "This Month"
                :time-grid f
                        :and (:scheduled (format-time-string "%Y-%m-%d" (days-from-today)) 
                          :scheduled (format-time-string "%Y-%m-%d" (last-day-of-month))))))))))))

What I end up getting is the following:

Year-agenda:
Friday      5 July 2024

 Other items
  inbox:       8 d. ago:  TODO Read Reading List Item
  inbox:      TODO Habit                           !       
Monday      8 July 2024

 Other items
  agenda:     A birthday
Tuesday     9 July 2024 W28

 Other items
  inbox:      Scheduled:  TODO An Emacs config task
Wednesday  24 July 2024

 Other items
  inbox:      Scheduled:  TODO Configure btop
Sunday     11 August 2024

 Other items
  agenda:     Another birthday
Tuesday     5 November 2024

 Other items
  agenda:     Another birthday
Saturday    7 December 2024

 Other items
  agenda:     Another birthday
Wednesday  22 January 2025

 Other items
  agenda:     Another birthday
Friday     11 April 2025

 Other items
  agenda:     Another birthday
Sunday      4 May 2025

 Other items
  agenda:     Another birthday

Because things are showing up under the Other items heading even when they are in this month, and the This Month heading doesn't appear anywhere, I suspect this means the group isn't catching these things at all, and this is all just stuff falling through to the default. To check, I modify the above custom command like so:

(setq org-agenda-custom-commands
      '(("u" "Upcoming Year"
     ((agenda "" ((org-agenda-span 'year)
              (org-agenda-show-all-dates nil)
              (org-super-agenda-groups
               '((:name "This Month"
                :time-grid f
                        :and (:scheduled (after (format-time-string "%Y-%m-%d" (days-from-today))) 
                         :scheduled (before (format-time-string "%Y-%m-%d" (last-day-of-month)))))
             (:discard (:anything t))))))))))

And now the agenda just looks like this:

Year-agenda:
Saturday    6 July 2024

Monday      8 July 2024 W28

 This Month
  inbox:      Scheduled:  TODO Emacs config task
Wednesday  24 July 2024

 This Month
  inbox:      Scheduled:  TODO Configure btop
Sunday     11 August 2024

Tuesday     5 November 2024

Saturday    7 December 2024

Wednesday  22 January 2025

Friday     11 April 2025

Sunday      4 May 2025

NB: The above result is from Saturday, July 6th when the edits were made, as opposed to the earlier content from Friday, July 5th.

So we see the present day, though it has no scheduled items. We also see rows for each upcoming day that has something scheduled, though unsurprisingly the org-anniversary items do not show up as they do not have scheduled time stamps. Additionally, the group name shows up now, but it shows up under the heading for the date, for each date that has an entry. This is the inverse of what I want: the "This Month" heading to appear once, and then have all the items grouped under it. I also don't want the date to be a heading above its entries, but to appear to one side for all entries.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.