在Emacs日历中,可以使用运行命令M-=
的{{1}}计算两个日期之间的天数(包括开始日期和结束日期)。我如何计算不包括周末(周六和周日)的天数以及来自变量的定义假期:calendar-count-days-region
和holiday-general-holidays
?
答案 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-holidays
和holiday-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
以使用此新功能。