1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
#' Invalid calendar dates
#'
#' @description
#' This family of functions is for working with _invalid_ calendar dates.
#'
#' Invalid dates represent dates made up of valid individual components, which
#' taken as a whole don't represent valid calendar dates. For example, for
#' [year_month_day()] the following component ranges are valid:
#' `year: [-32767, 32767]`, `month: [1, 12]`, `day: [1, 31]`.
#' However, the date `2019-02-31` doesn't exist even though it is made up
#' of valid components. This is an example of an invalid date.
#'
#' Invalid dates are allowed in clock, provided that they are eventually
#' resolved by using `invalid_resolve()` or by manually resolving them through
#' arithmetic or setter functions.
#'
#' @details
#' Invalid dates must be resolved before converting them to a time point.
#'
#' It is recommended to use `"previous"` or `"next"` for resolving invalid
#' dates, as these ensure that _relative ordering_ among `x` is maintained.
#' This is a often a very important property to maintain when doing time series
#' data analysis. See the examples for more information.
#'
#' @inheritParams rlang::args_dots_empty
#'
#' @param x `[calendar]`
#'
#' A calendar vector.
#'
#' @param invalid `[character(1) / NULL]`
#'
#' One of the following invalid date resolution strategies:
#'
#' - `"previous"`: The previous valid instant in time.
#'
#' - `"previous-day"`: The previous valid day in time, keeping the time of
#' day.
#'
#' - `"next"`: The next valid instant in time.
#'
#' - `"next-day"`: The next valid day in time, keeping the time of day.
#'
#' - `"overflow"`: Overflow by the number of days that the input is invalid
#' by. Time of day is dropped.
#'
#' - `"overflow-day"`: Overflow by the number of days that the input is
#' invalid by. Time of day is kept.
#'
#' - `"NA"`: Replace invalid dates with `NA`.
#'
#' - `"error"`: Error on invalid dates.
#'
#' Using either `"previous"` or `"next"` is generally recommended, as these
#' two strategies maintain the _relative ordering_ between elements of the
#' input.
#'
#' If `NULL`, defaults to `"error"`.
#'
#' If `getOption("clock.strict")` is `TRUE`, `invalid` must be supplied and
#' cannot be `NULL`. This is a convenient way to make production code robust
#' to invalid dates.
#'
#' @return
#' - `invalid_detect()`: Returns a logical vector detecting invalid dates.
#'
#' - `invalid_any()`: Returns `TRUE` if any invalid dates are detected.
#'
#' - `invalid_count()`: Returns a single integer containing the number of
#' invalid dates.
#'
#' - `invalid_remove()`: Returns `x` with invalid dates removed.
#'
#' - `invalid_resolve()`: Returns `x` with invalid dates resolved using the
#' `invalid` strategy.
#'
#' @name clock-invalid
#' @examples
#' # Invalid date
#' x <- year_month_day(2019, 04, 30:31, c(3, 2), 30, 00)
#' x
#'
#' invalid_detect(x)
#'
#' # Previous valid moment in time
#' x_previous <- invalid_resolve(x, invalid = "previous")
#' x_previous
#'
#' # Previous valid day, retaining time of day
#' x_previous_day <- invalid_resolve(x, invalid = "previous-day")
#' x_previous_day
#'
#' # Note that `"previous"` retains the relative ordering in `x`
#' x[1] < x[2]
#' x_previous[1] < x_previous[2]
#'
#' # But `"previous-day"` here does not!
#' x_previous_day[1] < x_previous_day[2]
#'
#' # Remove invalid dates entirely
#' invalid_remove(x)
#'
#' y <- year_quarter_day(2019, 1, 90:92)
#' y
#'
#' # Overflow rolls forward by the number of days between `y` and the previous
#' # valid date
#' invalid_resolve(y, invalid = "overflow")
NULL
# ------------------------------------------------------------------------------
#' @rdname clock-invalid
#' @export
invalid_detect <- function(x) {
UseMethod("invalid_detect")
}
#' @export
invalid_detect.default <- function(x) {
stop_clock_unsupported(x)
}
# ------------------------------------------------------------------------------
#' @rdname clock-invalid
#' @export
invalid_any <- function(x) {
UseMethod("invalid_any")
}
#' @export
invalid_any.default <- function(x) {
stop_clock_unsupported(x)
}
# ------------------------------------------------------------------------------
#' @rdname clock-invalid
#' @export
invalid_count <- function(x) {
UseMethod("invalid_count")
}
#' @export
invalid_count.default <- function(x) {
stop_clock_unsupported(x)
}
# ------------------------------------------------------------------------------
#' @rdname clock-invalid
#' @export
invalid_remove <- function(x) {
UseMethod("invalid_remove")
}
#' @export
invalid_remove.default <- function(x) {
stop_clock_unsupported(x)
}
#' @export
invalid_remove.clock_calendar <- function(x) {
x[!invalid_detect(x)]
}
# ------------------------------------------------------------------------------
#' @rdname clock-invalid
#' @export
invalid_resolve <- function(x, ..., invalid = NULL) {
UseMethod("invalid_resolve")
}
#' @export
invalid_resolve.default <- function(x, ..., invalid = NULL) {
stop_clock_unsupported(x)
}
validate_invalid <- function(invalid) {
invalid <- strict_validate_invalid(invalid)
if (!is_string(invalid)) {
abort("`invalid` must be a character vector with length 1.")
}
invalid
}
|