Quarterly rules are the “hardest” to create in almanac because thespecification that it is built on top of, theRFC-5545 iCalendarSpec does not include quarterly as a frequency. The rationale forthis is that it can always be built from a combination of monthly oryearly rules, if a bit clunky. The goal of this vignette is just to showa few examples of quarterly rules.
This one is particularly easy, but the reason is a bit misleading.Let’s show it first:
on_first_day_of_quarter<-monthly(since ="2000-01-01")%>%recur_on_interval(3)%>%recur_on_day_of_month(1)alma_search("2000-01-01","2002-01-01", on_first_day_of_quarter)#> [1] "2000-01-01" "2000-04-01" "2000-07-01" "2000-10-01" "2001-01-01"#> [6] "2001-04-01" "2001-07-01" "2001-10-01" "2002-01-01"Whenever you need the “first” event per quarter, there is a goodchance that that event will fall in the first month of the quarter, asis the case here. Whenever you have a guarantee that your event falls inthe first month of the quarter, and can be computed from the start ofthat month, you can probably just usemonthly() %>% recur_on_interval(3) to get your quarterlyvalues.
Before moving on to more complex examples, I want to show the generalway to change the start of the fiscal year. This means that rather thanstarting the quarter on January, we could start it on March. Rememberthat thesince date is used as an anchor date for thingslikerecur_on_interval(), so if we chose asince date in March, then we could recur with a differentfiscal calendar.
on_first_day_of_quarter_march_start<-monthly(since ="2000-03-01")%>%recur_on_interval(3)%>%recur_on_day_of_month(1)alma_search("2000-01-01","2002-01-01", on_first_day_of_quarter_march_start)#> [1] "2000-03-01" "2000-06-01" "2000-09-01" "2000-12-01" "2001-03-01"#> [6] "2001-06-01" "2001-09-01" "2001-12-01"As a more general case of the first example, what happens if we wantto compute the N-th day of the quarter, from either the front or theback? Unlike the first example, we no longer have the guarantee that thedate will be in the first month, and if we count from the back we reallyneed the entire set of 3 months that make up the quarter to countcorrectly.
The secret here is to break the rule into 4 smaller rules, one perquarter, which you can then combine into 1 larger quarterly runion.Let’s start with a concrete example in Q1. How can we get the 60th dayof the quarter?
on_60th_day_of_q1<-yearly()%>%recur_on_month_of_year(1:3)%>%recur_on_day_of_month(1:31)%>%recur_on_position(60)alma_search("2000-01-01","2002-01-01", on_60th_day_of_q1)#> [1] "2000-02-29" "2001-03-01"This breaks down as follows:
Use ayearly() frequency rather than amonthly() one.
Recur on the 3 months that make up the first quarter.
Recur on all days of the month (it isn’t a problem if a monthdoesn’t have day 30 or 31).
This gives us access to all ~90 days in the quarter (the exactnumber varies per quarter). Withrecur_on_position() we cantake thenth day of that set.
Usingyearly() rather thanmonthly() isrequired forrecur_on_position() to work correctly.recur_on_position() takes then-th position ofthe set,within the frequency. So if we had chosen monthly itwould try and take the 60th position within the monthly set, which isn’twhat we wanted.
The rest of the quarterly rules are straightforward from here. Wejust change the month of year values. To make this more usable, I’llalso wrap it in a parameterized function, and go ahead and construct thecombined runion object from the four pieces.
make_on_nth_doq<-function(since ="1970-01-01",nth = 1L) { all_days<-1:31 on_nth_day_of_q1<-yearly(since = since)%>%recur_on_month_of_year(1:3)%>%recur_on_day_of_month(all_days)%>%recur_on_position(nth) on_nth_day_of_q2<-yearly(since = since)%>%recur_on_month_of_year(4:6)%>%recur_on_day_of_month(all_days)%>%recur_on_position(nth) on_nth_day_of_q3<-yearly(since = since)%>%recur_on_month_of_year(7:9)%>%recur_on_day_of_month(all_days)%>%recur_on_position(nth) on_nth_day_of_q4<-yearly(since = since)%>%recur_on_month_of_year(10:12)%>%recur_on_day_of_month(all_days)%>%recur_on_position(nth) on_nth_doq<-runion( on_nth_day_of_q1, on_nth_day_of_q2, on_nth_day_of_q3, on_nth_day_of_q4 ) on_nth_doq}Let’s give it a whirl.
on_60th_doq<-make_on_nth_doq(since ="2000-01-01",nth =60)alma_search("2000-01-01","2002-01-01", on_60th_doq)#> [1] "2000-02-29" "2000-05-30" "2000-08-29" "2000-11-29" "2001-03-01"#> [6] "2001-05-30" "2001-08-29" "2001-11-29"It can also select days from the end of the quarter, for example, thelast day in the quarter:
on_last_doq<-make_on_nth_doq(since ="2000-01-01",nth =-1)alma_search("2000-01-01","2002-01-01", on_last_doq)#> [1] "2000-03-31" "2000-06-30" "2000-09-30" "2000-12-31" "2001-03-31"#> [6] "2001-06-30" "2001-09-30" "2001-12-31"This general strategy of using a base rule ofyearly() %>% recur_on_month_of_year(), plus some usageofrecur_on_position() is how I have solved most of thequarterly problems I can think of.
To showcase this strategy again, let’s figure out how to get the nthweek day of the quarter. Again, start with Q1 first, this time computingthe 6th Monday of Q1.
since<-"2000-01-01"day<-"Monday"nth<-6on_6th_monday_of_q1<-yearly(since = since)%>%recur_on_month_of_year(1:3)%>%recur_on_day_of_week(day)%>%recur_on_position(nth)alma_search("2000-01-01","2002-01-01", on_6th_monday_of_q1)#> [1] "2000-02-07" "2001-02-05"Multiple week days can be used here.
since<-"2000-01-01"day<-c("Monday","Tuesday")nth<-19on_19th_monday_or_tuesday_of_q1<-yearly(since = since)%>%recur_on_month_of_year(1:3)%>%recur_on_day_of_week(day)%>%recur_on_position(nth)alma_search("2000-01-01","2002-01-01", on_19th_monday_or_tuesday_of_q1)#> [1] "2000-03-06" "2001-03-05"Now generalize:
make_on_nth_day_of_week_of_the_quarter<-function(since ="1970-01-01",day ="Monday",nth = 1L) { on_nth_of_q1<-yearly(since = since)%>%recur_on_month_of_year(1:3)%>%recur_on_day_of_week(day)%>%recur_on_position(nth) on_nth_of_q2<-yearly(since = since)%>%recur_on_month_of_year(4:6)%>%recur_on_day_of_week(day)%>%recur_on_position(nth) on_nth_of_q3<-yearly(since = since)%>%recur_on_month_of_year(7:9)%>%recur_on_day_of_week(day)%>%recur_on_position(nth) on_nth_of_q4<-yearly(since = since)%>%recur_on_month_of_year(10:12)%>%recur_on_day_of_week(day)%>%recur_on_position(nth) on_nth_of_the_quarter<-runion( on_nth_of_q1, on_nth_of_q2, on_nth_of_q3, on_nth_of_q4 ) on_nth_of_the_quarter}on_last_friday_of_the_quarter<-make_on_nth_day_of_week_of_the_quarter(since ="2000-01-01",day ="Friday",nth =-1)fridays<-alma_search("2000-01-01","2002-01-01", on_last_friday_of_the_quarter)fridays#> [1] "2000-03-31" "2000-06-30" "2000-09-29" "2000-12-29" "2001-03-30"#> [6] "2001-06-29" "2001-09-28" "2001-12-28"wday(fridays,label =TRUE)#> [1] Fri Fri Fri Fri Fri Fri Fri Fri#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < SatRemember that each of these results are just rsets that can becombined with other rules if you need to create more complex quarterlystrategies. For example, let’s take the “last Friday of the quarter”runion and combine it with a rule for “on every Wednesday”.
on_wednesdays<-weekly()%>%recur_on_day_of_week("Wednesday")on_last_friday_of_quarter_or_wednesdays<-runion( on_wednesdays, on_last_friday_of_the_quarter)last_friday_or_wednesdays<-alma_search("2000-01-01","2002-01-01", on_last_friday_of_quarter_or_wednesdays)last_friday_or_wednesdays[1:15]#> [1] "2000-01-05" "2000-01-12" "2000-01-19" "2000-01-26" "2000-02-02"#> [6] "2000-02-09" "2000-02-16" "2000-02-23" "2000-03-01" "2000-03-08"#> [11] "2000-03-15" "2000-03-22" "2000-03-29" "2000-03-31" "2000-04-05"wday(last_friday_or_wednesdays[1:15],label =TRUE)#> [1] Wed Wed Wed Wed Wed Wed Wed Wed Wed Wed Wed Wed Wed Fri Wed#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat