Package 'almanac'

Title: Tools for Working with Recurrence Rules
Description: Provides tools for defining recurrence rules and recurrence sets. Recurrence rules are a programmatic way to define a recurring event, like the first Monday of December. Multiple recurrence rules can be combined into larger recurrence sets. A full holiday and calendar interface is also provided that can generate holidays within a particular year, can detect if a date is a holiday, can respect holiday observance rules, and allows for custom holidays.
Authors: Davis Vaughan [aut, cre], Posit Software, PBC [cph, fnd]
Maintainer: Davis Vaughan <[email protected]>
License: MIT + file LICENSE
Version: 1.0.0.9000
Built: 2024-11-04 03:18:29 UTC
Source: https://github.com/davisvaughan/almanac

Help Index


Date adjustments

Description

This family of ⁠adj_*()⁠ functions encode business logic for common date adjustments. If x falls on an event date, it is adjusted according to the function's adjustment rule. Otherwise it is left untouched.

  • 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.

Usage

adj_following(x, rschedule)

adj_preceding(x, rschedule)

adj_modified_following(x, rschedule)

adj_modified_preceding(x, rschedule)

adj_nearest(x, rschedule)

adj_none(x, rschedule)

Arguments

x

⁠[Date]⁠

A vector of dates.

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

Value

An adjusted vector of Dates.

Examples

# A Saturday
x <- as.Date("1970-01-03")

on_weekends <- weekly() %>% recur_on_weekends()

# Adjust forward to Monday
adj_following(x, on_weekends)

# Adjust backwards to Friday
adj_preceding(x, on_weekends)

# Adjust to nearest non-event date
adj_nearest(x, on_weekends)
adj_nearest(x + 1, on_weekends)

# Sundays, one of which is at the end of the month
sundays <- as.Date(c("2020-05-24", "2020-05-31"))

# Adjust forward, unless that takes us into a new month, in which case we
# adjust backwards.
adj_modified_following(sundays, on_weekends)

# Saturdays, one of which is at the beginning of the month
saturdays <- as.Date(c("2020-08-01", "2020-08-08"))

# Adjust backwards, unless that takes us into a new month, in which
# case we adjust forwards
adj_modified_preceding(saturdays, on_weekends)

Get all events

Description

alma_events() retrieves all of the events in the rschedule's event set.

Usage

alma_events(rschedule, ..., year = NULL)

Arguments

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

...

These dots are for future extensions and must be empty.

year

⁠[NULL / integer]⁠

An optional integer vector of years to limit the returned events to.

Value

A Date vector of events.

Examples

rrule <- daily(since = "1970-01-01", until = "1970-01-05")

alma_events(rrule)

on_christmas <- yearly() %>%
  recur_on_month_of_year("Dec") %>%
  recur_on_day_of_month(25)

alma_events(on_christmas, year = c(2020, 2022))

Check if dates are in an event set

Description

alma_in() checks if x is in the event set of dates defined by the rschedule.

Usage

alma_in(x, rschedule)

Arguments

x

⁠[Date]⁠

A vector of dates.

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

Value

A logical vector the same size as x.

Examples

rrule <- weekly() %>%
  recur_on_day_of_week("Thursday")

# A Thursday and Friday
x <- as.Date("1970-01-01") + 0:1

alma_in(x, rrule)

# Every month, on the 2nd day of the month
rrule2 <- monthly() %>%
  recur_on_day_of_month(2)

# Make a larger set of multiple rules
rb <- runion(rrule, rrule2)

alma_in(x, rb)

Generate the next or previous event

Description

  • alma_next() generates the next event after x.

  • alma_previous() generates the previous event before x.

Usage

alma_next(x, rschedule, inclusive = FALSE)

alma_previous(x, rschedule, inclusive = FALSE)

Arguments

x

⁠[Date]⁠

A vector of dates.

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

inclusive

⁠[logical(1)]⁠

If x is an event, should it be considered the next or previous event?

Value

A Date vector the same size as x.

Examples

on_12th <- monthly() %>% recur_on_day_of_month(12)
on_monday <- weekly() %>% recur_on_day_of_week("Monday")

# On the 12th of the month, or on Mondays
rb <- runion(on_12th, on_monday)

alma_next(c("2019-01-01", "2019-01-11"), rb)
alma_previous(c("2019-01-01", "2019-01-11"), rb)

Generate date sequences

Description

alma_seq() generates a sequence of all dates between from and to, skipping any events defined by the rschedule.

Usage

alma_seq(from, to, rschedule, inclusive = TRUE)

Arguments

from, to

⁠[Date(1)]⁠

Dates defining the range to look for events.

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

inclusive

⁠[logical(1)]⁠

If from or to are events in the rschedule, should they be removed from the sequence?

Value

A vector of dates in the range of ⁠[from, to]⁠, with all events in the rschedule removed.

Examples

on_weekends <- weekly() %>% recur_on_weekends()

# Generate a sequence of all non-weekend dates in Jan-2000
alma_seq("2000-01-01", "2000-01-31", on_weekends)

Step relative to an rschedule

Description

alma_step() is useful for shifting dates by "n business days".

alma_step() steps over a sequence of dates 1 day at a time, for n days. After each step, an adjustment is applied to shift to the next non-event date.

Usage

alma_step(x, n, rschedule)

Arguments

x

⁠[Date]⁠

A vector of dates.

n

⁠[integer]⁠

The number of days to step. Can be negative to step backwards.

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

Details

Imagine you are on a Friday and want to shift forward 2 days using an rrule that marks weekends as events. alma_step() works like this:

  • Step forward 1 day to Saturday.

  • Apply an adjustment of adj_following(), which rolls forward to Monday.

  • Step forward 1 day to Tuesday.

  • Apply an adjustment of adj_following(), but no adjustment is required.

This lends itself naturally to business logic. Two business days from Friday is Tuesday.

Value

A Date vector the same size as x shifted by n steps.

Examples

# Make a rrule for weekends
on_weekends <- weekly() %>%
  recur_on_weekends()

