Watch progress websites exist for anime and film. They work great. Socialization is great.
But they don’t record the exact time at which I finished each episode. I find such statistics amusing to dig through in some kind of year-end review. I also found it tremendously helpful to know which episodes were most recently watched, and in what order. Helps with recollecting the context of each show, especially when chasing more than a couple of shows at the same time. Depending on your judgment, using Letterboxd and MyAnimeList may also constitute giving private information to 3rd parties.
So I ended with an organization system that’s, effectively, top layer of bullet points for the show, and inner layer for the episodes. Something like:
* STRT SHIROBAKO
** [2025-04-01 Tue 11:11] E01
** [2025-04-16 Wed 22:22] E02
* TODO Do It Yourself!!
* DONE ゆるキャンプ
** [2025-03-20 Thu 20:21] E10
** [2025-03-21 Fri 20:59] E11
** [2025-04-14 Mon 21:36] E12
* STRT mono
** [2025-04-13 Sun 16:56] E01
I want to be able to run a command, and get it as a linear timeline:
* [2025-03-20 Thu 20:21] ゆるキャンプ E10
* [2025-03-21 Fri 20:59] ゆるキャンプ E11
* [2025-04-01 Tue 11:11] SHIROBAKO E01
* [2025-04-13 Sun 16:56] mono E01
* [2025-04-14 Mon 21:36] ゆるキャンプ E12
* [2025-04-16 Wed 22:22] SHIROBAKO E02
Which turns out to be a pretty easy simple regex-based forward parsing algorithm. I thought about using org-element-map, but it seemed like a bit of an overkill?
To use, I do g g M-x org-category->timeline (evidently, I use evil-mode), bringing me to a new buffer *Category Timeline* filled out appropriately.
(defun org-category->timeline ()
"Collect category-timestamp tree into a single, chronologically
ordered timestamp list. Starting at point."
(interactive)
(let ((pt (point))
(curr-category nil)
(curr-pt (search-forward "\n* " nil t))
(items '()))
;; Collect items
(while curr-pt
(goto-char curr-pt)
;; Skip org-todo marker
(forward-word) (forward-char)
(setq curr-category (buffer-substring-no-properties (point) (line-end-position))
curr-pt (save-excursion (search-forward "\n* " nil t)))
(while-let ((i (re-search-forward (rx "\n** ["
(group (* (not "]")))
"] ")
curr-pt t)))
(let ((time (match-string-no-properties 1))
(comment (buffer-substring-no-properties (point) (line-end-position))))
(push (list (encode-time (org-parse-time-string time))
time curr-category comment)
items))))
;; Sort by time
(sort items (lambda (a b) (time-less-p (car a) (car b))))
;; Restore to starting point in the source buffer
(goto-char pt)
;; Print items into a new buffer
(switch-to-buffer "*Category Timeline*")
(erase-buffer)
(dolist (i items)
(let ((time (cadr i))
(category (caddr i))
(comment (cadddr i)))
(insert "* [" time "] " category " " comment ?\n)))))