emacs - 如何计算 Emacs 日历中不包括周末和节假日的天数

标签 emacs elisp

在 Emacs 日历中,可以使用 M-= 计算两个日期(包括开始日期和结束日期)之间的天数。运行命令 calendar-count-days-region .我如何计算不包括周末(周六和周日)的天数以及是否定义了来自变量的假期:holiday-general-holidaysholiday-local-holidays ?

最佳答案

我认为这基本上分为三个部分:

  • 计算某个区域的天数
  • 减去周末天数
  • 减去假期

  • Emacs 的第一部分已经包含了 M-= ( calendar-count-days-region ),所以让我们来看看 that function

    有用,但不幸的是它读取缓冲区并直接发送输出。让我们制作一个通用版本,它接受开始和结束日期参数并返回天数而不是打印它们:
    (defun my-calendar-count-days(d1 d2)
      (let* ((days (- (calendar-absolute-from-gregorian d1)
                      (calendar-absolute-from-gregorian d2)))
             (days (1+ (if (> days 0) days (- days)))))
        days))
    

    这几乎只是 calendar-count-days-region 函数的副本,但没有缓冲区读取和写入内容。一些测试:
    (ert-deftest test-count-days ()
      "Test my-calendar-count-days function"
      (should (equal (my-calendar-count-days '(5 1 2014) '(5 31 2014)) 31))
      (should (equal (my-calendar-count-days '(12 29 2013) '(1 4 2014)) 7))
      (should (equal (my-calendar-count-days '(2 28 2012) '(3 1 2012)) 3))
      (should (equal (my-calendar-count-days '(2 28 2014) '(3 1 2014)) 2)))
    

    现在,对于第 2 步,我找不到任何内置函数来计算某个日期范围的周末天数(令人惊讶!)。幸运的是,这个/might/在处理绝对日期时非常简单。这是一个非常幼稚的尝试,它只是遍历范围内的所有绝对日期并查找星期六和星期日:
    (defun my-calendar-count-weekend-days(date1 date2)
      (let* ((tmp-date (if (< date1 date2) date1 date2))
             (end-date (if (> date1 date2) date1 date2))
             (weekend-days 0))
        (while (<= tmp-date end-date)
          (let ((day-of-week (calendar-day-of-week
                              (calendar-gregorian-from-absolute tmp-date))))
            (if (or (= day-of-week 0)
                    (= day-of-week 6))
                (incf weekend-days ))
            (incf tmp-date)))
        weekend-days))
    

    该函数应该优化,因为它做了一堆不必要的循环(例如,我们知道周日之后的 5 天不会是周末,所以没有必要转换和测试它们),但为了这个例子的目的,我认为这很清楚和简单。确实,现在已经足够了。一些测试:
    (ert-deftest test-count-weekend-days ()
      "Test my-calendar-count-weekend-days function"
      (should (equal (my-calendar-count-weekend-days
              (calendar-absolute-from-gregorian '(5 1 2014))
              (calendar-absolute-from-gregorian '(5 31 2014))) 9))
      (should (equal (my-calendar-count-weekend-days
              (calendar-absolute-from-gregorian '(4 28 2014))
              (calendar-absolute-from-gregorian '(5 2 2014))) 0))
      (should (equal (my-calendar-count-weekend-days
              (calendar-absolute-from-gregorian '(2 27 2004))
              (calendar-absolute-from-gregorian '(2 29 2004))) 2)))
    

    最后,我们需要知道范围内的假期,emacs 在 holiday-in-range 函数中提供了这个!请注意,此函数调用 calendar-holiday-list 来确定要包含哪些假期,因此如果您真的只想搜索 holiday-general-holidaysholiday-local-holidays,则需要适本地设置 calendar-holidays 变量。有关详细信息,请参阅 C-h v calendar-holidays

    现在我们可以将所有这些都包装在一个新的交互函数中,该函数执行上述三个步骤。这本质上是 calendar-count-days-region 的另一个修改版本,它在打印结果之前减去周末和假期(在运行之前请参阅下面的编辑):
    (defun calendar-count-days-region2 ()
      "Count the number of days (inclusive) between point and the mark 
      excluding weekends and holidays."
      (interactive)
      (let* ((d1 (calendar-cursor-to-date t))
             (d2 (car calendar-mark-ring))
             (date1 (calendar-absolute-from-gregorian d1))
             (date2 (calendar-absolute-from-gregorian d2))
             (start-date (if (<  date1 date2) date1 date2))
             (end-date (if (> date1 date2) date1 date2))
             (days (- (my-calendar-count-days d1 d2)
                      (+ (my-calendar-count-weekend-days start-date end-date)
                         (my-calendar-count-holidays-on-weekdays-in-range
                          start-date end-date)))))
        (message "Region has %d workday%s (inclusive)"
                 days (if (> days 1) "s" ""))))
    

    我确信对 lisp/elisp 更了解的人可以大大简化/改进这些示例,但我希望它至少可以作为一个起点。

    实际上,既然我已经完成了它,我希望有人随时出现并指出有一个 emacs 包已经这样做了......

    编辑: DOH!,错误 #001:如果假期是周末,那一天会被删除两次......

    曾经的解决方案是简单地包装 holiday-in-range,这样我们就可以消除已经因周末而被删除的假期:
     (defun my-calendar-count-holidays-on-weekdays-in-range (start end)
      (let ((holidays (holiday-in-range start end))
            (counter 0))
        (dolist (element holidays)
          (let ((day (calendar-day-of-week (car element))))
            (if (and (> day 0)
                     (< day 6))
                (incf counter))))
        counter))
    

    我已经更新了上面的 calendar-count-days-region2 以使用这个新函数。

    关于emacs - 如何计算 Emacs 日历中不包括周末和节假日的天数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23566000/

    相关文章:

    emacs - 在 Emacs 中使用 texcount 来确定 Latex 或 tex 文件的字数(需要可选参数)

    emacs - Emacs 中显示缓冲区 split-height-threshold 和 split-width-threshold 的反向求值顺序

    Emacs 键绑定(bind)中的 Qt Creator

    emacs - 存储在文件中的elisp 代码的结果值?

    emacs - 使用 AUCTeX 在 LaTeX 模式下插入常规双引号

    Emacs - 在视觉标记之间切换?

    sql - 在 Emacs 中,有没有办法从 init.el 以非交互方式调用交互函数?

    Emacs 在 go-mode 下找不到 gofmt

    emacs - 在 Emacs Paredit 中交换括号和方括号

    emacs - 如何在 emacs 中取消绑定(bind) ctrl+c