;;; dawn.el --- Lightweight dawn/dusk task scheduling -*- lexical-binding: t; -*- ;; Copyright (C) 2022 Case Duckworth ;; Author: Case Duckworth ;; Maintainer: Case Duckworth ;; URL: https://codeberg.org/acdw/dusk.el ;; Version: 0.3.0 ;; Keywords: calendar, themes, convenience ;; Package-Requires: ((emacs "24.3")) ;;; Commentary: ;; There is also circadian.el, but it doesn't quite work for me. ;; This code comes mostly from https://gnu.xyz/auto_theme.html, but also ;; somewhere else (which I've forgotten) and my own brain :) ;;; Code: (require 'calendar) (require 'cl-lib) (require 'solar) ;;; Timers (defvar dawn--dawn-timer nil "Timer for dawn-command.") (defvar dawn--dusk-timer nil "Timer for dusk-command.") (defvar dawn--reset-timer nil "Timer to reset dawn at midnight.") ;;; Functions (defun dawn-encode-time (f) "Encode fractional time F. If F is nil, return nil." (when f (let ((hhmm (cl-floor f)) (date (cdddr (decode-time)))) (encode-time (append (list 0 (round (* 60 (cadr hhmm))) (car hhmm)) date))))) (defun dawn-midnight () "Return the time of the /next/ midnight." (let ((date (cdddr (decode-time)))) (encode-time (append (list 0 0 0 (1+ (car date))) (cdr date))))) (defun dawn-sunrise () "Return the time of today's sunrise." (dawn-encode-time (caar (solar-sunrise-sunset (calendar-current-date))))) (defun dawn-sunset () "Return the time of today's sunset." (dawn-encode-time (caadr (solar-sunrise-sunset (calendar-current-date))))) ;;; Interface ;;;###autoload (defun dawn-schedule (dawn-command dusk-command) "Run DAWN-COMMAND at sunrise, and DUSK-COMMAND at dusk. Requires `calendar-longitude' and `calendar-latitude' to be set; if they're not, it will prompt the user for them or error." (when (or (null calendar-longitude) (null calendar-latitude)) (or (solar-setup) (user-error "`dawn' won't work without setting %s!" (cond ((and (null calendar-longitude) (null calendar-latitude)) "`calendar-longitude' and `calendar-latitude'") ((null calendar-longitude) "`calendar-longitude'") ((null calendar-latitude) "`calendar-latitude'"))))) (let ((dawn (dawn-sunrise)) (dusk (dawn-sunset))) (cond ((or (null dawn) (null dusk)) ;; There is no sunrise or sunset, due to how close we are to the poles. ;; In this case, we must figure out whether it's day or night. (pcase (caddr (solar-sunrise-sunset (calendar-current-date))) ("0:00" (funcall dusk-command)) ; 0 hours of daylight ("24:00" (funcall dawn-command)) ; 24 hours of daylight )) ((time-less-p nil dawn) ;; If it isn't dawn yet, it's still dark. Run DUSK-COMMAND and schedule ;; DAWN-COMMAND and DUSK-COMMAND for later. (funcall dusk-command) (run-at-time dawn nil dawn-command) (run-at-time dusk nil dusk-command)) ((time-less-p nil dusk) ;; If it isn't dusk yet, it's still light. Run DAWN-COMMAND and schedule ;; DUSK-COMMAND. (funcall dawn-command) (run-at-time dusk nil dusk-command)) (t ;; Otherwise, it's past dusk, so run DUSK-COMMAND. (funcall dusk-command))) ;; Schedule a reset at midnight, to re-calculate dawn/dusk times. (run-at-time (dawn-midnight) nil #'dawn-schedule dawn-command dusk-command))) (provide 'dawn) ;;; dawn.el ends here