'Second to last Wednesday of month in R

In R, how can I produce a list of dates of all 2nd to last Wednesdays of the month in a specified date range? I've tried a few things but have gotten inconsistent results for months with five Wednesdays.



Solution 1:[1]

To generate a regular sequence of dates you can use seq with dates for parameter from and to. See the seq.Date documentation for more options.

Create a data frame with the date, the month and weekday. And then obtain the second to last wednesday for each month with the help of aggregate.

day_sequence = seq(as.Date("2020/1/1"), as.Date("2020/12/31"), "day")

df = data.frame(day = day_sequence, 
                month = months(day_sequence), 
                weekday = weekdays(day_sequence))

#Filter only wednesdays
df = df[df$weekday == "Wednesday",]

result = aggregate(day ~ month, df, function(x){head(tail(x,2),1)})

tail(x,2) will return the last two rows, then head(.., 1) will give you the first of these last two.

Result:

       month        day
1      April 2020-04-22
2     August 2020-08-19
3   December 2020-12-23
4   February 2020-02-19
5    January 2020-01-22
6       July 2020-07-22
7       June 2020-06-17
8      March 2020-03-18
9        May 2020-05-20
10  November 2020-11-18
11   October 2020-10-21
12 September 2020-09-23

Solution 2:[2]

There are probably simpler ways of doing this but the function below does what the question asks for. it returns a named vector of days such that

  • They are between from and to.
  • Are weekday day, where 1 is Monday.
  • Are n to last of the month.

By n to last I mean the nth counting from the end of the month.

whichWeekday <- function(from, to, day, n, format = "%Y-%m-%d"){
  from <- as.Date(from, format = format)
  to <- as.Date(to, format = format)
  day <- as.character(day)
  d <- seq(from, to, by = "days")
  m <- format(d, "%Y-%m")
  f <- c(TRUE, m[-1] != m[-length(m)])
  f <- cumsum(f)
  wed <- tapply(d, f, function(x){
    i <- which(format(x, "%u") == day)
    x[ tail(i, n)[1] ]
  })
  y <- as.Date(wed, origin = "1970-01-01")
  setNames(y, format(y, "%Y-%m"))
}

whichWeekday("2019-01-01", "2020-03-31", 4, 2)
#     2019-01      2019-02      2019-03      2019-04      2019-05 
#"2019-01-23" "2019-02-20" "2019-03-20" "2019-04-17" "2019-05-22" 
#     2019-06      2019-07      2019-08      2019-09      2019-10 
#"2019-06-19" "2019-07-24" "2019-08-21" "2019-09-18" "2019-10-23" 
#     2019-11      2019-12      2020-01      2020-02      2020-03 
#"2019-11-20" "2019-12-18" "2020-01-22" "2020-02-19" "2020-03-18" 

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 R. Schifini
Solution 2