# "Step forward by 2 business days"
# 2019-09-13 is a Friday.
# Here we:
# - Step 1 day to Saturday
# - Adjust to Monday
# - Step 1 day to Tuesday
alma_step("2019-09-13", 2, on_weekends)

# If Monday, 2019-09-16, was a recurring holiday, we could create
# a custom runion and step over that too.
on_09_16 <- yearly() %>%
  recur_on_month_of_year(9) %>%
  recur_on_day_of_month(16)

rb <- runion(on_09_16, on_weekends)

alma_step("2019-09-13", 2, rb)

Default values in almanac

Description

  • almanac_since() represents the default since date used in almanac. It defaults to 1900-01-01, a Monday.

  • almanac_until() represents the default until date used in almanac. It defaults to 2100-01-01, a Friday.

The choice of since and until are somewhat arbitrary, but should generate a useful event set range for most rschedules. If you need to adjust the defaults, then you should supply the since and until arguments directly to the rrule generators, like yearly() and weekly().

The since default is particularly important for weekly recurrence rules, where the since date represents the anchor point to begin counting from. See recur_on_day_of_week() for examples of how to adjust this.

Usage

almanac_since()

almanac_until()

Examples

almanac_since()
almanac_until()

Calendar events

Description

cal_events() returns a data frame of holiday name / event date pairs for a calendar. It is similar to alma_events(), but returns information about the name of the holiday and has specialized behavior related to observed dates when filtering by year.

Usage

cal_events(x, ..., year = NULL, observed = FALSE)

Arguments

x

⁠[rcalendar]⁠

An rcalendar.

...

These dots are for future extensions and must be empty.

year

⁠[integer]⁠

An integer vector of years to filter for.

observed

⁠[FALSE / TRUE]⁠

When filtering for specific years, should the observed date of the holiday be used for filtering purposes? If FALSE, the actual date of the holiday will be used, i.e. the date before any observance adjustments created by hol_observe() have been applied, which is typically desired when filtering for a year's worth of holidays. See the examples.

Value

A two column data frame:

  • name is a character vector of holiday names.

  • date is a Date vector of holiday event dates.

Examples

on_weekends <- weekly() %>%
  recur_on_weekends()

# New Year's Day, observed on the nearest weekday if it falls on a weekend
on_new_years <- hol_new_years_day() %>%
  hol_observe(on_weekends, adj_nearest)

# Christmas, observed on the nearest weekday if it falls on a weekend
on_christmas <- hol_christmas() %>%
  hol_observe(on_weekends, adj_nearest)

cal <- rcalendar(on_new_years, on_christmas)
cal

# In 2010, Christmas fell on a Saturday and was adjusted backwards
cal_events(cal, year = 2010)

# In 2011, New Year's fell on a Saturday and was adjusted backwards.
# Note that the returned date is in 2010, even though we requested holidays
# for 2011, because most people would consider the actual New Year's date of
# 2011-01-01 part of the 2011 set of holidays, even though it was observed in
# 2010.
cal_events(cal, year = 2011)

# If you want to filter by the observed date, set `observed = TRUE`, which
# will move the New Year's Day that was observed in 2010 to the 2010 result
cal_events(cal, year = 2010, observed = TRUE)
cal_events(cal, year = 2011, observed = TRUE)

Calendar matching

Description

cal_match() matches a date in x to a holiday in rcalendar and returns the corresponding holiday name, or NA if it doesn't exist in the calendar.

If a date corresponds to multiple holidays, the holiday that was added to the calendar first is returned.

This function is intended to be similar to base::match().

Usage

cal_match(x, rcalendar)

Arguments

x

⁠[Date]⁠

A date vector to match.

rcalendar

⁠[rcalendar]⁠

A calendar to look for holiday matches in.

Value

A character vector the same size as x.

Examples

cal <- rcalendar(
  hol_christmas(),
  hol_halloween(),
  hol_new_years_day(),
  hol_us_presidents_day()
)

x <- as.Date(c(
  "2019-01-02",
  "2019-12-25",
  "2018-02-19",
  "2018-02-20",
  "2020-10-31"
))

cal_match(x, cal)

Calendar names

Description

cal_names() returns the names of the holidays in a calendar.

Usage

cal_names(x)

Arguments

x

⁠[rcalendar]⁠

An rcalendar.

Value

A character vector of holiday names.

Examples

x <- rcalendar(hol_christmas(), hol_new_years_day())
cal_names(x)

US federal calendar

Description

cal_us_federal() is an example calendar that represents the federal holidays in the United States. It makes no attempt to be historically accurate, but instead represents the currently recognized federal holidays. The calendar represents the observed dates of each holiday, rather than the actual dates of each holiday (i.e. if a holiday falls on a Saturday, it is federally observed on the preceding Friday).

Refer to the source code of cal_us_federal() to get a feel for how to build your own personal calendar.

Usage

cal_us_federal(since = NULL, until = NULL)

Arguments

since

⁠[Date(1)]⁠

A lower bound on the event set to generate.

Defaults to almanac_since() if not set.

until

⁠[Date(1)]⁠

An upper bound on the event set to generate.

Defaults to almanac_until() if not set.

Value

An rcalendar.

Examples

cal <- cal_us_federal()

# All 2023 holidays
cal_events(cal, year = 2023)

# Notice that for 2028, `cal_events()` knows that you probably want to
# treat New Year's Day as a 2028 holiday even though it will observed in
# 2027 (because it will be a Saturday and will be rolled back to being
# observed on Friday)
cal_events(cal, year = 2028)

# Were any of these dates on a holiday?
x <- as.Date(c(
  "2023-11-10",
  "2023-10-05",
  "2023-06-19",
  "2023-05-29",
  "2023-05-28"
))

alma_in(x, cal)

# Which one?
cal_match(x, cal)

Calendar additions and removals

Description

  • cal_add() adds an rholiday to an rcalendar.

  • cal_remove() removes an rholiday from an rcalendar by name, either by specifying a character name or an rholiday object with the same name.

Usage

cal_add(x, rholiday)

cal_remove(x, what)

Arguments

x

