If you have read the introduction vignette,
vignette("almanac")
, then you’ve seen rrules, runions,
rintersects, rsetdiffs, and the functions alma_search()
,
alma_events()
, and alma_in()
. Additionally
included in almanac are a set of tools for adjusting and
shifting dates. These functions are broken down as:
Adjustment functions, such as adj_following()
and
adj_nearest()
Adjusted rschedules
alma_step()
stepper()
“Adjusting” a date defines the behavior of what happens if a date lands on an event in a rschedule’s event set. For example, if you have a date that is currently on Christmas, you could adjust forward to the next non-event date, adjust backwards to the previous non-event date, or adjust to the nearest non-event date. The logic of how to perform these adjustments is encoded into the following adjustment functions:
adj_following()
Choose the first non-event date after x
.
adj_preceding()
Choose the first non-event date before x
.
adj_modified_following()
Choose the first non-event date after x
, unless it falls
in a different month, in which case the first non-event date before
x
is chosen instead.
adj_modified_preceding()
Choose the first non-event date before x
, unless it
falls in a different month, in which case the first non-event date after
x
is chosen instead.
adj_nearest()
Choose the nearest non-event date to x
. If the closest
preceding and following non-event dates are equally far away, the
following non-event date is chosen.
adj_none()
Performs no adjustment and returns x
unchanged.
To use an adjustment function, you’ll need an rschedule that contains
the event set you are interested in adjusting around. Then provide that,
along with the dates you are interested in adjusting, to an
adj_*()
function.
on_christmas <- yearly() %>%
recur_on_day_of_month(25) %>%
recur_on_month_of_year("Dec")
x <- as.Date(c("2019-12-24", "2019-12-25"))
adj_following(x, on_christmas)
#> [1] "2019-12-24" "2019-12-26"
adj_preceding(x, on_christmas)
#> [1] "2019-12-24" "2019-12-24"
The adj_nearest()
function is particularly useful when
you have a date on a weekend and want to roll it back to Friday if it
falls on Saturday but roll it forward to Monday if it falls on
Sunday.
In the introduction vignette, you were introduced to a number of different rschedules. As a reminder, an rschedule is just a general term for an rrule or an rset. There is actually a third type of rschedule in almanac, called radjusted, which is useful when combined with the adjustment functions.
To motivate it, imagine your company deems Christmas to be a holiday. Whenever Christmas rolls around on the 25th of December, you get that day off. But what happens when Christmas falls on a Saturday? What about Sunday? Most corporations will observe a holiday that falls on the weekend on the nearest working day instead of on the weekend date that it actually occurred on.
In almanac, it seems like this would pose a problem. You can create rrules for Christmas and for weekends, but an rset like runion, rintersect, or rsetdiff would perform some kind of set operation on them. What you really need is a way to say: recur on the dates defined by this rschedule, unless it intersects with this second rschedule. In those cases, apply an adjustment to the intersected dates to create valid dates. This is the job of radjusted.
on_adjusted_christmas <- radjusted(
rschedule = on_christmas,
adjust_on = on_weekends,
adjustment = adj_nearest
)
on_adjusted_christmas
#> <radjusted>
#> adjust:
#> <rrule>
#> • frequency: yearly
#> • range: [1900-01-01, 2100-01-01]
#> • month of year: Dec
#> • day of month: 25
#> adjust on:
#> <rrule>
#> • frequency: weekly
#> • range: [1900-01-01, 2100-01-01]
#> • day of week: Sat, and Sun
This is just another rschedule, so it can be used with all of the
other alma_*()
functions we’ve seen so far. For example, we
can confirm that Christmas dates that fall on the weekend are adjusted
appropriately by searching for a few of them.
alma_step()
allows you to take an existing vector of
dates and shift it by a number of days, “stepping over” any events in
the event set defined by an rschedule. This is generally useful for
shifting by “N business days”, where the logic for a business day is
encapsulated in the rschedule.
You can think of alma_step()
as a way to replace
lubridate’s x + days(5)
with
x + business_days(5)
where business_days()
is
specific to your company’s holiday calendar.
In the following example, we shift a Thursday and Friday by 1 working day. Notice that Friday is shifted forward to Monday.
# A Thursday / Friday pair
x <- as.Date(c("2019-12-19", "2019-12-20"))
# Shift by 1 working day, stepping over weekends
step <- alma_step(x, n = 1, rschedule = on_weekends)
data.frame(
x = x,
x_wday = wday(x, label = TRUE),
step = step,
step_wday = wday(step, label = TRUE)
)
#> x x_wday step step_wday
#> 1 2019-12-19 Thu 2019-12-20 Fri
#> 2 2019-12-20 Fri 2019-12-23 Mon
Internally, n
is applied 1 day at a time.
adj_following()
is called after each 1 day shift if
n
is positive, otherwise adj_preceding()
is
called.
To break this down, we’ll analyze the Friday.
Start on 2019-12-20
, a Friday.
Step forward 1 day, to 2019-12-21
, a
Saturday.
Apply adj_following()
, landing us on Monday,
2019-12-23
.
You can shift backwards with a negative n
. It’s also
fully vectorized, and you can use different signs of n
in
the same call. For an example, let’s consider what happens if you start
on a non-event date. Here, we start on a Saturday and Sunday, and apply
a +1 and -1 day shift to them, respectively.
# A Saturday / Sunday pair
x <- as.Date(c("2019-12-21", "2019-12-22"))
step <- alma_step(x, c(1, -1), on_weekends)
data.frame(
x = x,
x_wday = wday(x, label = TRUE),
step = step,
step_wday = wday(step, label = TRUE)
)
#> x x_wday step step_wday
#> 1 2019-12-21 Sat 2019-12-23 Mon
#> 2 2019-12-22 Sun 2019-12-20 Fri
For Saturday, we apply the 1 day shift, landing on Sunday, and then
call adj_following()
, landing on Monday.
For Sunday, we apply the -1 day shift, landing on Saturday, and then
call adj_preceding()
, landing on Friday.
alma_step()
is nice, but it would be really nice to have
something like lubridate’s x + days(5)
syntax, but relative
to an rschedule. Due to some issues with how R’s S3 dispatch system
works with +
, this isn’t exactly replicable with almanac,
but you can get close (lubridate uses R’s S4 object oriented system to
get it to work, but I don’t want to go there).
First off, you need an object the holds information about how to
shift relative to an rschedule. You can create one of these with
stepper()
. The only thing you give stepper()
is the rschedule to step relative to. It returns a function of 1
argument, n
, which you’ll call with the desired number of
days to shift. The resulting object can be added to or subtracted from
your vector of dates. It sounds a little complicated, but hopefully
things will clear up with an example. Let’s reproduce the last example
from the previous section:
Notice the usage of %s+%
. This replaces +
,
and allows you to step forward. There is also a %s-%
for
stepping backwards.
The nice thing about working_day
is that you can
continue to use it on other date vectors.