1

I am trying to calculate business days between two days. Successfully, I calculated the days without Saturday and Sunday from this question(Calculate the number of weekdays between 2 dates in R), and now I am trying to implement national holidays into this code. How can I add national holidays into here?

I used this code to calculate weekdays.

Nweekdays <- function(a, b) {
  sum(!weekdays(seq(a, b, "days")) %in% c("Saturday", "Sunday"))}
itzmurd4
  • 663
  • 10
  • 25
Marie
  • 71
  • 1
  • 6
  • 2
    First step would be specifying which nation's holiday schedule you'd want to use, right? Anyway, the timeDate package has several functions like holidayNYSE() and holidayLONDON() that may help you: https://cran.r-project.org/web/packages/timeDate/timeDate.pdf – Christopher Anderson Aug 07 '17 at 22:12

2 Answers2

4

Updated your function a bit so holidays can be added...

Nweekdays <- function(a, b, holidays, weekend) { 
  possible_days <- seq(a, b, "days")
  # Count all days that are not weekend and
  # are not holidays
  sum(!weekdays(possible_days) %in% weekend & !possible_days %in% holidays)
}

weekend <-  c("Saturday", "Sunday")
holidays <- as.Date(c("2017-12-31", "2017-12-24", "2017-07-04"))
Nweekdays(as.Date("2017-08-01"), as.Date("2017-12-31"), holidays, weekend)
[1] 109

While the Gregorian calendar is pretty global, the definition of weekend and holidays is dependent on country, region, etc.

s_baldur
  • 29,441
  • 4
  • 36
  • 69
  • Thanks! I tried to reflect the caluculation to all the rows, and got "Error in seq.Date(a, b, "days") : 'from' must be of length 1" Do you know any idea of this error? StartDate <- as.Date(df$StartDate) EndDate <- as.Date(df$EndDate) df$Business_Days<- Nweekdays(StartDate, EndDate, holidays, weekend) – Marie Aug 07 '17 at 22:41
  • The function above does what you ask for in original question. Hard to help you more when I don't know the shape of your `data.frame`. – s_baldur Aug 07 '17 at 22:45
2

Having some issues with the bizdays package, I came across this solution. I have tweaked the solutions in two way's, one is an answer to the error Marie from the comments is experiencing.

First improvement: weekend <- c("Saturday", "Sunday") is language dependent, so I changed it to the wday function and use numbers to reference days. Further I added the default to make saturdays and sundays the free days and an option to include the last date or not:

library(lubridate) ## lubridate for wday function
CountWorkdays <- function(from, to, holidays = c(), free = c(7,1), include_last = FALSE) { 
  # Create list of all days
  possible_days <- seq(from, to, "days")
  
  # Include last? If not, remove last item.
  if (!include_last) {
    possible_days <- possible_days[-length(possible_days)]
  }
  
  # Count all days that are not weekend and are not holidays
  return(sum(!wday(possible_days) %in% free & !possible_days %in% holidays))
}

Second improvement: If you want to use this function on a dataframe you can use mapply, sapply or equivalent functions, but you can also vectorise the function and make it accept vectors (then it is also usable in dplyr::mutate function). It is important to set which arguments are considered vectors and which are not, I choose the from and to dates to be vectorised, others are considered equal for every row. (a situation where this might not be the case is when you consider contract working days per row for people the work less then five days a week).

CountWorkdaysV <- Vectorize(CountWorkdays, c("from", "to"))

This last adjustment seems to work, but I am not really sure about performance impacts so check before you adopt this function.

Hope this helps somebody who stumbles upon this older question via Google like I did.

2019, 2020, and 2021 US Federal Holidays from https://www.opm.gov/policy-data-oversight/pay-leave/federal-holidays/

holidays <- as.Date(c("2019-01-01", "2019-01-21", "2019-02-18", "2019-05-27", "2019-07-04", "2019-09-02", "2019-10-14", "2019-11-11", "2019-11-28", "2019-12-25",
                      "2020-01-01", "2020-01-20", "2020-02-17", "2020-05-25", "2020-07-03", "2020-09-07", "2020-10-12", "2020-11-11", "2020-11-26", "2020-12-25",
                      "2021-01-01", "2021-01-18", "2021-01-20", "2021-02-15", "2021-05-31", "2021-06-18", "2021-07-05", "2021-09-06", "2021-10-11", "2021-11-11", "2021-11-25", "2021-12-24"))

Example Use:

CountWorkdaysV(as.Date("2021-01-15"), as.Date("2021-01-31"), holidays = holidays, include_last = TRUE)
# 9 days
M.Viking
  • 5,067
  • 4
  • 17
  • 33
ChrisFlink
  • 21
  • 1