为每日重复事项添加工作日判定

[2024-12-17 周二 14:21]

有的时候,我们只想在工作日处理一些工作相关的每日重复事项。

设置节假日

排除掉周六、周日以及法定节假日(也许还有自定义的假日)之后,就是工作日。在 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表达式的日历条目,后续会用到。

设置函数

(defun my/date-is-workday (date &optional offset)
  "工作日/调休日返回t,其余返回nil;offset指查看偏移天数的情况"
  (let* ((offset (or offset 0))
         (timestamp (time-to-seconds (date-to-time date)))
         (offset-timestamp (time-add timestamp (seconds-to-time (* 24 60 60 offset))))
         (date-string (format-time-string "%Y-%m-%d" offset-timestamp))
         (start-day (time-to-days (org-read-date nil t date-string)))
         (parsed-time (parse-time-string date-string))
         (year (nth 5 parsed-time))
         (month (nth 4 parsed-time)) 
         (day (nth 3 parsed-time))
         (workdays nil)
         (holidays nil)
         (files (org-agenda-files nil 'ifmode))
         (result-string " ")
         file rtn rtnall
         )
    (setq date (calendar-gregorian-from-absolute start-day))
    (while (setq file (pop files))
      (catch 'nextfile
        (setq rtn (apply #'org-agenda-get-day-entries
				         file date
				         '(:sexp)))
        (when rtn
          (setq rtnall (append rtnall rtn)))
      ))
    (dolist (result rtnall)
      (setq result-string (concat result-string (substring-no-properties result)))
      )
    (when (string-match "🏮" result-string)
      (setq holidays '123))
    (when (string-match "💼" result-string)
      (setq workdays '123))
    ;; Remove the custom command after use 
    (if (or (= (calendar-day-of-week (list month day year)) 0) ; Sunday
            (= (calendar-day-of-week (list month day year)) 6)) ; Saturday
        (if workdays
            t
          nil)
      (if holidays
          nil
        t)
      )
    ))

这个函数会根据输入日期+可选的偏移日期,通过匹配emoji来判断是否为工作日。

以下函数参考了这篇文章

(defun my/org-hook-for-repeat-on-workday()
  "offset意味当天为假且加上负offset日期之后的那天为真时,则返回真"
  (when (and (org-entry-get nil "WORKDAY") (string-match "d" (org-get-repeat)))
    ;; Get time from item at POINT
    (let* ((offset (string-to-number (org-entry-get nil "WORKDAY")))
           ;; Convert to timestamp - required for the next step
           (seconds-timestamp (org-time-string-to-seconds (org-entry-get (point) "SCHEDULED")))
           ;; Convert to decoded time - required to find out the weekday
           (decoded-time))
      (while (if (not (my/date-is-workday (format-time-string "%Y-%m-%d" seconds-timestamp)))
                 (if (my/date-is-workday (format-time-string "%Y-%m-%d" seconds-timestamp) (* -1 offset))
                     nil
                   t)
               nil)
        (setq seconds-timestamp (time-add seconds-timestamp (seconds-to-time (* 24 60 60))))
        )
      (let ((result-string (format-time-string "%Y-%m-%d %H:%M" seconds-timestamp)))
        (org-schedule nil result-string))
      ))
  )

(add-hook 'org-todo-repeat-hook 'my/org-hook-for-repeat-on-workday)

这个函数会提取属性中的 WORKDAY ,如果有值且按日重复,则会启动。

偏移值的目的主要是用来处理「节假日第一天」和「节假日最后一天」的需求。如果有任务需要在「节假日第一天」和工作日重复,则将 WORKDAY 的值设定为1;如果有任务需要在「节假日最后一天」和工作日重复,则将 WORKDAY 的值设定为-1;如果都不需要,则将 WORKDAY 的值设定为0,仅会匹配工作日。

使用效果

根据sexp表达式的条目中🏮和💼的emoji来判断。

→ 是周末
—> 有💼
–—> 工作日
—> 无💼
–—> 节假日
→ 不是周末
—> 有🏮
–—> 节假日
—> 无🏮
–—> 工作日

2 个赞

可以考虑搞个chinese-holidays包上去,包内容每月自动跟网络上的休假信息同步。

实际上国家法定节假日在前一年就会提前公布,是固定的。

是的,差不多11月份就会公布下一年的。

:sweat_smile: 调用 org-agenda 函数会污染现存的 agenda-buffer ,于是按照 org-agenda 的原理重写了 :sexp 日历项查找功能