⁠[rcalendar]⁠

An rcalendar.

rholiday

⁠[rholiday]⁠

An rholiday to add to the rcalendar.

what

⁠[character(1) / rholiday]⁠

The name of a holiday to remove from the rcalendar, or an rholiday object with the corresponding name that you'd like to remove.

Value

A new rcalendar with the holiday added or removed.

Examples

cal <- rcalendar(
  hol_christmas(),
  hol_halloween(),
  hol_new_years_day(),
  hol_us_presidents_day()
)

# Can't forget Easter!
cal %>%
  cal_add(hol_easter())

# Didn't actually need Halloween
cal %>%
  cal_remove(hol_halloween())

# Can remove by name or by object
cal %>%
  cal_remove("Halloween")

Calendar locations

Description

  • cal_next() generates the next holiday after x.

  • cal_previous() generates the previous holiday before x.

If no holiday exists before/after x, a missing row is generated.

Usage

cal_next(x, rcalendar, ..., inclusive = FALSE)

cal_previous(x, rcalendar, ..., inclusive = FALSE)

Arguments

x

⁠[Date]⁠

A vector of dates.

rcalendar

⁠[rcalendar]⁠

An rcalendar.

...

These dots are for future extensions and must be empty.

inclusive

⁠[logical(1)]⁠

If x is an event, should it be considered the next or previous event?

Value

A two column data frame, like cal_events(), which is the same size as x and contains either the next or previous holiday relative to x.

Examples

x <- as.Date(c("2023-04-11", "2023-08-10", "2021-05-06"))
cal <- cal_us_federal()

cal_next(x, cal)
cal_previous(x, cal)

Holiday utility functions

Description

These three functions allow you to tweak existing holidays created by rholiday() so that they more properly align with business calendars. The resulting holidays can then be added into an rcalendar().

  • hol_observe() adjusts a holiday based on when it is actually observed. For example, many holidays that occur on a Saturday are actually observed on the preceding Friday or following Monday.

  • hol_offset() creates a new holiday by offsetting it from an existing one. For example, Boxing Day is the day after Christmas, and the observance of Boxing Day may be dependent on the observance of Christmas (i.e. if Christmas is Sunday, it may be observed on Monday, so Boxing Day would be observed on Tuesday).

  • hol_rename() renames an existing holiday. This is typically useful after a call to hol_offset(), since it doesn't rename the holiday but you may want to give it a different name.

Usage

hol_observe(x, adjust_on, adjustment)

hol_offset(x, by)

hol_rename(x, name)

Arguments

x

⁠[rholiday]⁠

An rholiday.

adjust_on

⁠[rschedule]⁠

An rschedule that determines when the adjustment is to be applied.

adjustment

⁠[function]⁠

An adjustment function to apply to problematic dates. Typically one of the pre-existing adjustment functions, like adj_nearest().

A custom adjustment function must have two arguments x and rschedule. x is the complete vector of dates that possibly need adjustment. rschedule is the rschedule who's event set determines when an adjustment needs to be applied. The function should adjust x as required and return the adjusted Date vector.

by

⁠[integer(1)]⁠

A single integer to offset by.

name

⁠[character(1)]⁠

A new name for the holiday.

Examples

on_weekends <- weekly() %>%
  recur_on_weekends()

# Christmas, adjusted to nearest Friday or Monday if it falls on a weekend
on_christmas <- hol_christmas() %>%
  hol_observe(on_weekends, adj_nearest)

# Boxing Day is the day after Christmas.
# If observed Christmas is a Friday, then observed Boxing Day should be Monday.
# If observed Christmas is a Monday, then observed Boxing Day should be Tuesday.
on_boxing_day <- on_christmas %>%
  hol_offset(1) %>%
  hol_observe(on_weekends, adj_following) %>%
  hol_rename("Boxing Day")

christmas_dates <- alma_events(on_christmas, year = 2010:2015)
boxing_day_dates <- alma_events(on_boxing_day, year = 2010:2015)

data.frame(
  christmas = christmas_dates,
  boxing_day = boxing_day_dates,
  christmas_weekday = lubridate::wday(christmas_dates, label = TRUE),
  boxing_day_weekday = lubridate::wday(boxing_day_dates, label = TRUE)
)

Holidays

Description

This page lists a number of pre-created holidays that can be added to a calendar created with rcalendar(). This list makes no attempt to be comprehensive. If you need to create your own holiday, you can do so with rholiday().

It also makes no attempt to be historically accurate, i.e. Juneteenth was created in 2021, but hol_us_juneteenth() will generate event dates before that. Because rholiday() takes an arbitrary rschedule object, you can always create an rschedule that is historically accurate and use that instead.

Usage

hol_christmas(since = NULL, until = NULL)

hol_christmas_eve(since = NULL, until = NULL)

hol_easter(since = NULL, until = NULL)

hol_good_friday(since = NULL, until = NULL)

hol_halloween(since = NULL, until = NULL)

hol_new_years_day(since = NULL, until = NULL)

hol_new_years_eve(since = NULL, until = NULL)

hol_st_patricks_day(since = NULL, until = NULL)

hol_valentines_day(since = NULL, until = NULL)

hol_us_election_day(since = NULL, until = NULL)

hol_us_fathers_day(since = NULL, until = NULL)

hol_us_independence_day(since = NULL, until = NULL)

hol_us_indigenous_peoples_day(since = NULL, until = NULL)

hol_us_juneteenth(since = NULL, until = NULL)

hol_us_labor_day(since = NULL, until = NULL)

hol_us_martin_luther_king_junior_day(since = NULL, until = NULL)

hol_us_memorial_day(since = NULL, until = NULL)

hol_us_mothers_day(since = NULL, until = NULL)

hol_us_presidents_day(since = NULL, until = NULL)

hol_us_thanksgiving(since = NULL, until = NULL)

hol_us_veterans_day(since = NULL, until = NULL)

Arguments

since

⁠[Date(1)]⁠

A lower bound on the event set to generate.

Defaults to almanac_since() if not set.

until

⁠[Date(1)]⁠

An upper bound on the event set to generate.

