#' Roll scheduled events
#'
#' @description
#' Given a schedule of events, create a new schedule where the events are
#' adjusted according to some rule.
#'
#' * `roll_by()` adjusts the events of a schedule by some incremental period.
#' * `roll_forward()` and `roll_backward()` adjust the events of a schedule
#' to the nth previous/next occurrence of some other scheduled event.
#'
#' @param x The schedule of events to adjust.
#' @param n The increment of the event adjustment.
#'   * For `roll_by()` this is the increment of the `unit` period to adjust the
#'   events by. Eg. `2` to adjust the events two `unit` periods into the future
#'    or `-3` to adjust the events three unit periods into the past.
#'   * For `roll_forward()` and `roll_backward()`, `n` defaults to 1, indicating
#'   the schedule should be adjusted to the `to_schedule` event immediately
#'   preceding/following the events of `x`. If more than 1, will skip n-1
#'   occurrences.
#' @param unit  A character shortcut for a period object. Eg. "year", "years",
#'    "month", "months", "week", "weeks" etc. Can be any value accepted by
#'    `lubridate::period()`.
#' @param to_schedule A schedule to which events can be rolled.
#' @param .p Optionally, a schedule to use for limiting the adjustment
#' performed on `x`. Events falling on `.p` will be adjusted. Events not
#' falling on `.p` will be returned unadjusted in the output schedule. Leave
#' `NULL` (the default) to adjust all the events of `x`.
#' * Eg. `roll_forward(x, to_schedule = on_wday("Sun"))` rolls the events of `x`
#' to the next Sunday.
#'
#' @return A schedule object.
#' @examples
#'
#' library(lubridate, warn.conflicts = FALSE)
#' library(magrittr, warn.conflicts = FALSE)
#'
#' # Imagine you get paid on the 25th of the month
#'
#' on_payday <- on_mday(25)
#'
#' schedule_days(on_payday, during = 2000)
#'
#' # Except if your payday falls on a weekend, in which case it moves to the
#' # next weekday
#'
#' on_payday %>%
#'   roll_forward(to = on_weekday(), .p = on_weekend()) %>%
#'   schedule_days(during = 2000)
#'
#' # For some people payday may adjust to the previous weekday if it falls on
#' # a weekend
#'
#' on_payday %>%
#'   roll_backward(to = on_weekday(), .p = on_weekend()) %>%
#'   schedule_days(during = 2000)
#'
#' # Imagine the garbage truck normally comes every Monday, but if
#' # Monday is a state holiday, then it comes on Tuesday instead.
#'
#' on_labor_day <-
#'   on_first(on_wday("Mon"), within_given = "month") %>%
#'   only_occur(in_month("Sep"))
#'
#' on_christmas_day <- only_occur(in_month("Dec"), on_mday(25))
#'
#' on_non_working_day <-
#'   on_weekend() %>%
#'   also_occur(on_labor_day) %>%
#'   also_occur(on_christmas_day)
#'
#' on_my_business_day <- dont_occur(on_non_working_day)
#'
#' on_normal_trash_day <- on_wday("Mon")
#'
#' on_trash_day <-
#'   on_normal_trash_day %>%
#'   roll_forward(to_schedule = on_my_business_day, .p = on_non_working_day)
#'
#' # A Monday in September
#' happen(on_normal_trash_day, ymd("2019-09-09"))
#' happen(on_trash_day, ymd("2019-09-09"))
#'
#' # Labor Day Monday should not be trash day
#' happen(on_normal_trash_day, ymd("2019-09-02"))
#' happen(on_trash_day, ymd("2019-09-02"))
#'
#' # The day after Labor Day Monday is trash day
#' happen(on_normal_trash_day, ymd("2019-09-03"))
#' happen(on_trash_day, ymd("2019-09-03"))
#'
#' # Say that a trash inspector always comes the day after trash day, whatever
#' # day that happens to be.
#'
#' on_inspection_day <-
#'   on_trash_day %>%
#'   roll_by(1, "day")
#'
#' # Inspector comes on a Tuesday in September
#' happen(on_inspection_day, ymd("2019-09-10"))
#'
#' # Inspector doesn't come on that Wednesday
#' happen(on_inspection_day, ymd("2019-09-11"))
#'
#' # Inspector doesn't come on the Tuesday after Labor Day Monday
#' happen(on_inspection_day, ymd("2019-09-03"))
#'
#' # Inspector does come on that Wednesday
#' happen(on_inspection_day, ymd("2019-09-04"))
#' @export
roll_by <- function(x, n, unit, .p = NULL){
  my_schedule <- x
  if(class(unit) == "character"){
    unit_freq <- lubridate::period(n, units = unit)
    date_test <- function(date){
      adjusted_date <- date %m-% unit_freq
      result <- happen(my_schedule, adjusted_date)
      if(!is.null(.p)){
        is_applicable <- happen(.p, adjusted_date)
        result <- result & is_applicable
        }
      result
      }
    }
  out <- list(date_test = date_test)
  out$n_terms <- 1
  class(out) <- "schedule"
  if(!is.null(.p)){
    applicable_schedule <- only_occur(my_schedule, dont_occur(.p))
    out <- also_occur(out, applicable_schedule)
  }
  out
}
#' @rdname roll_by
#' @export
roll_forward <- function(x, to_schedule, n = 1, .p = NULL){
  my_schedule <- x
  date_test <- function(date){
    candidate_dates <- date
    rolling_dates <- candidate_dates
    need_rolling <- !happen(my_schedule, candidate_dates)
    n_counter <- rep_len(0L, length(candidate_dates))
    while(any(need_rolling)){
      is_applicable <- happen(to_schedule, rolling_dates)
      n_counter[need_rolling & is_applicable] <-
        n_counter[need_rolling & is_applicable] + 1L
      rolling_dates[need_rolling] <- rolling_dates[need_rolling] %m-% days(1)
      need_rolling <- !happen(my_schedule, rolling_dates)
    }
    on_target_candidates <- happen(to_schedule, candidate_dates)
    target_counter <- n_counter == n
    is_successful_candidate <- on_target_candidates & target_counter
    successful_candidates <- candidate_dates[is_successful_candidate]
    result <- date %in% successful_candidates
    if(!is.null(.p)){
      is_applicable <- happen(.p, rolling_dates)
      result <- result & is_applicable
    }
    result
  }
  out <- list(date_test = date_test)
  out$n_terms <- 1
  class(out) <- "schedule"
  if(!is.null(.p)){
    applicable_schedule <- only_occur(my_schedule, dont_occur(.p))
    out <- also_occur(out, applicable_schedule)
  }
  out
}
#' @rdname roll_by
#' @export
roll_backward <- function(x, to_schedule, n = 1, .p = NULL){
  my_schedule <- x
  date_test <- function(date){
    candidate_dates <- date
    rolling_dates <- candidate_dates
    need_rolling <- !happen(my_schedule, candidate_dates)
    n_counter <- rep_len(0L, length(candidate_dates))
    while(any(need_rolling)){
      is_applicable <- happen(to_schedule, rolling_dates)
      n_counter[need_rolling & is_applicable] <-
        n_counter[need_rolling & is_applicable] + 1L
      rolling_dates[need_rolling] <- rolling_dates[need_rolling] %m+% days(1)
      need_rolling <- !happen(my_schedule, rolling_dates)
    }
    on_target_candidates <- happen(to_schedule, candidate_dates)
    target_counter <- n_counter == n
    is_successful_candidate <- on_target_candidates & target_counter
    successful_candidates <- candidate_dates[is_successful_candidate]
    result <- date %in% successful_candidates
    if(!is.null(.p)){
      is_applicable <- happen(.p, rolling_dates)
      result <- result & is_applicable
    }
    result
  }
  out <- list(date_test = date_test)
  out$n_terms <- 1
  class(out) <- "schedule"
  if(!is.null(.p)){
    applicable_schedule <- only_occur(my_schedule, dont_occur(.p))
    out <- also_occur(out, applicable_schedule)
  }
  out
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.