emacs-为每日重复事项添加工作日判定
有的时候,我们只想在工作日处理一些工作相关的每日重复事项。
1. 设置节假日
排除掉周六、周日以及法定节假日(也许还有自定义的假日)之后,就是工作日。在 org-agenda-files 中任一文件加入以下内容:
;; 2025中国法定节假日
%%(diary-date 1 1 2025) 🏮元旦🏮
%%(diary-block 1 28 2025 2 4 2025) 🏮春节🏮
%%(diary-date 1 26 2025) 💼春节-上班💼
%%(diary-date 2 8 2025) 💼春节-上班💼
%%(diary-block 4 4 2025 4 6 2025) 🏮清明🏮
%%(diary-block 5 1 2025 5 5 2025) 🏮劳动节🏮
%%(diary-date 4 27 2025) 💼劳动节-上班💼
%%(diary-block 5 31 2025 6 2 2025) 🏮端午🏮
%%(diary-block 10 1 2025 10 8 2025) 🏮国庆、中秋🏮
%%(diary-date 9 28 2025) 💼国庆中秋-上班💼
%%(diary-date 10 11 2025) 💼国庆中秋-上班💼
即可在org-agenda中生成sexp表达式的日历条目,后续会用到。
2. 设置函数
(defun my/date-is-workday (date &optional offset)
"判断日期是否为工作日(包含调休)。DATE格式为 'YYYY-MM-DD'。"
(let* ((d (date-to-time date))
;; 处理偏移量
(target-time (time-add d (days-to-time (or offset 0))))
(greg (calendar-gregorian-from-absolute (time-to-days target-time)))
;; 获取当天的 Agenda 字符串
(entries (mapconcat (lambda (f)
(mapconcat #'identity (org-agenda-get-day-entries f greg :sexp) ""))
(org-agenda-files) ""))
(is-holiday (string-match-p "🏮" entries))
(is-workday (string-match-p "💼" entries))
(day-of-week (calendar-day-of-week greg)))
(cond
;; 优先级1:如果标记为调休日(💼),强制为工作日
(is-workday t)
;; 优先级2:如果标记为节假日(🏮),强制为非工作日
(is-holiday nil)
;; 优先级3:周末为非工作日,周一至周五为工作日
((memq day-of-week '(0 6)) nil)
(t t))))
这个函数会根据输入日期+可选的偏移日期,通过匹配emoji来判断是否为工作日。
以下函数参考了这篇文章1。
(defun my/org-hook-for-repeat-on-workday ()
"如果任务有WORKDAY属性且是重复任务,自动跳过节假日调度到下一个工作日。"
(let ((workday-attr (org-entry-get nil "WORKDAY")))
(when (and workday-attr (org-get-repeat))
(let* ((offset (string-to-number workday-attr))
;; 获取原始时间戳
(time (org-time-string-to-time (org-entry-get nil "SCHEDULED")))
(day-sec (* 24 60 60)))
;; 循环直到找到一个既不是假期,也不是“带偏移的假期”的日期
;; 注意:这里保留了你原有的逻辑:必须同时满足两个条件才停止循环
(while (and (not (my/date-is-workday (format-time-string "%Y-%m-%d" time)))
(not (my/date-is-workday (format-time-string "%Y-%m-%d" time) (- offset))))
(setq time (time-add time (seconds-to-time day-sec))))
;; 重新调度
(org-schedule nil (format-time-string
(if (string-match-p ":" (org-entry-get nil "SCHEDULED"))
"%Y-%m-%d %a %H:%M"
"%Y-%m-%d %a")
time))))))
(add-hook 'org-todo-repeat-hook 'my/org-hook-for-repeat-on-workday)
这个函数会提取属性中的 WORKDAY ,如果有值且按日重复,则会启动。
偏移值的目的主要是用来处理「节假日第一天」和「节假日最后一天」的需求。如果有任务需要在「节假日第一天」和工作日重复,则将 WORKDAY 的值设定为1;如果有任务需要在「节假日最后一天」和工作日重复,则将 WORKDAY 的值设定为-1;如果都不需要,则将 WORKDAY 的值设定为0,仅会匹配工作日。
3. 使用效果
根据sexp表达式的条目中🏮和💼的emoji来判断。
-> 是周末
—> 有💼
–—> 工作日
—> 无💼
–—> 节假日
-> 不是周末
—> 有🏮
–—> 节假日
—> 无🏮
–—> 工作日