Defaults to almanac_until() if not set.

Details

Note that relative holidays, such as New Year's Eve, which is 1 day before New Year's Day, aren't pre-created in a way that allows you to define observance rules for them that depend on the observance rules of the holiday they are relative to. If you need to do this, you should start with the base holiday, here hol_new_years_day(), and use hol_observe() and hol_offset() on that to generate a New Year's Eve holiday that matches your required observance rules. See the examples of hol_offset() for more information.

Examples

on_christmas <- hol_christmas()
on_christmas

# These are like any other rschedule object
alma_events(on_christmas, year = 2020:2025)

# But they can also be added into an rcalendar
cal <- rcalendar(
  on_christmas,
  hol_halloween(),
  hol_new_years_day(),
  hol_us_presidents_day()
)
cal

# Which gives you access to a number of `cal_*()` functions
cal_events(cal, year = 2020:2022)

Create a new rschedule

Description

new_rschedule() is a developer focused tool that is not required for normal usage of almanac. It is only exported to allow other packages to construct new rschedule objects that work with almanac functions prefixed with ⁠alma_*()⁠, like alma_in().

rschedule_events() is a generic function that rschedule subclasses must provide a method for. rschedule_events() should return a Date vector containing the complete ordered set of events in the event set of that rschedule.

Usage

new_rschedule(..., class)

rschedule_events(x)

Arguments

...

⁠[named fields]⁠

Named data fields.

class

⁠[character]⁠

A required subclass.

x

⁠[rschedule subclass]⁠

An object that subclasses rschedule.

Details

An rschedule is an abstract class that rrule and rset both inherit from. The sole functionality of rschedule classes is to provide a method for rschedule_events().

Value

For new_rschedule(), a new rschedule subclass.

For rschedule_events(), a Date vector of events.

Examples

events <- as.Date("1970-01-01")

static <- new_rschedule(
  events = events,
  class = "static_rschedule"
)

# You have to register an `rschedule_events()` method first!
try(alma_events(static))

Create an adjusted rschedule

Description

radjusted() creates a new adjusted rschedule on top of an existing one. The new rschedule contains the same event dates as the existing rschedule, except when they intersect with the dates in the event set of the rschedule, adjust_on. In those cases, an adjustment is applied to the problematic dates to shift them to valid event dates.

This is most useful when creating corporate holiday rschedules. For example, Christmas always falls on December 25th, but if it falls on a Saturday, your company might observe Christmas on the previous Friday. If it falls on a Sunday, you might observe it on the following Monday. In this case, you could construct an rschedule for a recurring event of December 25th, and a second rschedule for weekends. When Christmas falls on a weekend, you would apply an adjustment of adj_nearest() to get the observance date.

Usage

radjusted(rschedule, adjust_on, adjustment)

Arguments

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

adjust_on

⁠[rschedule]⁠

An rschedule that determines when the adjustment is to be applied.

adjustment

⁠[function]⁠

An adjustment function to apply to problematic dates. Typically one of the pre-existing adjustment functions, like adj_nearest().

A custom adjustment function must have two arguments x and rschedule. x is the complete vector of dates that possibly need adjustment. rschedule is the rschedule who's event set determines when an adjustment needs to be applied. The function should adjust x as required and return the adjusted Date vector.

Value

An adjusted rschedule.

Examples

since <- "2000-01-01"
until <- "2010-01-01"

on_christmas <- yearly(since = since, until = until) %>%
  recur_on_month_of_year("Dec") %>%
  recur_on_day_of_month(25)

# All Christmas dates, with no adjustments
alma_events(on_christmas)

on_weekends <- weekly(since = since, until = until) %>%
  recur_on_weekends()

# Now all Christmas dates that fell on a weekend are
# adjusted either forwards or backwards, depending on which
# non-event date was closer
on_adj_christmas <- radjusted(on_christmas, on_weekends, adj_nearest)

alma_events(on_adj_christmas)

Create a recurring calendar

Description

rcalendar() creates a calendar filled with holidays created from one of the existing ⁠hol_*()⁠ holidays (such as hol_christmas()) or from a manually generated holiday created using rholiday(). That calendar can then be used as an rschedule with any other ⁠alma_*()⁠ function (like alma_in()), or with one of the specialized calendar functions, like cal_match() or cal_events().

Usage

rcalendar(...)

Arguments

...

⁠[rholidays]⁠

One or more holidays created from rholiday() or ⁠hol_*()⁠.

Examples

on_earth_day <- yearly() %>%
  recur_on_month_of_year("April") %>%
  recur_on_day_of_month(22) %>%
  rholiday("Earth Day")

cal <- rcalendar(
  hol_christmas(),
  on_earth_day,
  hol_us_independence_day()
)

cal

cal_events(cal, year = 2020:2022)

# Lookup holiday name based on date, if it exists
cal_match(c("2021-12-25", "2021-12-26"), cal)

# Find next holiday
alma_next("2021-12-26", cal)

Create a custom rschedule

Description

rcustom() creates an rschedule from manually defined event dates. This can be useful when combined with runion() and rsetdiff() if you have a set of fixed event dates to forcibly include or exclude from an rschedule.

Usage

rcustom(events)

Arguments

events

⁠[Date]⁠

A vector of event dates.

Value

A custom rschedule.

Examples

include <- rcustom("2019-07-05")
exclude <- rcustom("2019-07-04")

independence_day <- yearly() %>%
  recur_on_month_of_year("July") %>%
  recur_on_day_of_month(4)

# Remove forcibly excluded day
independence_day <- rsetdiff(independence_day, exclude)

# Add forcibly included day
independence_day <- runion(independence_day, include)

alma_search("2018-01-01", "2020-12-31", independence_day)

Control the number of times to recur

Description

recur_for_count() controls the total number of events in the recurrence set.

Usage

recur_for_count(x, n)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

n

⁠[positive integer(1)]⁠

The number of times to recur for.

Details

Remember that the number of times the occurrence has occurred is counted from the since date and is limited by the until date! Adjust them as necessary to get your desired results.

Value

