A need that I sometimes have is to know how many business days are there in a given month. It is not difficult to code a suitable function, but it is probably worth knowing that you really don’t have to: Emacs has you covered! Well, almost…
It turns out that Calc has two functions operating on business days – badd
, which may take a date form and an integer and evaluates to the date “that many business days after the given date” (it is actually a bit more general than that, see the docs for the full story), and bsub
, which takes two dates and calculates how many business days are there between them (again, this is a simplification for the purpose of this blog post).
Using the calc-eval
function I already mentioned six years ago (time flies, doesn’t it?), we can find out how many business days there are in July 2021: M-: (calc-eval "bsub(<2021-08-01>,<2021-07-01>)")
yields 22
. (There’s also an example on the Emacs Wiki.)
Of course, the obvious problem is that some days are holidays and should not be counted. Emacs being Emacs, the list of holidays is (of course) configurable. The issue with this is that the list is stored in a Calc variable called Holidays
, and it is not immediately obvious how to use it from your Lisp programs. The format of that variable is pretty complicated – and hence also quite general – but it is easy to add some day, say November 1st this year (Nov 1 is a national holiday in Poland), to it: (setq var-Holidays (calc-eval
"[sat,sun,<2021-11-01>]" 'raw))
. (Note that the 'raw
argument means that Calc will be using its internal format instead of a string, which – as the manual mentions – is more efficient. If you are brave enough to look up the Internals section of the Calc manual, though, you will find information about Calc’s data formats, and it turns out that it’s not even very complicated. Interestingly, the Holidays
variable by default uses “variables” sat
and sun
which are actually undefined, but that doesn’t matter, since the badd
and bsub
functions only care about the names of these variables.) It turns out that you can do all sorts of things with the Holidays
variable. For example, to mark Nov 1 of every year as a holiday, you can say (setq var-Holidays (calc-eval "[sat,sun,date(y,11,01)]"
'raw))
.
But let’s get back to, well, business. (I plan to explore customizing the holidays in a later post.) Here’s a question: how to count business days in, say, August this year? It’s not that obvious given that Aug 1, 2021 is going to be a Sunday. Should we compute bsub(<2021-09-01>,<2021-08-01>)
or maybe bsub(<2021-08-31>,<2021-07-31>)
? In other words, when computing bsub
, is the first day of the interval counted in and the last day not or vice versa? Apparently it’s neither. The manual says:
The difference between two dates one or both of which fall on holidays equals the number of actual business days between them.
and it seems a little ambiguous, but the takeaway is this: if the first or the last day of the given month is a holiday, we should use the first and last day of the month to compute the number of business days, and if not, we can use the last day of the given month and the last day of the previous one.
This is actually pretty complicated. I will definitely explore this subject in the future, and hopefully a simpler way will emerge…