I was happy with none of the approaches, as I wanted a noninteractive solution that could be used quickly and also from the CLI, so I wrote this:
(defun load-theme-string (theme)
(if (-contains? (custom-available-themes) (intern theme)) (load-theme (intern theme))))
(defun toggle-theme (&optional suffix)
"Heuristically toggle between light and dark themes."
(interactive)
(let* ((theme (s-replace-all '(("light" . "dark") ("dark" . "light")
("black" . "white") ("white" . "black")
("day" . "night") ("night" . "day"))
(symbol-name doom-theme)))
(theme-base (s-replace-regexp "-[^-]*$" "" theme)))
(or (if suffix (or (load-theme-string (concat theme "-" suffix)) (load-theme-string (concat theme-base "-" suffix)))) (load-theme-string (if (and (not suffix) (equal theme (symbol-name doom-theme))) (concat theme "-light") theme)) (load-theme-string theme-base))
)
)
It is based on Doom and intentionally does not handle every edge case,
but works for most themes included in Doom Emacs -
though it can of course be adjusted for other environments
Futhermore, it can be called interactively or with an argument,
so I can switch the theme from the terminal
(providing "light"/"dark" or no parameter):
emacsclient -e "(toggle-theme \"dark\")"
(car custom-enabled-themes)
returns the currently enabled theme.