An updated rrule.

Examples

# Using the default `since` date
daily_since_epoch_for_5 <- daily() %>% recur_for_count(5)

alma_search("1969-12-31", "1970-01-25", daily_since_epoch_for_5)

# Changing the `since` date
daily_since_2019_for_5 <- daily(since = "2019-01-01") %>% recur_for_count(5)

alma_search("2018-12-31", "2019-01-25", daily_since_2019_for_5)

# In the case of "impossible" dates, such as 2019-02-31 and 2019-04-31 in the
# example below, they are not added to the total count. Only true event
# dates are counted.
on_31_for_5 <- monthly(since = "2019-01-01") %>%
  recur_on_day_of_month(31) %>%
  recur_for_count(5)

alma_search("2019-01-01", "2020-01-01", on_31_for_5)

Recur on a day of the month

Description

recur_on_day_of_month() recurs on a specific day of the month.

Usage

recur_on_day_of_month(x, day)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

day

⁠[integer]⁠

The days of the month on which to recur. Negative values are allowed, which specify n days from the end of the month.

Details

If the day of the month doesn't exist for that particular month, then it is ignored. For example, if recur_on_day_of_month(30) is set, then it will never generate an event in February.

Value

An updated rrule.

Examples

# When used with a yearly or monthly frequency, `recur_on_day_of_month()`
# expands the number of days in the event set.
on_yearly <- yearly()
on_yearly_day_of_month_1_to_2 <- on_yearly %>% recur_on_day_of_month(1:2)

start <- "1999-01-01"
end <- "2000-06-30"

alma_search(start, end, on_yearly)
alma_search(start, end, on_yearly_day_of_month_1_to_2)

# When used with a daily frequency, `recur_on_day_of_month()` limits the
# number of days in the event set.
on_daily <- daily()
on_daily_day_of_month_1_to_2 <- on_daily %>% recur_on_day_of_month(1:2)

length(alma_search(start, end, on_daily))
length(alma_search(start, end, on_daily_day_of_month_1_to_2))

# Using a negative value is a powerful way to look back from the end of the
# month. This is particularly useful because months don't have the same
# number of days.
on_last_of_month <- monthly() %>% recur_on_day_of_month(-1)

alma_search(start, end, on_last_of_month)

# If you want particular days of the week at the end of the month, you
# could use something like this, which checks if the end of the month
# is also a Friday.
on_last_of_month_that_is_also_friday <- on_last_of_month %>% recur_on_day_of_week("Friday")
alma_search(start, end, on_last_of_month_that_is_also_friday)

# But you probably wanted this, which takes the last friday of the month,
# on whatever day that lands on
on_last_friday_of_month <- monthly() %>% recur_on_day_of_week("Friday", nth = -1)
alma_search(start, end, on_last_friday_of_month)

Recur on a day of the week

Description

  • recur_on_day_of_week() recurs on a specific day of the week.

  • recur_on_weekends() and recur_on_weekdays() are helpers for recurring on weekends and weekdays.

Usage

recur_on_day_of_week(x, day, ..., nth = NULL)

recur_on_weekdays(x)

recur_on_weekends(x)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

day

⁠[integer / character]⁠

Days of the week to recur on. Integer values must be from 1 to 7, with 1 = Monday and 7 = Sunday. This is also allowed to be a full weekday string like "Tuesday", or an abbreviation like "Tues".

...

These dots are for future extensions and must be empty.

nth

⁠[integer / NULL]⁠

Limit to the n-th occurrence of the day in the base frequency. For example, in a monthly frequency, using nth = -1 would limit to the last day in the month. The default of NULL chooses all occurrences.

Details

Multiple week day values are allowed, and nth will be applied to all of them. If you want to apply different nth values to different days of the week, call recur_on_day_of_week() twice with different day values.

It is particularly important to pay attention to the since date when using weekly rules. The day of the week to use comes from the since date, which, by default, is a Monday (1900-01-01). See almanac_since() for more information.

Value

An updated rrule.

Examples

# Using default `since` (1900-01-01, a Monday)
on_weekly_mondays <- weekly()

start <- "1999-01-01" # <- a Friday
end <- "1999-03-01"

# This finds the first Monday, and then continues from there
alma_search(start, end, on_weekly_mondays)

# We start counting from a Friday here
on_weekly_fridays <- weekly(since = start)
alma_search(start, end, on_weekly_fridays)

# Alternatively, we could use `recur_on_day_of_week()` and force a recurrence
# rule on Friday
on_forced_friday <- on_weekly_mondays %>% recur_on_day_of_week("Friday")
alma_search(start, end, on_forced_friday)

# At monthly frequencies, you can use n-th values to look for particular
# week day events
on_first_friday_in_month <- monthly() %>% recur_on_day_of_week("Fri", nth = 1)
alma_search(start, end, on_first_friday_in_month)

# Negative values let you look from the back
on_last_friday_in_month <- monthly() %>% recur_on_day_of_week("Fri", nth = -1)
alma_search(start, end, on_last_friday_in_month)

# At yearly frequencies, this looks for the first sunday of the year
on_first_sunday_in_year <- yearly() %>% recur_on_day_of_week("Sunday", nth = 1)
alma_search(start, end, on_first_sunday_in_year)

# Last week day of the month
last_weekday_of_month <- monthly() %>%
  # Last occurrence of each weekday in the month
  recur_on_day_of_week(c("Mon", "Tue", "Wed", "Thu", "Fri"), nth = -1) %>%
  # Now choose the last one of those in each month
  recur_on_position(-1)

alma_search(start, end, last_weekday_of_month)

Recur on a day of the year

Description

recur_on_day_of_year() recurs on a specific day of the year.

Usage

recur_on_day_of_year(x, day)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

day

⁠[integer]⁠

Days of the year to recur on. Values must be from ⁠[-366, -1]⁠ and ⁠[1, 366]⁠.

Value

An updated rrule.

Examples

library(lubridate, warn.conflicts = FALSE)

on_5th_day_of_year <- yearly() %>% recur_on_day_of_year(5)

