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

时间:2014-05-09 13:34:16

标签: emacs elisp

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

1 个答案:

答案 0 :(得分:3)

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

  • 统计某个地区的日子
  • 减去周末的日子
  • 减去假期

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步,我找不到任何内置函数来计算日期范围的周末天数(令人惊讶!)。幸运的是,在使用绝对日期时,这可能/非常简单。这是一个非常天真的尝试,它只是循环遍历范围内的所有绝对日期,并寻找星期六和星期六。周日:

(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!,Bug#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以使用此新功能。