UP | HOME

🔙回到目录 | 🔻 本网页更新于 [2026-08-27 四 17:58]

emacs-在org-agenda中展示从今日开始一月后的任务


1. 前言

先前文章:emacs-在org-agenda中展示年度、季度、月度、周度任务

在此基础上,我们这篇文章要解决两个痛点。
一是提升灵活性。先前的代码将搜索的 QUERY 部分写死在了函数中,导致只能展示固定的查询结果。
二是提升时间的相对性。先前的代码搜索出来的内容是互相包含的,月度结果包含周度结果,我们需要有一个排除的办法。

1.1. 提升时间相对性

(defun my/time-add-months (time delta)
  "将 TIME 增加 DELTA 个月,若目标日不存在则截断至月末。"
  (let* ((dec (decode-time time))
         (day (decoded-time-day dec))
         (month (decoded-time-month dec))
         (year (decoded-time-year dec))
         (new-month (+ month delta))
         (new-year (+ year (/ (1- new-month) 12)))
         (new-month (1+ (mod (1- new-month) 12)))
         (last-day (calendar-last-day-of-month new-month new-year)))
    (encode-time 0 (decoded-time-minute dec)
                 (decoded-time-hour dec)
                 (min day last-day) new-month new-year)))

(defun my/org-ql-ts-period (period)
  "根据输入的 PERIOD 返回当前或相对当前时间的起止日期,格式为 'YYYY-MM-DD'。

季节 (Season) 定义:
- 季度1: 1月 - 3月
- 季度2: 4月 - 6月
- 季度3: 7月 - 9月
- 季度4: 10月 - 12月

星期一被视为一周的开始。

支持的 PERIOD 符号:
- 'y'  : 当前年(1月1日 ~ 12月31日)
- 's'  : 当前季度
- 'm'  : 当前月
- 'w'  : 当前周(周一 ~ 周日)
- 'ry' : 从今天开始的一年(今天 ~ 今天 + 12个月)
- 'rs' : 从今天开始的一个季度(今天 ~ 今天 + 3个月)
- 'rm' : 从今天开始的一个月(今天 ~ 今天 + 1个月)
- 'rw' : 从今天开始的一周(今天 ~ 今天 + 7天)

参数 PERIOD: 一个表示时间段的符号。

返回: (start-date . end-date) 的 cons,日期字符串为 'YYYY-MM-DD'。
若输入无效则返回错误信息。"
  (let* ((now (current-time))
         (decoded-time (decode-time now))
         (sec (nth 0 decoded-time))
         (min (nth 1 decoded-time))
         (hour (nth 2 decoded-time))
         (day (nth 3 decoded-time))
         (month (nth 4 decoded-time))
         (year (nth 5 decoded-time))
         start-date
         end-date)
    (cond
     ;; 年 (Year)
     ((eq period 'y)
      (setq start-date (format-time-string "%Y-01-01"))
      (setq end-date (format-time-string "%Y-12-31")))

     ;; 季 (Season)
     ((eq period 's)
      (let* ((start-month (cond ((<= month 3) 1)
                                ((<= month 6) 4)
                                ((<= month 9) 7)
                                (t 10)))
             (end-month (+ start-month 2))
             (end-day (calendar-last-day-of-month end-month year)))
        (setq start-date (format-time-string "%Y-%m-01" (encode-time 0 0 0 1 start-month year)))
        (setq end-date (format-time-string (format "%%Y-%%m-%d" end-day) (encode-time 0 0 0 end-day end-month year)))))

     ;; 月 (Month)
     ((eq period 'm)
      (setq start-date (format-time-string "%Y-%m-01"))
      (let* ((last-day (calendar-last-day-of-month month year)))
        (setq end-date (format-time-string (format "%%Y-%%m-%d" last-day)))))

     ;; 周 (Week)
     ((eq period 'w)
      (let* ((day-of-week (string-to-number (format-time-string "%u"))) ; 星期一为1,星期日为7
             (start-offset (- day-of-week 1))
             (end-offset (- 7 day-of-week))
             (start-time (time-subtract now (seconds-to-time (* start-offset 24 60 60))))
             (end-time (time-add now (seconds-to-time (* end-offset 24 60 60)))))
        (setq start-date (format-time-string "%Y-%m-%d" start-time))
        (setq end-date (format-time-string "%Y-%m-%d" end-time))))

     ;; 相对年 (Relative Year)
     ((eq period 'ry)
      (let ((start-time now)
            (end-time (time-add now (seconds-to-time (* 365 24 60 60)))))
        (setq start-date (format-time-string "%Y-%m-%d" start-time))
        (setq end-date (format-time-string "%Y-%m-%d" end-time))))

     ;; 相对季度 (Relative Season, 3 months)
     ((eq period 'rs)
      (let ((start-time now)
            (end-time (my/time-add-months now 3)))
        (setq start-date (format-time-string "%Y-%m-%d" start-time))
        (setq end-date (format-time-string "%Y-%m-%d" end-time))))

     ;; 相对月 (Relative Month)
     ((eq period 'rm)
      (let ((start-time now)
            (end-time (my/time-add-months now 1)))
        (setq start-date (format-time-string "%Y-%m-%d" start-time))
        (setq end-date (format-time-string "%Y-%m-%d" end-time))))

     ;; 相对周 (Relative Week, 7 days)
     ((eq period 'rw)
      (let ((start-time now)
            (end-time (time-add now (seconds-to-time (* 7 24 60 60)))))
        (setq start-date (format-time-string "%Y-%m-%d" start-time))
        (setq end-date (format-time-string "%Y-%m-%d" end-time))))

     ;; 无效输入
     (t (error "无效的参数,请输入 'y', 's', 'm', 'w', 'ry', 'rs', 'rm', 或 'rw'")))
    (cons start-date end-date)))

my/time-add-months 函数可以将输入的 TIME 添加 DELTA 个月后返回时间戳。和其他函数不同,这里会采取反向截断的方式。如果你想在8月31日计算1月后时间,其他函数会计算为10月1日(因为9月只有30日),而这个函数会向后截断,返回9月30日。
my/org-ql-ts-period 函数可以根据输入的符号返回以当前时间为准的一对字符串 '(起始时间 结束时间)
对应的输入和输出表格如下。

输入符号 输出的起始时间 输出的结束时间
'y 今年第一天 今年最后一天
's 本季度第一天 本季度最后一天
'm 本月第一天 本月最后一天
'w 本周第一天 本周最后一天
'ry 当天 从当天起往后365天的那天
'rs 当天 从当天起往后3个月的那天
'rm 当天 从当天起往后1个月的那天
'rw 当天 从当天起往后7天的那天

1.2. 提升灵活性

为了灵活查询,最好的办法是构造一个函数,使其返回的值能直接在 org-ql-block 的查询中使用。这里我们跟在 :except 后的符号,代表会被排除。

(cl-defun my/org-ql-range (field period &key ((:except except-period)))
  "根据 FIELD 和 PERIOD 返回一个 org-ql 查询片段,表示 FIELD 在该周期内。
FIELD 是日期字段符号,如 'deadline、'scheduled、'ts 等。
PERIOD 是周期符号,如 'w、'm、's、'y、'ry、'rs、'rm、'rw。
如果提供 :except EXCEPT-PERIOD,则从主范围中排除该周期。"
  (let ((main-range (my/org-ql-ts-period period)))
    (if except-period
        (let ((except-range (my/org-ql-ts-period except-period)))
          `(and (,field :from ,(car main-range) :to ,(cdr main-range))
                (not (,field :from ,(car except-range) :to ,(cdr except-range)))))
      `(,field :from ,(car main-range) :to ,(cdr main-range)))))

使用例:

(org-ql-block
 `((and (todo "TODO" "HOLD") (tags "工作") ,(my/org-ql-range 'deadline 'w))
   :sort (deadline scheduled priority)
   :header "🎯本周截止🎯" ))
(org-ql-block
 `((and (todo "TODO" "HOLD") (tags "工作") ,(my/org-ql-range 'deadline 'rm :except 'w))
   :sort (deadline scheduled priority)
   :header "🎯未来一月🎯" ))

© Published by Emacs 32.0.50 (Org mode 10.0-pre) | RSS 评论