alma_search("1999-01-01", "2000-12-31", on_5th_day_of_year)

# Notice that if you use a `since` date that has a day of the year
# after the specified one, it rolls to the next year
on_5th_day_of_year2 <- yearly(since = "1999-01-06") %>% recur_on_day_of_year(5)
alma_search("1999-01-01", "2000-12-31", on_5th_day_of_year2)

# Negative values select from the back, which is useful in leap years
leap_year(as.Date("2000-01-01"))

last_day_of_year <- yearly() %>% recur_on_day_of_year(-1)
last_day_of_year_bad <- yearly() %>% recur_on_day_of_year(365)

alma_search("1999-01-01", "2000-12-31", last_day_of_year)
alma_search("1999-01-01", "2000-12-31", last_day_of_year_bad)

Recur on easter

Description

recur_on_easter() is a special helper to recur on Easter. Easter is particularly difficult to construct a recurrence rule for.

Usage

recur_on_easter(x, offset = NULL)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

offset

[Deprecated]

⁠[integer(1)]⁠

Deprecated in favor of using roffset() directly.

An offset in terms of a number of days on either side of Easter to recur on. This offset must still fall within the same year, otherwise the date will be silently ignored.

Value

An updated rrule.

Examples

on_easter <- yearly() %>% recur_on_easter()

# Rather than:
if (FALSE) {
on_easter_monday <- yearly() %>% recur_on_easter(1)
}

# Please use:
on_easter_monday <- roffset(on_easter, 1)

alma_search("1999-01-01", "2001-01-01", on_easter)

both <- runion(on_easter, on_easter_monday)

alma_search("1999-01-01", "2001-01-01", both)

Recur on an interval

Description

recur_on_interval() adjusts the interval of the base frequency of the recurrence rule. For example, a monthly() rule with an interval of 2 would become "every other month".

Usage

recur_on_interval(x, n)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

n

⁠[positive integer(1)]⁠

The interval on which to recur.

Value

An updated rrule.

Examples

# The default interval is 1
on_monthly <- monthly(since = "1999-01-01")

alma_search("1999-01-01", "1999-06-01", on_monthly)

# Adjust to every other month
on_every_other_month <- on_monthly %>% recur_on_interval(2)

alma_search("1999-01-01", "1999-06-01", on_every_other_month)

# Note that the frequency is limited to "every other month", but you
# can still have multiple events inside a single month
on_every_other_month_on_day_25_or_26 <- on_every_other_month %>%
  recur_on_day_of_month(25:26)

alma_search("1999-01-01", "1999-06-01", on_every_other_month_on_day_25_or_26)

Recur on a month of the year

Description

recur_on_month_of_year() recurs on a specific month of the year.

Usage

recur_on_month_of_year(x, month)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

month

⁠[integer / character]⁠

Months of the year to mark as events. Integer values must be between ⁠[1, 12]⁠. This can also be a full month string like "November", or an abbreviation like "Nov".

Value

An updated rrule.

Examples

# There is a big difference between adding this rule to a `yearly()`
# or `monthly()` frequency, and a `daily()` frequency.

# Limit from every day to every day in February
on_feb_daily <- daily() %>% recur_on_month_of_year("Feb")

# Limit from 1 day per month to 1 day in February
on_feb_monthly <- monthly() %>% recur_on_month_of_year("Feb")

start <- "1999-01-01"
end <- "2001-01-01"

alma_search(start, end, on_feb_daily)

alma_search(start, end, on_feb_monthly)

Recur on a position within a frequency

Description

recur_on_position() let's you have fine tuned control over which element of the set to select within the base frequency.

Usage

recur_on_position(x, n)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

n

⁠[integer]⁠

The positions to select within an intrafrequency set. Negative numbers select from the end of the set.

Value

An updated rrule.

Examples

library(lubridate, warn.conflicts = FALSE)

start <- "1999-01-01"
end <- "1999-05-01"

# You might want the last day of the month that is either a
# Sunday or a Monday, but you don't want to return both.
# This would return both:
on_last_monday_and_sunday <- monthly() %>%
  recur_on_day_of_week(c("Monday", "Sunday"), nth = -1)

alma_search(start, end, on_last_monday_and_sunday)

# To return just the last one, you would select the last value in
# the set, which is computed on a per month basis
on_very_last_monday_or_sunday <- on_last_monday_and_sunday %>%
  recur_on_position(-1)

alma_search(start, end, on_very_last_monday_or_sunday)

wday(alma_search(start, end, on_very_last_monday_or_sunday), label = TRUE)

Recur on a week of the year

Description

recur_on_week_of_year() recurs on a specific week of the year.

Usage

recur_on_week_of_year(x, week)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

week

⁠[integer]⁠

Weeks of the year to recur on. Integer values must be between ⁠[1, 53]⁠ or ⁠[-53, -1]⁠.

Details

Weekly rules are implemented according to the ISO-8601 standard. This requires that the first week of a year is the first one containing at least 4 days of the new year. Additionally, the week will start on the week day specified by recur_with_week_start(), which defaults to Monday.

Value

An updated rrule.

Examples

# Weekly rules are a bit tricky because they are implemented to comply
# with ISO-8601 standards, which require that the first week of the year
# is when there are at least 4 days in that year, and the week starts on
# the week day specified by `recur_with_week_start()` (Monday by default).
on_first_week <- yearly() %>% recur_on_week_of_year(1)

# In 2017:
# - Look at dates 1-4
# - 2017-01-02 is a Monday, so start the first week here
alma_search("2017-01-01", "2017-01-25", on_first_week)

# In 2015:
# - Look at dates 1-4
# - None of these are Monday, so the start of the week is
#   in the previous year
# - Look at 2014 and find the last Monday, 2014-12-29. This is the start of
#   the first week in 2015.
alma_search("2014-12-25", "2015-01-25", on_first_week)

# Say we want the start of the week to be Sunday instead of Monday!

# In 2015:
# - Look at dates 1-4
# - 2015-01-04 is a Sunday, so start the first week here
on_first_week_sun <- yearly() %>%
  recur_on_week_of_year(1) %>%
  recur_with_week_start("Sunday")

alma_search("2014-12-25", "2015-01-25", on_first_week_sun)

Control the start of the week

Description

recur_with_week_start() controls the week day that represents the start of the week. This is important for rules that use recur_on_week_of_year().

The default day of the week to start on is Monday.

Usage

recur_with_week_start(x, day)

Arguments

x

⁠[rrule]⁠

A recurrence rule.

day

⁠[integer(1) / character(1)]⁠

Day of the week to start the week on. Must be an integer value in ⁠[1, 7]⁠, with 1 = Monday and 7 = Sunday. This is also allowed to be a full weekday string like "Tuesday", or an abbreviation like "Tues".

Value

An updated rrule.

Examples

# Weekly rules are a bit tricky because they are implemented to comply
# with ISO-8601 standards, which require that the first week of the year
# is when there are at least 4 days in that year, and the week starts on
# the week day specified by `recur_with_week_start()` (Monday by default).
on_first_week <- yearly() %>% recur_on_week_of_year(1)

# In 2017:
# - Look at dates 1-4
# - 2017-01-02 is a Monday, so start the first week here
alma_search("2017-01-01", "2017-01-25", on_first_week)

# In 2015:
# - Look at dates 1-4
# - None of these are Monday, so the start of the week is
#   in the previous year
# - Look at 2014 and find the last Monday, 2014-12-29. This is the start of
#   the first week in 2015.
alma_search("2014-12-25", "2015-01-25", on_first_week)

# Say we want the start of the week to be Sunday instead of Monday!

# In 2015:
# - Look at dates 1-4
# - 2015-01-04 is a Sunday, so start the first week here
on_first_week_sun <- yearly() %>%
  recur_on_week_of_year(1) %>%
  recur_with_week_start("Sunday")

alma_search("2014-12-25", "2015-01-25", on_first_week_sun)

Create a recurring holiday

Description

rholiday() is used to create custom holidays. It wraps up a holiday name and its corresponding rschedule into a holiday object with special properties.

Holiday objects can be tweaked with hol_rename(), hol_observe(), and hol_offset(), and they can be added to a calendar with rcalendar().

Usage

rholiday(rschedule, name)

Arguments

rschedule

⁠[rschedule]⁠

The recurrence schedule that determines when the holiday occurs.

name

⁠[character(1)]⁠

The name of the holiday. This serves as a unique identifier when adding multiple holidays to an rcalendar().

Examples

on_christmas <- yearly() %>%
  recur_on_month_of_year("Dec") %>%
  recur_on_day_of_month(25)

# Bundle a holiday name with its recurrence schedule to create a holiday
rholiday(on_christmas, "Christmas")

# This is how the built in holiday objects are created
hol_christmas()

Create an offset rschedule

Description

roffset() creates a new rschedule with events that are offset from an existing rschedule by a certain amount. This can be useful when generating relative events like "the day after Christmas."

Usage

roffset(rschedule, by)

Arguments

rschedule

⁠[rschedule]⁠

An rschedule to offset.

by

⁠[integer(1)]⁠

A single integer to offset by.

Value

An offset rschedule.

Examples

on_christmas <- yearly() %>%
  recur_on_month_of_year("Dec") %>%
  recur_on_day_of_month(25)

on_day_after_christmas <- roffset(on_christmas, by = 1)

alma_search("2018-01-01", "2023-01-01", on_day_after_christmas)

# Now what if you want the observed holiday representing the day after
# Christmas?
on_weekends <- weekly() %>% recur_on_weekends()

# Adjust Christmas to the nearest weekday
on_christmas <- radjusted(on_christmas, on_weekends, adj_nearest)

# Offset by 1 and then adjust that to the following weekday.
# We never adjust backwards because that can coincide with the observed day
# for Christmas.
on_day_after_christmas <- on_christmas %>%
  roffset(by = 1) %>%
  radjusted(on_weekends, adj_following)

# Note that:
# - A Christmas on Friday the 24th resulted in a day after Christmas of
#   Monday the 27th
# - A Christmas on Monday the 26th resulted in a day after Christmas of
#   Tuesday the 27th
christmas <- alma_search("2018-01-01", "2023-01-01", on_christmas)
day_after_christmas <- alma_search("2018-01-01", "2023-01-01", on_day_after_christmas)

lubridate::wday(christmas, label = TRUE)
lubridate::wday(day_after_christmas, label = TRUE)

Create a recurrence rule

Description

These functions allow you to create a recurrence rule with a specified frequency. They are the base elements for all recurrence rules. To add to them, use one of the ⁠recur_*()⁠ functions.

  • daily() Recur on a daily frequency.

  • weekly() Recur on a weekly frequency.

  • monthly() Recur on a monthly frequency.

  • yearly() Recur on a yearly frequency.

Usage

daily(since = NULL, until = NULL)

weekly(since = NULL, until = NULL)

monthly(since = NULL, until = NULL)

yearly(since = NULL, until = NULL)

Arguments

since

⁠[Date(1)]⁠

The lower bound on the event set. Depending on the final recurrence rule, pieces of information from this anchor date might be used to generate a complete recurrence rule.

until

⁠[Date(1)]⁠

The upper bound on the event set.

Details

By default, since == "1900-01-01" and until == "2100-01-01", which should capture most use cases well while still being performant. You may need to adjust these dates if you want events outside this range. See almanac_since() and almanac_until() for more information.

In terms of speed, it is generally more efficient if you adjust the since and until date to be closer to the first date in the sequence of dates that you are working with. For example, if you are working with dates in the range of 2019 and forward, adjust the since date to be 2019-01-01 for a significant speed boost.

As the anchor date, events are often calculated relative to this date. As an example, a rule of "on Monday, every other week" would use the since date to find the first Monday to start the recurrence from.

There is no quarterly() recurrence frequency, but this can be accomplished with monthly() %>% recur_on_interval(3). The month to start the quarterly interval from will be pulled from the since date inside monthly(). The default will use a quarterly rule starting in January since the default since date is 1900-01-01. See the examples.

Value

A new empty rrule.

Examples

rrule <- monthly() %>% recur_on_day_of_month(25)

alma_search("1970-01-01", "1971-01-01", rrule)

# Notice that dates before 1900-01-01 are never generated with the defaults!
alma_search("1899-01-01", "1901-01-01", rrule)

# Adjust the `since` date to get access to these dates
rrule_pre_1900 <- monthly(since = "1850-01-01") %>% recur_on_day_of_month(25)
alma_search("1899-01-01", "1901-01-01", rrule_pre_1900)

# A quarterly recurrence rule can be built from
# `monthly()` and `recur_on_interval()`
on_first_of_the_quarter <- monthly() %>%
  recur_on_interval(3) %>%
  recur_on_day_of_month(1)

alma_search("1999-01-01", "2000-04-01", on_first_of_the_quarter)

# Alter the starting quarter by altering the `since` date
on_first_of_the_quarter_starting_in_feb <- monthly(since = "1998-02-01") %>%
  recur_on_interval(3) %>%
  recur_on_day_of_month(1)

alma_search(
  "1999-01-01",
  "2000-04-01",
  on_first_of_the_quarter_starting_in_feb
)

Create a new set-based recurrence schedule

Description

Often, a single rrule will be sufficient. However, more complex recurrence objects can be constructed by combining multiple rschedules into a recurrence set.

There are three types of recurrence sets provided in almanac, each of which construct their event sets by performing a set operation on the underlying events of the rschedules in the set.

  • runion() takes the union.

  • rintersect() takes the intersection.

  • rsetdiff() takes the set difference.

Usage

runion(...)

rintersect(...)

rsetdiff(...)

Arguments

...

⁠[rschedules]⁠

rschedule objects to add to the set.

Details

For rsetdiff(), the event set is created "from left to right" and depends on the order that the rschedules were added to the set.

Value

A runion, rintersect, or rsetdiff.

Examples

since <- "2019-04-01"
until <- "2019-05-31"

on_weekends <- weekly(since = since, until = until) %>%
  recur_on_weekends()

on_25th <- monthly(since = since, until = until) %>%
  recur_on_day_of_month(25)

# On weekends OR the 25th of the month
ru <- runion(on_weekends, on_25th)
alma_events(ru)

# On weekends AND the 25th of the month
ri <- rintersect(on_weekends, on_25th)
alma_events(ri)

# On weekends AND NOT the 25th of the month
rsd1 <- rsetdiff(on_weekends, on_25th)
alma_events(rsd1)

# On the 25th of the month AND NOT the weekend
rsd2 <- rsetdiff(on_25th, on_weekends)
alma_events(rsd2)

Create a new stepper

Description

  • stepper() returns a function that can be used to add or subtract a number of days from a Date, "stepping" over events specified by an rschedule. You supply it the rschedule to step relative to, and then call the returned function with the number of days to step by.

  • workdays() is a convenient stepper for stepping over the weekend.

  • ⁠%s+%⁠ steps forwards.

  • ⁠%s-%⁠ steps backwards.

You must use ⁠%s+⁠ and ⁠%s-%⁠ to control the stepping. + and - will not work due to limitations in R's S3 dispatch system. Alternatively, you can call vctrs::vec_arith() directly, which powers ⁠%s+%⁠ with a correct double dispatch implementation.

Usage

stepper(rschedule)

x %s+% y

x %s-% y

workdays(n, since = NULL, until = NULL)

Arguments

rschedule

⁠[rschedule]⁠

An rschedule, such as an rrule, runion, rintersect, or rsetdiff.

x, y

⁠[objects]⁠

Objects to perform step arithmetic on. Typically Dates or steppers.

n

⁠[integer]⁠

The number of days to step. Can be negative to step backwards.

since

⁠[Date(1)]⁠

The lower bound on the event set. Depending on the final recurrence rule, pieces of information from this anchor date might be used to generate a complete recurrence rule.

until

⁠[Date(1)]⁠

The upper bound on the event set.

Details

Internally, a stepper is just powered by alma_step(), so feel free to use that directly.

Value

  • stepper() returns a function of 1 argument, n, that can be used to step by n days, relative to the rschedule.

  • workdays() return a new stepper object.

  • ⁠%s+%⁠ and ⁠%s-%⁠ return a new shifted Date vector.

Examples

# A Thursday and Friday
x <- as.Date(c("1970-01-01", "1970-01-02"))

# Thursday is stepped forward 1 working day to Friday,
# and then 1 more working day to Monday.
# Friday is stepped forward 1 working day to Monday,
# and then 1 more working day to Tuesday
x %s+% workdays(2)

# ---------------------------------------------------------------------------

on_weekends <- weekly() %>%
  recur_on_weekends()

on_christmas <- yearly() %>%
  recur_on_day_of_month(25) %>%
  recur_on_month_of_year("Dec")

rb <- runion(on_weekends, on_christmas)

workday <- stepper(rb)

# Friday before Christmas, which was on a Monday
friday_before_christmas <- as.Date("2000-12-22")

# Steps over the weekend and Christmas to the following Tuesday
friday_before_christmas %s+% workday(1)

# ---------------------------------------------------------------------------

# Christmas in 2005 was on a Sunday, but your company probably "observed"
# it on Monday. So when you are on the Friday before Christmas in 2005,
# stepping forward 1 working day should go to Tuesday.

# We'll adjust the previous rule for Christmas to roll to the nearest
# non-weekend day, if it happened to fall on a weekend.
on_observed_christmas <- radjusted(
  on_christmas,
  adjust_on = on_weekends,
  adjustment = adj_nearest
)

# Note that the "observed" date for Christmas is the 26th
alma_search("2005-01-01", "2006-01-01", on_observed_christmas)

rb2 <- runion(on_weekends, on_observed_christmas)

workday2 <- stepper(rb2)

friday_before_christmas_2005 <- as.Date("2005-12-23")

# Steps over the weekend and the observed Christmas date
# of 2005-12-26 to Tuesday the 27th.
friday_before_christmas_2005 %s+% workday2(1)