diff --git a/NAMESPACE b/NAMESPACE index 0d193eeb..981e5254 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -171,6 +171,12 @@ S3method(as_zoned_time,clock_naive_time) S3method(as_zoned_time,clock_sys_time) S3method(as_zoned_time,clock_zoned_time) S3method(as_zoned_time,default) +S3method(calendar_count_between,clock_calendar) +S3method(calendar_count_between,clock_iso_year_week_day) +S3method(calendar_count_between,clock_year_day) +S3method(calendar_count_between,clock_year_month_day) +S3method(calendar_count_between,clock_year_month_weekday) +S3method(calendar_count_between,clock_year_quarter_day) S3method(calendar_end,clock_calendar) S3method(calendar_end,clock_iso_year_week_day) S3method(calendar_end,clock_year_day) @@ -221,6 +227,8 @@ S3method(calendar_widen,clock_year_month_weekday) S3method(calendar_widen,clock_year_quarter_day) S3method(date_ceiling,Date) S3method(date_ceiling,POSIXt) +S3method(date_count_between,Date) +S3method(date_count_between,POSIXt) S3method(date_end,Date) S3method(date_end,POSIXt) S3method(date_floor,Date) @@ -581,6 +589,7 @@ export(as_year_month_day) export(as_year_month_weekday) export(as_year_quarter_day) export(as_zoned_time) +export(calendar_count_between) export(calendar_end) export(calendar_group) export(calendar_leap_year) @@ -595,6 +604,7 @@ export(clock_labels_lookup) export(clock_locale) export(date_build) export(date_ceiling) +export(date_count_between) export(date_end) export(date_floor) export(date_format) @@ -680,6 +690,7 @@ export(sys_time_parse) export(sys_time_parse_RFC_3339) export(time_point_cast) export(time_point_ceiling) +export(time_point_count_between) export(time_point_floor) export(time_point_precision) export(time_point_round) diff --git a/NEWS.md b/NEWS.md index 948e1090..a9405bd9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # clock (development version) +* New `date_count_between()`, `calendar_count_between()`, and + `time_point_count_between()` for computing the number of units of time between + two dates (i.e. the number of years, months, days, or seconds). This has a + number of uses, like computing the age of an individual in years, or + determining the number of weeks that have passed since the start of the year + (#266). + * Integer division is now defined for two duration objects through ` %/% `. This always returns an integer vector, so be aware that using very precise duration objects (like nanoseconds) can easily diff --git a/R/calendar.R b/R/calendar.R index 225c9f26..4b8304e6 100644 --- a/R/calendar.R +++ b/R/calendar.R @@ -711,6 +711,146 @@ calendar_start_end_time <- function(x, x_precision, precision, values) { # ------------------------------------------------------------------------------ +#' Counting: calendars +#' +#' @description +#' `calendar_count_between()` counts the number of `precision` units between +#' `start` and `end` (i.e., the number of years or months). This count +#' corresponds to the _whole number_ of units, and will never return a +#' fractional value. +#' +#' This is suitable for, say, computing the whole number of years or months +#' between two calendar dates, accounting for the day and time of day. +#' +#' Each calendar has its own help page describing the precisions that you can +#' count at: +#' +#' - [year-month-day][year-month-day-count-between] +#' +#' - [year-month-weekday][year-month-weekday-count-between] +#' +#' - [iso-year-week-day][iso-year-week-day-count-between] +#' +#' - [year-quarter-day][year-quarter-day-count-between] +#' +#' - [year-day][year-day-count-between] +#' +#' @section Comparison Direction: +#' The computed count has the property that if `start <= end`, then +#' `start + <= end`. Similarly, if `start >= end`, then +#' `start + >= end`. In other words, the comparison direction between +#' `start` and `end` will never change after adding the count to `start`. This +#' makes this function useful for repeated count computations at +#' increasingly fine precisions. +#' +#' @inheritParams calendar_group +#' +#' @param start,end `[clock_calendar]` +#' +#' A pair of calendar vectors. These will be recycled to their common size. +#' +#' @return An integer representing the number of `precision` units between +#' `start` and `end`. +#' +#' @name calendar-count-between +#' @examples +#' # Number of whole years between these dates +#' x <- year_month_day(2000, 01, 05) +#' y <- year_month_day(2005, 01, 04:06) +#' +#' # Note that `2000-01-05 -> 2005-01-04` is only 4 full years +#' calendar_count_between(x, y, "year") +NULL + +#' @rdname calendar-count-between +#' @export +calendar_count_between <- function(start, + end, + precision, + ..., + n = 1L) { + UseMethod("calendar_count_between") +} + +#' @export +calendar_count_between.clock_calendar <- function(start, + end, + precision, + ..., + n = 1L) { + check_dots_empty() + + if (!is_calendar(end)) { + abort("`end` must be a .") + } + + size <- vec_size_common(start = start, end = end) + + args <- vec_cast_common(start = start, end = end) + args <- vec_recycle_common(!!!args, .size = size) + start <- args[[1]] + end <- args[[2]] + + n <- vec_cast(n, integer(), x_arg = "n") + if (!is_number(n) || n <= 0L) { + abort("`n` must be a single positive integer.") + } + + precision_int <- validate_precision_string(precision) + + if (!calendar_is_valid_precision(start, precision_int)) { + abort(paste0( + "`precision` must be a valid '", calendar_name(start), "' precision." + )) + } + if (calendar_precision_attribute(start) < precision_int) { + abort("Precision of inputs must be at least as precise as `precision`.") + } + + # Core computation to get the difference (pre-adjustment). + # Result is an integer because it represents a count of duration units. + out <- calendar_count_between_compute(start, end, precision) + + # Comparison proxy, truncated to avoid fields already when computing `out` + args <- calendar_count_between_proxy_compare(start, end, precision) + start_proxy <- args[[1]] + end_proxy <- args[[2]] + + if (ncol(start_proxy) == 0L) { + # vctrs bug with vec_compare()? + # https://github.com/r-lib/vctrs/issues/1500 + comparison <- vec_rep(0L, size) + } else { + comparison <- vec_compare(end_proxy, start_proxy) + } + + # - When `start > end` and the non-year portion of `start < end`, add 1 + # - When `start < end` and the non-year portion of `start > end`, subtract 1 + adjustment <- vec_rep(-1L, size) + adjustment[start > end] <- 1L + adjustment[comparison != adjustment] <- 0L + + out <- out + adjustment + + if (n != 1L) { + out <- out %/% n + } + + out +} + +# Internal generic +calendar_count_between_compute <- function(start, end, precision) { + UseMethod("calendar_count_between_compute") +} + +# Internal generic +calendar_count_between_proxy_compare <- function(start, end, precision) { + UseMethod("calendar_count_between_proxy_compare") +} + +# ------------------------------------------------------------------------------ + #' Precision: calendar #' #' `calendar_precision()` extracts the precision from a calendar object. It @@ -956,3 +1096,6 @@ field_index <- function(x) { field(x, "index") } +is_calendar <- function(x) { + inherits(x, "clock_calendar") +} diff --git a/R/date.R b/R/date.R index 112410c9..64e70c35 100644 --- a/R/date.R +++ b/R/date.R @@ -1713,3 +1713,249 @@ check_number_of_supplied_optional_arguments <- function(to, by, total_size) { invisible() } + +# ------------------------------------------------------------------------------ + +#' Counting: date and date-time +#' +#' @description +#' `date_count_between()` counts the number of `precision` units between +#' `start` and `end` (i.e., the number of years or months or hours). This count +#' corresponds to the _whole number_ of units, and will never return a +#' fractional value. +#' +#' This is suitable for, say, computing the whole number of years or months +#' between two dates, accounting for the day and time of day. +#' +#' There are separate help pages for counting for dates and date-times: +#' +#' - [dates (Date)][date-count-between] +#' +#' - [date-times (POSIXct/POSIXlt)][posixt-count-between] +#' +#' @inheritSection calendar_count_between Comparison Direction +#' +#' @inheritParams calendar_count_between +#' +#' @param start,end `[Date / POSIXct / POSIXlt]` +#' +#' A pair of date or date-time vectors. These will be recycled to their common +#' size. +#' +#' @inherit calendar_count_between return +#' +#' @export +#' @examples +#' # See method specific documentation for more examples +#' +#' start <- date_parse("2000-05-05") +#' end <- date_parse(c("2020-05-04", "2020-05-06")) +#' +#' # Age in years +#' date_count_between(start, end, "year") +#' +#' # Number of "whole" months between these dates +#' date_count_between(start, end, "month") +date_count_between <- function(start, end, precision, ..., n = 1L) { + UseMethod("date_count_between") +} + +#' Counting: date +#' +#' @description +#' This is a Date method for the [date_count_between()] generic. +#' +#' `date_count_between()` counts the number of `precision` units between +#' `start` and `end` (i.e., the number of years or months). This count +#' corresponds to the _whole number_ of units, and will never return a +#' fractional value. +#' +#' This is suitable for, say, computing the whole number of years or months +#' between two dates, accounting for the day of the month. +#' +#' _Calendrical based counting:_ +#' +#' These precisions convert to a year-month-day calendar and count while in that +#' type. +#' +#' - `"year"` +#' +#' - `"month"` +#' +#' _Time point based counting:_ +#' +#' These precisions convert to a time point and count while in that type. +#' +#' - `"week"` +#' +#' - `"day"` +#' +#' For dates, whether a calendar or time point is used is not all that +#' important, but is is fairly important for date-times. +#' +#' @inheritSection calendar_count_between Comparison Direction +#' +#' @inheritParams date_count_between +#' +#' @param start,end `[Date]` +#' +#' A pair of date vectors. These will be recycled to their common +#' size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"year"` +#' - `"month"` +#' - `"week"` +#' - `"day"` +#' +#' @inherit date_count_between return +#' +#' @name date-count-between +#' +#' @export +#' @examples +#' start <- date_parse("2000-05-05") +#' end <- date_parse(c("2020-05-04", "2020-05-06")) +#' +#' # Age in years +#' date_count_between(start, end, "year") +#' +#' # Number of "whole" months between these dates. i.e. +#' # `2000-05-05 -> 2020-04-05` is 239 months +#' # `2000-05-05 -> 2020-05-05` is 240 months +#' # Since 2020-05-04 occurs before the 5th of that month, +#' # it gets a count of 239 +#' date_count_between(start, end, "month") +#' +#' # Number of days between +#' date_count_between(start, end, "day") +#' +#' # Number of full 3 day periods between these two dates +#' date_count_between(start, end, "day", n = 3) +#' +#' # Essentially the truncated value of this +#' date_count_between(start, end, "day") / 3 +#' +#' # --------------------------------------------------------------------------- +#' +#' # Breakdown into full years, months, and days between +#' x <- start +#' +#' years <- date_count_between(x, end, "year") +#' x <- add_years(x, years) +#' +#' months <- date_count_between(x, end, "month") +#' x <- add_months(x, months) +#' +#' days <- date_count_between(x, end, "day") +#' x <- add_days(x, days) +#' +#' data.frame( +#' start = start, +#' end = end, +#' years = years, +#' months = months, +#' days = days +#' ) +#' +#' # Note that when breaking down a date like that, you may need to +#' # set `invalid` during intermediate calculations +#' start <- date_build(2019, c(3, 3, 4), c(30, 31, 1)) +#' end <- date_build(2019, 5, 05) +#' +#' # These are 1 month apart (plus a few days) +#' months <- date_count_between(start, end, "month") +#' +#' # But adding that 1 month to `start` results in an invalid date +#' try(add_months(start, months)) +#' +#' # You can choose various ways to resolve this +#' start_previous <- add_months(start, months, invalid = "previous") +#' start_next <- add_months(start, months, invalid = "next") +#' +#' days_previous <- date_count_between(start_previous, end, "day") +#' days_next <- date_count_between(start_next, end, "day") +#' +#' # Resulting in slightly different day values. +#' # No result is "perfect". Choosing "previous" or "next" both result +#' # in multiple `start` dates having the same month/day breakdown values. +#' data.frame( +#' start = start, +#' end = end, +#' months = months, +#' days_previous = days_previous, +#' days_next = days_next +#' ) +date_count_between.Date <- function(start, end, precision, ..., n = 1L) { + check_dots_empty() + + if (!is_Date(end)) { + abort("`end` must be a .") + } + + # Designed to match `add_*()` functions to guarantee that + # if `start <= end`, then `start + <= end` + allowed_precisions_calendar <- c( + PRECISION_YEAR, PRECISION_MONTH + ) + allowed_precisions_naive_time <- c( + PRECISION_WEEK, PRECISION_DAY + ) + allowed_precisions_sys_time <- c( + ) + + date_count_between_impl( + start = start, + end = end, + precision = precision, + n = n, + allowed_precisions_calendar = allowed_precisions_calendar, + allowed_precisions_naive_time = allowed_precisions_naive_time, + allowed_precisions_sys_time = allowed_precisions_sys_time + ) +} + +date_count_between_impl <- function(start, + end, + precision, + n, + allowed_precisions_calendar, + allowed_precisions_naive_time, + allowed_precisions_sys_time) { + precision_int <- validate_precision_string(precision) + + if (precision_int %in% allowed_precisions_calendar) { + start <- as_year_month_day(start) + end <- as_year_month_day(end) + out <- calendar_count_between(start, end, precision, n = n) + return(out) + } + + if (precision_int %in% allowed_precisions_naive_time) { + start <- as_naive_time(start) + end <- as_naive_time(end) + out <- time_point_count_between(start, end, precision, n = n) + return(out) + } + + if (precision_int %in% allowed_precisions_sys_time) { + start <- as_sys_time(start) + end <- as_sys_time(end) + out <- time_point_count_between(start, end, precision, n = n) + return(out) + } + + precisions <- c( + allowed_precisions_calendar, + allowed_precisions_naive_time, + allowed_precisions_sys_time + ) + precisions <- vapply(precisions, precision_to_string, character(1)) + precisions <- encodeString(precisions, quote = "'") + precisions <- paste0(precisions, collapse = ", ") + + abort(paste0("`precision` must be one of: ", precisions, ".")) +} diff --git a/R/gregorian-year-day.R b/R/gregorian-year-day.R index ae598c3c..fd58788a 100644 --- a/R/gregorian-year-day.R +++ b/R/gregorian-year-day.R @@ -915,6 +915,78 @@ calendar_end.clock_year_day <- function(x, precision) { # ------------------------------------------------------------------------------ +#' Counting: year-day +#' +#' This is a year-day method for the [calendar_count_between()] generic. +#' It counts the number of `precision` units between `start` and `end` +#' (i.e., the number of years). +#' +#' @inheritParams calendar-count-between +#' +#' @param start,end `[clock_year_day]` +#' +#' A pair of year-day vectors. These will be recycled to their +#' common size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"year"` +#' +#' @inherit calendar-count-between return +#' +#' @name year-day-count-between +#' +#' @export +#' @examples +#' # Compute an individual's age in years +#' x <- year_day(2001, 100) +#' y <- year_day(2021, c(99, 101)) +#' +#' calendar_count_between(x, y, "year") +#' +#' # Or in a whole number multiple of years +#' calendar_count_between(x, y, "year", n = 3) +calendar_count_between.clock_year_day <- function(start, + end, + precision, + ..., + n = 1L) { + NextMethod() +} + +calendar_count_between_compute.clock_year_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + if (precision == PRECISION_YEAR) { + out <- get_year(end) - get_year(start) + return(out) + } + + abort("`precision` must be one of: 'year'.") +} + +calendar_count_between_proxy_compare.clock_year_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + start <- vec_proxy_compare(start) + end <- vec_proxy_compare(end) + + if (precision >= PRECISION_YEAR) { + start$year <- NULL + end$year <- NULL + } + + list(start = start, end = end) +} + +# ------------------------------------------------------------------------------ + #' Sequences: year-day #' #' @description diff --git a/R/gregorian-year-month-day.R b/R/gregorian-year-month-day.R index c433ba9a..72821898 100644 --- a/R/gregorian-year-month-day.R +++ b/R/gregorian-year-month-day.R @@ -1218,6 +1218,91 @@ calendar_end.clock_year_month_day <- function(x, precision) { # ------------------------------------------------------------------------------ +#' Counting: year-month-day +#' +#' This is a year-month-day method for the [calendar_count_between()] generic. +#' It counts the number of `precision` units between `start` and `end` +#' (i.e., the number of years or months). +#' +#' @inheritParams calendar-count-between +#' +#' @param start,end `[clock_year_month_day]` +#' +#' A pair of year-month-day vectors. These will be recycled to their +#' common size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"year"` +#' - `"month"` +#' +#' @inherit calendar-count-between return +#' +#' @name year-month-day-count-between +#' +#' @export +#' @examples +#' # Compute an individual's age in years +#' x <- year_month_day(2001, 2, 4) +#' today <- year_month_day(2021, 11, 30) +#' calendar_count_between(x, today, "year") +#' +#' # Compute the number of months between two dates, taking +#' # into account the day of the month and time of day +#' x <- year_month_day(2000, 4, 2, 5) +#' y <- year_month_day(2000, 7, c(1, 2, 2), c(3, 4, 6)) +#' calendar_count_between(x, y, "month") +calendar_count_between.clock_year_month_day <- function(start, + end, + precision, + ..., + n = 1L) { + NextMethod() +} + +calendar_count_between_compute.clock_year_month_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + if (precision == PRECISION_YEAR) { + out <- get_year(end) - get_year(start) + return(out) + } + + if (precision == PRECISION_MONTH) { + out <- (get_year(end) - get_year(start)) * 12L + out <- out + (get_month(end) - get_month(start)) + return(out) + } + + abort("`precision` must be one of: 'year', 'month'.") +} + +calendar_count_between_proxy_compare.clock_year_month_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + start <- vec_proxy_compare(start) + end <- vec_proxy_compare(end) + + if (precision >= PRECISION_YEAR) { + start$year <- NULL + end$year <- NULL + } + if (precision >= PRECISION_MONTH) { + start$month <- NULL + end$month <- NULL + } + + list(start = start, end = end) +} + +# ------------------------------------------------------------------------------ + #' Sequences: year-month-day #' #' @description diff --git a/R/gregorian-year-month-weekday.R b/R/gregorian-year-month-weekday.R index 556af364..839b79f0 100644 --- a/R/gregorian-year-month-weekday.R +++ b/R/gregorian-year-month-weekday.R @@ -1105,6 +1105,69 @@ calendar_end.clock_year_month_weekday <- function(x, precision) { # ------------------------------------------------------------------------------ +#' Counting: year-month-weekday +#' +#' This is a year-month-weekday method for the [calendar_count_between()] +#' generic. It counts the number of `precision` units between `start` and `end` +#' (i.e., the number of years or months). +#' +#' @details +#' Remember that year-month-weekday is not comparable when it is `"day"` +#' precision or finer, so this method is only defined for `"year"` and +#' `"month"` precision year-month-weekday objects. +#' +#' @inheritParams calendar-count-between +#' +#' @param start,end `[clock_year_month_weekday]` +#' +#' A pair of year-month-weekday vectors. These will be recycled to their +#' common size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"year"` +#' - `"month"` +#' +#' @inherit calendar-count-between return +#' +#' @name year-month-weekday-count-between +#' +#' @export +#' @examples +#' # Compute the number of months between two dates +#' x <- year_month_weekday(2001, 2) +#' y <- year_month_weekday(2021, c(1, 3)) +#' +#' calendar_count_between(x, y, "month") +#' +#' # Remember that day precision or finer year-month-weekday objects +#' # are not comparable, so this won't work +#' x <- year_month_weekday(2001, 2, 1, 1) +#' try(calendar_count_between(x, x, "month")) +calendar_count_between.clock_year_month_weekday <- function(start, + end, + precision, + ..., + n = 1L) { + NextMethod() +} + +calendar_count_between_compute.clock_year_month_weekday <- function(start, + end, + precision) { + calendar_count_between_compute.clock_year_month_day(start, end, precision) +} + +calendar_count_between_proxy_compare.clock_year_month_weekday <- function(start, + end, + precision) { + calendar_count_between_proxy_compare.clock_year_month_day(start, end, precision) +} + +# ------------------------------------------------------------------------------ + #' Sequences: year-month-weekday #' #' @description diff --git a/R/iso-year-week-day.R b/R/iso-year-week-day.R index 9df4e174..8f360dd9 100644 --- a/R/iso-year-week-day.R +++ b/R/iso-year-week-day.R @@ -936,6 +936,74 @@ calendar_end.clock_iso_year_week_day <- function(x, precision) { # ------------------------------------------------------------------------------ +#' Counting: iso-year-week-day +#' +#' This is an iso-year-week-day method for the [calendar_count_between()] +#' generic. It counts the number of `precision` units between `start` and `end` +#' (i.e., the number of ISO years). +#' +#' @inheritParams calendar-count-between +#' +#' @param start,end `[clock_iso_year_week_day]` +#' +#' A pair of iso-year-week-day vectors. These will be recycled to their +#' common size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"year"` +#' +#' @inherit calendar-count-between return +#' +#' @name iso-year-week-day-count-between +#' +#' @export +#' @examples +#' # Compute the number of whole ISO years between two dates +#' x <- iso_year_week_day(2001, 1, 2) +#' y <- iso_year_week_day(2021, 1, c(1, 3)) +#' calendar_count_between(x, y, "year") +calendar_count_between.clock_iso_year_week_day <- function(start, + end, + precision, + ..., + n = 1L) { + NextMethod() +} + +calendar_count_between_compute.clock_iso_year_week_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + if (precision == PRECISION_YEAR) { + out <- get_year(end) - get_year(start) + return(out) + } + + abort("`precision` must be one of: 'year'.") +} + +calendar_count_between_proxy_compare.clock_iso_year_week_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + start <- vec_proxy_compare(start) + end <- vec_proxy_compare(end) + + if (precision >= PRECISION_YEAR) { + start$year <- NULL + end$year <- NULL + } + + list(start = start, end = end) +} + +# ------------------------------------------------------------------------------ + #' Sequences: iso-year-week-day #' #' @description diff --git a/R/posixt.R b/R/posixt.R index 8207be3c..146438ac 100644 --- a/R/posixt.R +++ b/R/posixt.R @@ -1845,3 +1845,188 @@ date_seq.POSIXt <- function(from, abort("`by` must have a precision of 'year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', or 'second'.") } + +# ------------------------------------------------------------------------------ + +#' Counting: date-times +#' +#' @description +#' This is a POSIXct/POSIXlt method for the [date_count_between()] generic. +#' +#' `date_count_between()` counts the number of `precision` units between +#' `start` and `end` (i.e., the number of years or months). This count +#' corresponds to the _whole number_ of units, and will never return a +#' fractional value. +#' +#' This is suitable for, say, computing the whole number of years or months +#' between two dates, accounting for the day of the month and the time of day. +#' +#' Internally, the date-time is converted to one of the following three clock +#' types, and the counting is done directly on that type. The choice of type is +#' based on the most common interpretation of each precision, but is ultimately +#' a heuristic. See the examples for more information. +#' +#' _Calendrical based counting:_ +#' +#' These precisions convert to a year-month-day calendar and count while in that +#' type. +#' +#' - `"year"` +#' +#' - `"month"` +#' +#' _Naive-time based counting:_ +#' +#' These precisions convert to a naive-time and count while in that type. +#' +#' - `"week"` +#' +#' - `"day"` +#' +#' _Sys-time based counting:_ +#' +#' These precisions convert to a sys-time and count while in that type. +#' +#' - `"hour"` +#' +#' - `"minute"` +#' +#' - `"second"` +#' +#' @inheritSection calendar_count_between Comparison Direction +#' +#' @inheritParams date_count_between +#' +#' @param start,end `[POSIXct / POSIXlt]` +#' +#' A pair of date-time vectors. These will be recycled to their common +#' size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"year"` +#' - `"month"` +#' - `"week"` +#' - `"day"` +#' - `"hour"` +#' - `"minute"` +#' - `"second"` +#' +#' @inherit date_count_between return +#' +#' @name posixt-count-between +#' +#' @export +#' @examples +#' start <- date_time_parse("2000-05-05 02:00:00", zone = "America/New_York") +#' end <- date_time_parse( +#' c("2020-05-05 01:00:00", "2020-05-05 03:00:00"), +#' zone = "America/New_York" +#' ) +#' +#' # Age in years +#' date_count_between(start, end, "year") +#' +#' # Number of "whole" months between these dates. i.e. +#' # `2000-05-05 02:00:00 -> 2020-04-05 02:00:00` is 239 months +#' # `2000-05-05 02:00:00 -> 2020-05-05 02:00:00` is 240 months +#' # Since `2020-05-05 01:00:00` occurs before the 2nd hour, +#' # it gets a count of 239 +#' date_count_between(start, end, "month") +#' +#' # Number of seconds between +#' date_count_between(start, end, "second") +#' +#' # --------------------------------------------------------------------------- +#' # Naive-time VS Sys-time interpretation +#' +#' # The difference between whether `start` and `end` are converted to a +#' # naive-time vs a sys-time comes into play when dealing with daylight +#' # savings. +#' +#' # Here are two times around a 1 hour DST gap where clocks jumped from +#' # 01:59:59 -> 03:00:00 +#' x <- date_time_build(1970, 4, 26, 1, 50, 00, zone = "America/New_York") +#' y <- date_time_build(1970, 4, 26, 3, 00, 00, zone = "America/New_York") +#' +#' # When treated like sys-times, these are considered to be 10 minutes apart, +#' # which is the amount of time that would have elapsed if you were watching +#' # a clock as it changed between these two times. +#' date_count_between(x, y, "minute") +#' +#' # Lets add a 3rd date that is ~1 day ahead of these +#' z <- date_time_build(1970, 4, 27, 1, 55, 00, zone = "America/New_York") +#' +#' # When treated like naive-times, `z` is considered to be at least 1 day ahead +#' # of `x`, because `01:55:00` is after `01:50:00`. This is probably what you +#' # expected. +#' date_count_between(x, z, "day") +#' +#' # If these were interpreted like sys-times, then `z` would not be considered +#' # to be 1 day ahead. That would look something like this: +#' date_count_between(x, z, "second") +#' trunc(date_count_between(x, z, "second") / 86400) +#' +#' # This is because there have only been 83,100 elapsed seconds since `x`, +#' # which isn't a full day's worth (86,400 seconds). But we'd generally +#' # consider `z` to be 1 day ahead of `x` (and ignore the DST gap), so that is +#' # how it is implemented. +#' +#' # You can override this by converting directly to sys-time, then using +#' # `time_point_count_between()` +#' x_st <- as_sys_time(x) +#' x_st +#' +#' z_st <- as_sys_time(z) +#' z_st +#' +#' time_point_count_between(x_st, z_st, "day") +date_count_between.POSIXt <- function(start, end, precision, ..., n = 1L) { + check_dots_empty() + + if (!is_POSIXt(end)) { + abort("`end` must be a .") + } + + start <- to_posixct(start) + end <- to_posixct(end) + + start_zone <- date_zone(start) + end_zone <- date_zone(end) + + if (!identical(start_zone, end_zone)) { + start_zone <- zone_pretty(start_zone) + end_zone <- zone_pretty(end_zone) + + abort(paste0( + "`start` (", start_zone, ") and `end` (", end_zone, ") ", + "must have identical time zones." + )) + } + + precision_int <- validate_precision_string(precision) + + # Designed to match `add_*()` functions to guarantee that + # if `start <= end`, then `start + <= end` + allowed_precisions_calendar <- c( + PRECISION_YEAR, PRECISION_MONTH + ) + allowed_precisions_naive_time <- c( + PRECISION_WEEK, PRECISION_DAY + ) + allowed_precisions_sys_time <- c( + PRECISION_HOUR, PRECISION_MINUTE, PRECISION_SECOND + ) + + date_count_between_impl( + start = start, + end = end, + precision = precision, + n = n, + allowed_precisions_calendar = allowed_precisions_calendar, + allowed_precisions_naive_time = allowed_precisions_naive_time, + allowed_precisions_sys_time = allowed_precisions_sys_time + ) +} diff --git a/R/quarterly-year-quarter-day.R b/R/quarterly-year-quarter-day.R index ed8b4397..b17447c4 100644 --- a/R/quarterly-year-quarter-day.R +++ b/R/quarterly-year-quarter-day.R @@ -1071,6 +1071,91 @@ calendar_end.clock_year_quarter_day <- function(x, precision) { # ------------------------------------------------------------------------------ +#' Counting: year-quarter-day +#' +#' This is a year-quarter-day method for the [calendar_count_between()] generic. +#' It counts the number of `precision` units between `start` and `end` (i.e., +#' the number of years or quarters). +#' +#' @inheritParams calendar-count-between +#' +#' @param start,end `[clock_year_quarter_day]` +#' +#' A pair of year-quarter-day vectors. These will be recycled to their +#' common size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"year"` +#' - `"quarter"` +#' +#' @inherit calendar-count-between return +#' +#' @name year-quarter-day-count-between +#' +#' @export +#' @examples +#' # Compute the number of whole quarters between two dates +#' x <- year_quarter_day(2020, 3, 91) +#' y <- year_quarter_day(2025, 4, c(90, 92)) +#' calendar_count_between(x, y, "quarter") +#' +#' # Note that this is not always the same as the number of whole 3 month +#' # periods between two dates +#' x <- as_year_month_day(x) +#' y <- as_year_month_day(y) +#' calendar_count_between(x, y, "month", n = 3) +calendar_count_between.clock_year_quarter_day <- function(start, + end, + precision, + ..., + n = 1L) { + NextMethod() +} + +calendar_count_between_compute.clock_year_quarter_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + if (precision == PRECISION_YEAR) { + out <- get_year(end) - get_year(start) + return(out) + } + + if (precision == PRECISION_QUARTER) { + out <- (get_year(end) - get_year(start)) * 4L + out <- out + (get_quarter(end) - get_quarter(start)) + return(out) + } + + abort("`precision` must be one of: 'year', 'quarter'.") +} + +calendar_count_between_proxy_compare.clock_year_quarter_day <- function(start, + end, + precision) { + precision <- validate_precision_string(precision) + + start <- vec_proxy_compare(start) + end <- vec_proxy_compare(end) + + if (precision >= PRECISION_YEAR) { + start$year <- NULL + end$year <- NULL + } + if (precision >= PRECISION_QUARTER) { + start$quarter <- NULL + end$quarter <- NULL + } + + list(start = start, end = end) +} + +# ------------------------------------------------------------------------------ + #' Sequences: year-quarter-day #' #' @description diff --git a/R/time-point.R b/R/time-point.R index 1a99c725..0d20c8b0 100644 --- a/R/time-point.R +++ b/R/time-point.R @@ -887,6 +887,149 @@ is_advance <- function(x) { # ------------------------------------------------------------------------------ +#' Counting: time point +#' +#' @description +#' `time_point_count_between()` counts the number of `precision` units +#' between `start` and `end` (i.e., the number of days or hours). This count +#' corresponds to the _whole number_ of units, and will never return a +#' fractional value. +#' +#' This is suitable for, say, computing the whole number of days between two +#' time points, accounting for the time of day. +#' +#' @details +#' Remember that `time_point_count_between()` returns an integer vector. +#' With extremely fine precisions, such as nanoseconds, the count can quickly +#' exceed the maximum value that is allowed in an integer. In this case, an +#' `NA` will be returned with a warning. +#' +#' @inheritSection calendar_count_between Comparison Direction +#' +#' @inheritParams ellipsis::dots_empty +#' +#' @param start,end `[clock_time_point]` +#' +#' A pair of time points. These will be recycled to their common size. +#' +#' @param precision `[character(1)]` +#' +#' One of: +#' +#' - `"week"` +#' - `"day"` +#' - `"hour"` +#' - `"minute"` +#' - `"second"` +#' - `"millisecond"` +#' - `"microsecond"` +#' - `"nanosecond"` +#' +#' @param n `[positive integer(1)]` +#' +#' A single positive integer specifying a multiple of `precision` to use. +#' +#' @return An integer representing the number of `precision` units between +#' `start` and `end`. +#' +#' @export +#' @examples +#' x <- as_naive_time(year_month_day(2019, 2, 3)) +#' y <- as_naive_time(year_month_day(2019, 2, 10)) +#' +#' # Whole number of days or hours between two time points +#' time_point_count_between(x, y, "day") +#' time_point_count_between(x, y, "hour") +#' +#' # Whole number of 2-day units +#' time_point_count_between(x, y, "day", n = 2) +#' +#' # Leap years are taken into account +#' x <- as_naive_time(year_month_day(c(2020, 2021), 2, 28)) +#' y <- as_naive_time(year_month_day(c(2020, 2021), 3, 01)) +#' time_point_count_between(x, y, "day") +#' +#' # Time of day is taken into account. +#' # `2020-02-02T04 -> 2020-02-03T03` is not a whole day (because of the hour) +#' # `2020-02-02T04 -> 2020-02-03T05` is a whole day +#' x <- as_naive_time(year_month_day(2020, 2, 2, 4)) +#' y <- as_naive_time(year_month_day(2020, 2, 3, c(3, 5))) +#' time_point_count_between(x, y, "day") +#' time_point_count_between(x, y, "hour") +#' +#' # Can compute negative counts (using the same example from above) +#' time_point_count_between(y, x, "day") +#' time_point_count_between(y, x, "hour") +#' +#' # Repeated computation at increasingly fine precisions +#' x <- as_naive_time(year_month_day( +#' 2020, 2, 2, 4, 5, 6, 200, +#' subsecond_precision = "microsecond" +#' )) +#' y <- as_naive_time(year_month_day( +#' 2020, 3, 1, 8, 9, 10, 100, +#' subsecond_precision = "microsecond" +#' )) +#' +#' days <- time_point_count_between(x, y, "day") +#' x <- x + duration_days(days) +#' +#' hours <- time_point_count_between(x, y, "hour") +#' x <- x + duration_hours(hours) +#' +#' minutes <- time_point_count_between(x, y, "minute") +#' x <- x + duration_minutes(minutes) +#' +#' seconds <- time_point_count_between(x, y, "second") +#' x <- x + duration_seconds(seconds) +#' +#' microseconds <- time_point_count_between(x, y, "microsecond") +#' x <- x + duration_microseconds(microseconds) +#' +#' data.frame( +#' days = days, +#' hours = hours, +#' minutes = minutes, +#' seconds = seconds, +#' microseconds = microseconds +#' ) +time_point_count_between <- function(start, end, precision, ..., n = 1L) { + check_dots_empty() + + if (!is_time_point(start)) { + abort("`start` must be a .") + } + if (!is_time_point(end)) { + abort("`end` must be a .") + } + + args <- vec_cast_common(start = start, end = end) + args <- vec_recycle_common(!!!args) + start <- args[[1]] + end <- args[[2]] + + precision_int <- validate_precision_string(precision) + if (precision_int < PRECISION_WEEK) { + abort("`precision` must be at least 'week' precision.") + } + + n <- vec_cast(n, integer(), x_arg = "n") + if (!is_number(n) || n <= 0L) { + abort("`n` must be a single positive integer.") + } + + out <- end - start + out <- duration_cast(out, precision) + + if (n != 1L) { + out <- out %/% n + } + + as.integer(out) +} + +# ------------------------------------------------------------------------------ + #' Sequences: time points #' #' @description diff --git a/_pkgdown.yml b/_pkgdown.yml index f4966f31..28019b8f 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -15,6 +15,7 @@ reference: - date_time_build - date-today - date_group + - date_count_between - date-and-date-time-boundary - date-and-date-time-rounding - date-and-date-time-shifting @@ -42,6 +43,7 @@ reference: - title: Calendars contents: - calendar_group + - calendar_count_between - calendar-boundary - calendar_narrow - calendar_widen @@ -58,6 +60,7 @@ reference: - is_year_month_day - year_month_day_parse - calendar_group.clock_year_month_day + - year-month-day-count-between - year-month-day-boundary - year-month-day-narrow - year-month-day-widen @@ -72,6 +75,7 @@ reference: - as_year_month_weekday - is_year_month_weekday - calendar_group.clock_year_month_weekday + - year-month-weekday-count-between - year-month-weekday-boundary - year-month-weekday-narrow - year-month-weekday-widen @@ -86,6 +90,7 @@ reference: - as_iso_year_week_day - is_iso_year_week_day - calendar_group.clock_iso_year_week_day + - iso-year-week-day-count-between - iso-year-week-day-boundary - iso-year-week-day-narrow - iso-year-week-day-widen @@ -100,6 +105,7 @@ reference: - as_year_quarter_day - is_year_quarter_day - calendar_group.clock_year_quarter_day + - year-quarter-day-count-between - year-quarter-day-boundary - year-quarter-day-narrow - year-quarter-day-widen @@ -114,6 +120,7 @@ reference: - as_year_day - is_year_day - calendar_group.clock_year_day + - year-day-count-between - year-day-boundary - year-day-narrow - year-day-widen @@ -154,6 +161,7 @@ reference: - subtitle: Manipulation contents: - time_point_cast + - time_point_count_between - time-point-rounding - time_point_shift - time_point_precision @@ -198,6 +206,8 @@ reference: contents: - date-group - posixt-group + - date-count-between + - posixt-count-between - date-boundary - posixt-boundary - date-formatting diff --git a/man/calendar-count-between.Rd b/man/calendar-count-between.Rd new file mode 100644 index 00000000..bacd5046 --- /dev/null +++ b/man/calendar-count-between.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/calendar.R +\name{calendar-count-between} +\alias{calendar-count-between} +\alias{calendar_count_between} +\title{Counting: calendars} +\usage{ +calendar_count_between(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[clock_calendar]} + +A pair of calendar vectors. These will be recycled to their common size.} + +\item{precision}{\verb{[character(1)]} + +A precision. Allowed precisions are dependent on the calendar used.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +\code{calendar_count_between()} counts the number of \code{precision} units between +\code{start} and \code{end} (i.e., the number of years or months). This count +corresponds to the \emph{whole number} of units, and will never return a +fractional value. + +This is suitable for, say, computing the whole number of years or months +between two calendar dates, accounting for the day and time of day. + +Each calendar has its own help page describing the precisions that you can +count at: +\itemize{ +\item \link[=year-month-day-count-between]{year-month-day} +\item \link[=year-month-weekday-count-between]{year-month-weekday} +\item \link[=iso-year-week-day-count-between]{iso-year-week-day} +\item \link[=year-quarter-day-count-between]{year-quarter-day} +\item \link[=year-day-count-between]{year-day} +} +} +\section{Comparison Direction}{ + +The computed count has the property that if \code{start <= end}, then +\verb{start + <= end}. Similarly, if \code{start >= end}, then +\verb{start + >= end}. In other words, the comparison direction between +\code{start} and \code{end} will never change after adding the count to \code{start}. This +makes this function useful for repeated count computations at +increasingly fine precisions. +} + +\examples{ +# Number of whole years between these dates +x <- year_month_day(2000, 01, 05) +y <- year_month_day(2005, 01, 04:06) + +# Note that `2000-01-05 -> 2005-01-04` is only 4 full years +calendar_count_between(x, y, "year") +} diff --git a/man/date-count-between.Rd b/man/date-count-between.Rd new file mode 100644 index 00000000..1a4cf223 --- /dev/null +++ b/man/date-count-between.Rd @@ -0,0 +1,150 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/date.R +\name{date-count-between} +\alias{date-count-between} +\alias{date_count_between.Date} +\title{Counting: date} +\usage{ +\method{date_count_between}{Date}(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[Date]} + +A pair of date vectors. These will be recycled to their common +size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"year"} +\item \code{"month"} +\item \code{"week"} +\item \code{"day"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +This is a Date method for the \code{\link[=date_count_between]{date_count_between()}} generic. + +\code{date_count_between()} counts the number of \code{precision} units between +\code{start} and \code{end} (i.e., the number of years or months). This count +corresponds to the \emph{whole number} of units, and will never return a +fractional value. + +This is suitable for, say, computing the whole number of years or months +between two dates, accounting for the day of the month. + +\emph{Calendrical based counting:} + +These precisions convert to a year-month-day calendar and count while in that +type. +\itemize{ +\item \code{"year"} +\item \code{"month"} +} + +\emph{Time point based counting:} + +These precisions convert to a time point and count while in that type. +\itemize{ +\item \code{"week"} +\item \code{"day"} +} + +For dates, whether a calendar or time point is used is not all that +important, but is is fairly important for date-times. +} +\section{Comparison Direction}{ + +The computed count has the property that if \code{start <= end}, then +\verb{start + <= end}. Similarly, if \code{start >= end}, then +\verb{start + >= end}. In other words, the comparison direction between +\code{start} and \code{end} will never change after adding the count to \code{start}. This +makes this function useful for repeated count computations at +increasingly fine precisions. +} + +\examples{ +start <- date_parse("2000-05-05") +end <- date_parse(c("2020-05-04", "2020-05-06")) + +# Age in years +date_count_between(start, end, "year") + +# Number of "whole" months between these dates. i.e. +# `2000-05-05 -> 2020-04-05` is 239 months +# `2000-05-05 -> 2020-05-05` is 240 months +# Since 2020-05-04 occurs before the 5th of that month, +# it gets a count of 239 +date_count_between(start, end, "month") + +# Number of days between +date_count_between(start, end, "day") + +# Number of full 3 day periods between these two dates +date_count_between(start, end, "day", n = 3) + +# Essentially the truncated value of this +date_count_between(start, end, "day") / 3 + +# --------------------------------------------------------------------------- + +# Breakdown into full years, months, and days between +x <- start + +years <- date_count_between(x, end, "year") +x <- add_years(x, years) + +months <- date_count_between(x, end, "month") +x <- add_months(x, months) + +days <- date_count_between(x, end, "day") +x <- add_days(x, days) + +data.frame( + start = start, + end = end, + years = years, + months = months, + days = days +) + +# Note that when breaking down a date like that, you may need to +# set `invalid` during intermediate calculations +start <- date_build(2019, c(3, 3, 4), c(30, 31, 1)) +end <- date_build(2019, 5, 05) + +# These are 1 month apart (plus a few days) +months <- date_count_between(start, end, "month") + +# But adding that 1 month to `start` results in an invalid date +try(add_months(start, months)) + +# You can choose various ways to resolve this +start_previous <- add_months(start, months, invalid = "previous") +start_next <- add_months(start, months, invalid = "next") + +days_previous <- date_count_between(start_previous, end, "day") +days_next <- date_count_between(start_next, end, "day") + +# Resulting in slightly different day values. +# No result is "perfect". Choosing "previous" or "next" both result +# in multiple `start` dates having the same month/day breakdown values. +data.frame( + start = start, + end = end, + months = months, + days_previous = days_previous, + days_next = days_next +) +} diff --git a/man/date_count_between.Rd b/man/date_count_between.Rd new file mode 100644 index 00000000..a2e57fe4 --- /dev/null +++ b/man/date_count_between.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/date.R +\name{date_count_between} +\alias{date_count_between} +\title{Counting: date and date-time} +\usage{ +date_count_between(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[Date / POSIXct / POSIXlt]} + +A pair of date or date-time vectors. These will be recycled to their common +size.} + +\item{precision}{\verb{[character(1)]} + +A precision. Allowed precisions are dependent on the calendar used.} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +\code{date_count_between()} counts the number of \code{precision} units between +\code{start} and \code{end} (i.e., the number of years or months or hours). This count +corresponds to the \emph{whole number} of units, and will never return a +fractional value. + +This is suitable for, say, computing the whole number of years or months +between two dates, accounting for the day and time of day. + +There are separate help pages for counting for dates and date-times: +\itemize{ +\item \link[=date-count-between]{dates (Date)} +\item \link[=posixt-count-between]{date-times (POSIXct/POSIXlt)} +} +} +\section{Comparison Direction}{ + +The computed count has the property that if \code{start <= end}, then +\verb{start + <= end}. Similarly, if \code{start >= end}, then +\verb{start + >= end}. In other words, the comparison direction between +\code{start} and \code{end} will never change after adding the count to \code{start}. This +makes this function useful for repeated count computations at +increasingly fine precisions. +} + +\examples{ +# See method specific documentation for more examples + +start <- date_parse("2000-05-05") +end <- date_parse(c("2020-05-04", "2020-05-06")) + +# Age in years +date_count_between(start, end, "year") + +# Number of "whole" months between these dates +date_count_between(start, end, "month") +} diff --git a/man/iso-year-week-day-count-between.Rd b/man/iso-year-week-day-count-between.Rd new file mode 100644 index 00000000..fe6edfec --- /dev/null +++ b/man/iso-year-week-day-count-between.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/iso-year-week-day.R +\name{iso-year-week-day-count-between} +\alias{iso-year-week-day-count-between} +\alias{calendar_count_between.clock_iso_year_week_day} +\title{Counting: iso-year-week-day} +\usage{ +\method{calendar_count_between}{clock_iso_year_week_day}(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[clock_iso_year_week_day]} + +A pair of iso-year-week-day vectors. These will be recycled to their +common size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"year"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +This is an iso-year-week-day method for the \code{\link[=calendar_count_between]{calendar_count_between()}} +generic. It counts the number of \code{precision} units between \code{start} and \code{end} +(i.e., the number of ISO years). +} +\examples{ +# Compute the number of whole ISO years between two dates +x <- iso_year_week_day(2001, 1, 2) +y <- iso_year_week_day(2021, 1, c(1, 3)) +calendar_count_between(x, y, "year") +} diff --git a/man/posixt-count-between.Rd b/man/posixt-count-between.Rd new file mode 100644 index 00000000..57c043fd --- /dev/null +++ b/man/posixt-count-between.Rd @@ -0,0 +1,155 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/posixt.R +\name{posixt-count-between} +\alias{posixt-count-between} +\alias{date_count_between.POSIXt} +\title{Counting: date-times} +\usage{ +\method{date_count_between}{POSIXt}(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[POSIXct / POSIXlt]} + +A pair of date-time vectors. These will be recycled to their common +size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"year"} +\item \code{"month"} +\item \code{"week"} +\item \code{"day"} +\item \code{"hour"} +\item \code{"minute"} +\item \code{"second"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +This is a POSIXct/POSIXlt method for the \code{\link[=date_count_between]{date_count_between()}} generic. + +\code{date_count_between()} counts the number of \code{precision} units between +\code{start} and \code{end} (i.e., the number of years or months). This count +corresponds to the \emph{whole number} of units, and will never return a +fractional value. + +This is suitable for, say, computing the whole number of years or months +between two dates, accounting for the day of the month and the time of day. + +Internally, the date-time is converted to one of the following three clock +types, and the counting is done directly on that type. The choice of type is +based on the most common interpretation of each precision, but is ultimately +a heuristic. See the examples for more information. + +\emph{Calendrical based counting:} + +These precisions convert to a year-month-day calendar and count while in that +type. +\itemize{ +\item \code{"year"} +\item \code{"month"} +} + +\emph{Naive-time based counting:} + +These precisions convert to a naive-time and count while in that type. +\itemize{ +\item \code{"week"} +\item \code{"day"} +} + +\emph{Sys-time based counting:} + +These precisions convert to a sys-time and count while in that type. +\itemize{ +\item \code{"hour"} +\item \code{"minute"} +\item \code{"second"} +} +} +\section{Comparison Direction}{ + +The computed count has the property that if \code{start <= end}, then +\verb{start + <= end}. Similarly, if \code{start >= end}, then +\verb{start + >= end}. In other words, the comparison direction between +\code{start} and \code{end} will never change after adding the count to \code{start}. This +makes this function useful for repeated count computations at +increasingly fine precisions. +} + +\examples{ +start <- date_time_parse("2000-05-05 02:00:00", zone = "America/New_York") +end <- date_time_parse( + c("2020-05-05 01:00:00", "2020-05-05 03:00:00"), + zone = "America/New_York" +) + +# Age in years +date_count_between(start, end, "year") + +# Number of "whole" months between these dates. i.e. +# `2000-05-05 02:00:00 -> 2020-04-05 02:00:00` is 239 months +# `2000-05-05 02:00:00 -> 2020-05-05 02:00:00` is 240 months +# Since `2020-05-05 01:00:00` occurs before the 2nd hour, +# it gets a count of 239 +date_count_between(start, end, "month") + +# Number of seconds between +date_count_between(start, end, "second") + +# --------------------------------------------------------------------------- +# Naive-time VS Sys-time interpretation + +# The difference between whether `start` and `end` are converted to a +# naive-time vs a sys-time comes into play when dealing with daylight +# savings. + +# Here are two times around a 1 hour DST gap where clocks jumped from +# 01:59:59 -> 03:00:00 +x <- date_time_build(1970, 4, 26, 1, 50, 00, zone = "America/New_York") +y <- date_time_build(1970, 4, 26, 3, 00, 00, zone = "America/New_York") + +# When treated like sys-times, these are considered to be 10 minutes apart, +# which is the amount of time that would have elapsed if you were watching +# a clock as it changed between these two times. +date_count_between(x, y, "minute") + +# Lets add a 3rd date that is ~1 day ahead of these +z <- date_time_build(1970, 4, 27, 1, 55, 00, zone = "America/New_York") + +# When treated like naive-times, `z` is considered to be at least 1 day ahead +# of `x`, because `01:55:00` is after `01:50:00`. This is probably what you +# expected. +date_count_between(x, z, "day") + +# If these were interpreted like sys-times, then `z` would not be considered +# to be 1 day ahead. That would look something like this: +date_count_between(x, z, "second") +trunc(date_count_between(x, z, "second") / 86400) + +# This is because there have only been 83,100 elapsed seconds since `x`, +# which isn't a full day's worth (86,400 seconds). But we'd generally +# consider `z` to be 1 day ahead of `x` (and ignore the DST gap), so that is +# how it is implemented. + +# You can override this by converting directly to sys-time, then using +# `time_point_count_between()` +x_st <- as_sys_time(x) +x_st + +z_st <- as_sys_time(z) +z_st + +time_point_count_between(x_st, z_st, "day") +} diff --git a/man/time_point_count_between.Rd b/man/time_point_count_between.Rd new file mode 100644 index 00000000..f036ae40 --- /dev/null +++ b/man/time_point_count_between.Rd @@ -0,0 +1,123 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/time-point.R +\name{time_point_count_between} +\alias{time_point_count_between} +\title{Counting: time point} +\usage{ +time_point_count_between(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[clock_time_point]} + +A pair of time points. These will be recycled to their common size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"week"} +\item \code{"day"} +\item \code{"hour"} +\item \code{"minute"} +\item \code{"second"} +\item \code{"millisecond"} +\item \code{"microsecond"} +\item \code{"nanosecond"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +\code{time_point_count_between()} counts the number of \code{precision} units +between \code{start} and \code{end} (i.e., the number of days or hours). This count +corresponds to the \emph{whole number} of units, and will never return a +fractional value. + +This is suitable for, say, computing the whole number of days between two +time points, accounting for the time of day. +} +\details{ +Remember that \code{time_point_count_between()} returns an integer vector. +With extremely fine precisions, such as nanoseconds, the count can quickly +exceed the maximum value that is allowed in an integer. In this case, an +\code{NA} will be returned with a warning. +} +\section{Comparison Direction}{ + +The computed count has the property that if \code{start <= end}, then +\verb{start + <= end}. Similarly, if \code{start >= end}, then +\verb{start + >= end}. In other words, the comparison direction between +\code{start} and \code{end} will never change after adding the count to \code{start}. This +makes this function useful for repeated count computations at +increasingly fine precisions. +} + +\examples{ +x <- as_naive_time(year_month_day(2019, 2, 3)) +y <- as_naive_time(year_month_day(2019, 2, 10)) + +# Whole number of days or hours between two time points +time_point_count_between(x, y, "day") +time_point_count_between(x, y, "hour") + +# Whole number of 2-day units +time_point_count_between(x, y, "day", n = 2) + +# Leap years are taken into account +x <- as_naive_time(year_month_day(c(2020, 2021), 2, 28)) +y <- as_naive_time(year_month_day(c(2020, 2021), 3, 01)) +time_point_count_between(x, y, "day") + +# Time of day is taken into account. +# `2020-02-02T04 -> 2020-02-03T03` is not a whole day (because of the hour) +# `2020-02-02T04 -> 2020-02-03T05` is a whole day +x <- as_naive_time(year_month_day(2020, 2, 2, 4)) +y <- as_naive_time(year_month_day(2020, 2, 3, c(3, 5))) +time_point_count_between(x, y, "day") +time_point_count_between(x, y, "hour") + +# Can compute negative counts (using the same example from above) +time_point_count_between(y, x, "day") +time_point_count_between(y, x, "hour") + +# Repeated computation at increasingly fine precisions +x <- as_naive_time(year_month_day( + 2020, 2, 2, 4, 5, 6, 200, + subsecond_precision = "microsecond" +)) +y <- as_naive_time(year_month_day( + 2020, 3, 1, 8, 9, 10, 100, + subsecond_precision = "microsecond" +)) + +days <- time_point_count_between(x, y, "day") +x <- x + duration_days(days) + +hours <- time_point_count_between(x, y, "hour") +x <- x + duration_hours(hours) + +minutes <- time_point_count_between(x, y, "minute") +x <- x + duration_minutes(minutes) + +seconds <- time_point_count_between(x, y, "second") +x <- x + duration_seconds(seconds) + +microseconds <- time_point_count_between(x, y, "microsecond") +x <- x + duration_microseconds(microseconds) + +data.frame( + days = days, + hours = hours, + minutes = minutes, + seconds = seconds, + microseconds = microseconds +) +} diff --git a/man/year-day-count-between.Rd b/man/year-day-count-between.Rd new file mode 100644 index 00000000..56572165 --- /dev/null +++ b/man/year-day-count-between.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gregorian-year-day.R +\name{year-day-count-between} +\alias{year-day-count-between} +\alias{calendar_count_between.clock_year_day} +\title{Counting: year-day} +\usage{ +\method{calendar_count_between}{clock_year_day}(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[clock_year_day]} + +A pair of year-day vectors. These will be recycled to their +common size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"year"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +This is a year-day method for the \code{\link[=calendar_count_between]{calendar_count_between()}} generic. +It counts the number of \code{precision} units between \code{start} and \code{end} +(i.e., the number of years). +} +\examples{ +# Compute an individual's age in years +x <- year_day(2001, 100) +y <- year_day(2021, c(99, 101)) + +calendar_count_between(x, y, "year") + +# Or in a whole number multiple of years +calendar_count_between(x, y, "year", n = 3) +} diff --git a/man/year-month-day-count-between.Rd b/man/year-month-day-count-between.Rd new file mode 100644 index 00000000..843febf5 --- /dev/null +++ b/man/year-month-day-count-between.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gregorian-year-month-day.R +\name{year-month-day-count-between} +\alias{year-month-day-count-between} +\alias{calendar_count_between.clock_year_month_day} +\title{Counting: year-month-day} +\usage{ +\method{calendar_count_between}{clock_year_month_day}(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[clock_year_month_day]} + +A pair of year-month-day vectors. These will be recycled to their +common size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"year"} +\item \code{"month"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +This is a year-month-day method for the \code{\link[=calendar_count_between]{calendar_count_between()}} generic. +It counts the number of \code{precision} units between \code{start} and \code{end} +(i.e., the number of years or months). +} +\examples{ +# Compute an individual's age in years +x <- year_month_day(2001, 2, 4) +today <- year_month_day(2021, 11, 30) +calendar_count_between(x, today, "year") + +# Compute the number of months between two dates, taking +# into account the day of the month and time of day +x <- year_month_day(2000, 4, 2, 5) +y <- year_month_day(2000, 7, c(1, 2, 2), c(3, 4, 6)) +calendar_count_between(x, y, "month") +} diff --git a/man/year-month-weekday-count-between.Rd b/man/year-month-weekday-count-between.Rd new file mode 100644 index 00000000..19175b54 --- /dev/null +++ b/man/year-month-weekday-count-between.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gregorian-year-month-weekday.R +\name{year-month-weekday-count-between} +\alias{year-month-weekday-count-between} +\alias{calendar_count_between.clock_year_month_weekday} +\title{Counting: year-month-weekday} +\usage{ +\method{calendar_count_between}{clock_year_month_weekday}(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[clock_year_month_weekday]} + +A pair of year-month-weekday vectors. These will be recycled to their +common size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"year"} +\item \code{"month"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +This is a year-month-weekday method for the \code{\link[=calendar_count_between]{calendar_count_between()}} +generic. It counts the number of \code{precision} units between \code{start} and \code{end} +(i.e., the number of years or months). +} +\details{ +Remember that year-month-weekday is not comparable when it is \code{"day"} +precision or finer, so this method is only defined for \code{"year"} and +\code{"month"} precision year-month-weekday objects. +} +\examples{ +# Compute the number of months between two dates +x <- year_month_weekday(2001, 2) +y <- year_month_weekday(2021, c(1, 3)) + +calendar_count_between(x, y, "month") + +# Remember that day precision or finer year-month-weekday objects +# are not comparable, so this won't work +x <- year_month_weekday(2001, 2, 1, 1) +try(calendar_count_between(x, x, "month")) +} diff --git a/man/year-quarter-day-count-between.Rd b/man/year-quarter-day-count-between.Rd new file mode 100644 index 00000000..66deac86 --- /dev/null +++ b/man/year-quarter-day-count-between.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/quarterly-year-quarter-day.R +\name{year-quarter-day-count-between} +\alias{year-quarter-day-count-between} +\alias{calendar_count_between.clock_year_quarter_day} +\title{Counting: year-quarter-day} +\usage{ +\method{calendar_count_between}{clock_year_quarter_day}(start, end, precision, ..., n = 1L) +} +\arguments{ +\item{start, end}{\verb{[clock_year_quarter_day]} + +A pair of year-quarter-day vectors. These will be recycled to their +common size.} + +\item{precision}{\verb{[character(1)]} + +One of: +\itemize{ +\item \code{"year"} +\item \code{"quarter"} +}} + +\item{...}{These dots are for future extensions and must be empty.} + +\item{n}{\verb{[positive integer(1)]} + +A single positive integer specifying a multiple of \code{precision} to use.} +} +\value{ +An integer representing the number of \code{precision} units between +\code{start} and \code{end}. +} +\description{ +This is a year-quarter-day method for the \code{\link[=calendar_count_between]{calendar_count_between()}} generic. +It counts the number of \code{precision} units between \code{start} and \code{end} (i.e., +the number of years or quarters). +} +\examples{ +# Compute the number of whole quarters between two dates +x <- year_quarter_day(2020, 3, 91) +y <- year_quarter_day(2025, 4, c(90, 92)) +calendar_count_between(x, y, "quarter") + +# Note that this is not always the same as the number of whole 3 month +# periods between two dates +x <- as_year_month_day(x) +y <- as_year_month_day(y) +calendar_count_between(x, y, "month", n = 3) +} diff --git a/tests/testthat/_snaps/calendar.md b/tests/testthat/_snaps/calendar.md index e5ab69fa..a2107f67 100644 --- a/tests/testthat/_snaps/calendar.md +++ b/tests/testthat/_snaps/calendar.md @@ -178,6 +178,59 @@ Can't compute the end of a subsecond precision `x` (microsecond) at another subsecond precision (millisecond). +# `end` must be a calendar + + Code + (expect_error(calendar_count_between(x, 1, "year"))) + Output + + `end` must be a . + +# can't count with a precision that calendar doesn't use + + Code + (expect_error(calendar_count_between(x, x, "quarter"))) + Output + + `precision` must be a valid 'iso_year_week_day' precision. + +# can't count with a precision finer than the calendar precision + + Code + (expect_error(calendar_count_between(x, x, "month"))) + Output + + Precision of inputs must be at least as precise as `precision`. + +# `n` is validated + + Code + (expect_error(calendar_count_between(x, x, "year", n = NA_integer_))) + Output + + `n` must be a single positive integer. + Code + (expect_error(calendar_count_between(x, x, "year", n = -1))) + Output + + `n` must be a single positive integer. + Code + (expect_error(calendar_count_between(x, x, "year", n = 1.5))) + Output + + Can't convert from `n` to due to loss of precision. + * Locations: 1 + Code + (expect_error(calendar_count_between(x, x, "year", n = "x"))) + Output + + Can't convert `n` to . + Code + (expect_error(calendar_count_between(x, x, "year", n = c(1L, 2L)))) + Output + + `n` must be a single positive integer. + # precision: can only be called on calendars no applicable method for 'calendar_precision' applied to an object of class "c('clock_sys_time', 'clock_time_point', 'clock_rcrd', 'vctrs_rcrd', 'vctrs_vctr')" diff --git a/tests/testthat/_snaps/date.md b/tests/testthat/_snaps/date.md index 3ceb5459..0a8f00a8 100644 --- a/tests/testthat/_snaps/date.md +++ b/tests/testthat/_snaps/date.md @@ -232,6 +232,22 @@ These dots only exist to allow future extensions and should be empty. Did you misspecify an argument? +# must use a valid Date precision + + Code + (expect_error(date_count_between(x, x, "hour"))) + Output + + `precision` must be one of: 'year', 'month', 'week', 'day'. + +# can't count between a Date and a POSIXt + + Code + (expect_error(date_count_between(x, y, "year"))) + Output + + `end` must be a . + # cannot get the zone of a Date Can't get the zone of a 'Date'. diff --git a/tests/testthat/_snaps/gregorian-year-day.md b/tests/testthat/_snaps/gregorian-year-day.md index f25bb08c..8b773544 100644 --- a/tests/testthat/_snaps/gregorian-year-day.md +++ b/tests/testthat/_snaps/gregorian-year-day.md @@ -102,6 +102,14 @@ Output [1] "2019-001T01:02:03.000050" +# can't compute a unsupported difference precision + + Code + (expect_error(calendar_count_between(x, x, "day"))) + Output + + `precision` must be one of: 'year'. + # only granular precisions are allowed `from` must be 'year' precision. diff --git a/tests/testthat/_snaps/gregorian-year-month-day.md b/tests/testthat/_snaps/gregorian-year-month-day.md index ef69aa3e..4f2cb00b 100644 --- a/tests/testthat/_snaps/gregorian-year-month-day.md +++ b/tests/testthat/_snaps/gregorian-year-month-day.md @@ -136,6 +136,22 @@ `abbreviate` must be `TRUE` or `FALSE`. +# can't compute a unsupported count precision + + Code + (expect_error(calendar_count_between(x, x, "day"))) + Output + + `precision` must be one of: 'year', 'month'. + +# can't compute a quarter count + + Code + (expect_error(calendar_count_between(x, x, "quarter"))) + Output + + `precision` must be a valid 'year_month_day' precision. + # only granular precisions are allowed `from` must be 'year' or 'month' precision. diff --git a/tests/testthat/_snaps/gregorian-year-month-weekday.md b/tests/testthat/_snaps/gregorian-year-month-weekday.md index 3542c92e..5112a900 100644 --- a/tests/testthat/_snaps/gregorian-year-month-weekday.md +++ b/tests/testthat/_snaps/gregorian-year-month-weekday.md @@ -126,6 +126,22 @@ Computing the end of a 'year_month_weekday' with a precision equal to or more precise than 'day' is undefined. +# can't compute a unsupported count precision + + Code + (expect_error(calendar_count_between(x, x, "quarter"))) + Output + + `precision` must be a valid 'year_month_weekday' precision. + +# can't compare a 'year_month_weekday' with day precision! + + Code + (expect_error(calendar_count_between(x, x, "month"))) + Output + + 'year_month_weekday' types with a precision of >= 'day' cannot be trivially compared or ordered. Convert to 'year_month_day' to compare using day-of-month values. + # only granular precisions are allowed `from` must be 'year' or 'month' precision. diff --git a/tests/testthat/_snaps/iso-year-week-day.md b/tests/testthat/_snaps/iso-year-week-day.md index 376a4a51..ab20f51a 100644 --- a/tests/testthat/_snaps/iso-year-week-day.md +++ b/tests/testthat/_snaps/iso-year-week-day.md @@ -94,6 +94,14 @@ Output [1] "2019-W01-1T01:02:03.000050" +# can't compute a unsupported count precision + + Code + (expect_error(calendar_count_between(x, x, "week"))) + Output + + `precision` must be one of: 'year'. + # only year precision is allowed `from` must be 'year' precision. diff --git a/tests/testthat/_snaps/posixt.md b/tests/testthat/_snaps/posixt.md index 7706bb56..de305cc1 100644 --- a/tests/testthat/_snaps/posixt.md +++ b/tests/testthat/_snaps/posixt.md @@ -341,6 +341,22 @@ These dots only exist to allow future extensions and should be empty. Did you misspecify an argument? +# must use a valid POSIXt precision + + Code + (expect_error(date_count_between(x, x, "millisecond"))) + Output + + `precision` must be one of: 'year', 'month', 'week', 'day', 'hour', 'minute', 'second'. + +# can't count between a POSIXt and a Date + + Code + (expect_error(date_count_between(x, y, "year"))) + Output + + `end` must be a . + # op no applicable method for 'add_milliseconds' applied to an object of class "c('POSIXct', 'POSIXt')" diff --git a/tests/testthat/_snaps/quarterly-year-quarter-day.md b/tests/testthat/_snaps/quarterly-year-quarter-day.md index 5f94c1fa..f9d1c103 100644 --- a/tests/testthat/_snaps/quarterly-year-quarter-day.md +++ b/tests/testthat/_snaps/quarterly-year-quarter-day.md @@ -102,6 +102,14 @@ Output [1] "2019-Q1-01T01:02:03.000050" +# can't compute a unsupported count precision + + Code + (expect_error(calendar_count_between(x, x, "day"))) + Output + + `precision` must be one of: 'year', 'quarter'. + # only granular precisions are allowed `from` must be 'year' or 'quarter' precision. diff --git a/tests/testthat/_snaps/time-point.md b/tests/testthat/_snaps/time-point.md index 99ee3b7b..98a31128 100644 --- a/tests/testthat/_snaps/time-point.md +++ b/tests/testthat/_snaps/time-point.md @@ -131,6 +131,71 @@ `boundary` must be either "keep" or "advance". +# OOB results return a warning and NA + + Code + out <- time_point_count_between(sys_days(0), sys_days(1000), "nanosecond") + Warning + Conversion from duration to integer is outside the range of an integer. `NA` values have been introduced, beginning at location 1. + +# both inputs must be time points + + Code + (expect_error(time_point_count_between(sys_days(1), 1))) + Output + + `end` must be a . + Code + (expect_error(time_point_count_between(1, sys_days(1)))) + Output + + `start` must be a . + +# both inputs must be compatible + + Code + (expect_error(time_point_count_between(x, y))) + Output + + Can't combine `start` > and `end` >. + +# `n` is validated + + Code + (expect_error(time_point_count_between(x, x, "day", n = NA_integer_))) + Output + + `n` must be a single positive integer. + Code + (expect_error(time_point_count_between(x, x, "day", n = -1))) + Output + + `n` must be a single positive integer. + Code + (expect_error(time_point_count_between(x, x, "day", n = 1.5))) + Output + + Can't convert from `n` to due to loss of precision. + * Locations: 1 + Code + (expect_error(time_point_count_between(x, x, "day", n = "x"))) + Output + + Can't convert `n` to . + Code + (expect_error(time_point_count_between(x, x, "day", n = c(1L, 2L)))) + Output + + `n` must be a single positive integer. + +# `precision` must be a time point precision + + Code + (expect_error(time_point_count_between(x, x, "year"))) + Output + + `precision` must be at least 'week' precision. + # can't mix chronological time points and calendrical durations Can't convert `by` > to >. diff --git a/tests/testthat/test-calendar.R b/tests/testthat/test-calendar.R index ca06f67d..c2e24217 100644 --- a/tests/testthat/test-calendar.R +++ b/tests/testthat/test-calendar.R @@ -256,6 +256,42 @@ test_that("end: invalid dates are adjusted", { expect_identical(calendar_end(x, "day"), year_month_day(2019, 2, 31, 23)) }) +# ------------------------------------------------------------------------------ +# calendar_count_between() + +test_that("`n` gets used", { + x <- year_month_day(2019, 1) + y <- year_month_day(2019, 7) + expect_identical(calendar_count_between(x, y, "month", n = 2), 3L) +}) + +test_that("`end` must be a calendar", { + x <- year_month_day(2019) + expect_snapshot((expect_error(calendar_count_between(x, 1, "year")))) +}) + +test_that("can't count with a precision that calendar doesn't use", { + x <- iso_year_week_day(2019, 1, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "quarter")))) +}) + +test_that("can't count with a precision finer than the calendar precision", { + x <- year_month_day(2019) + expect_snapshot((expect_error(calendar_count_between(x, x, "month")))) +}) + +test_that("`n` is validated", { + x <- year_month_day(2019) + + expect_snapshot({ + (expect_error(calendar_count_between(x, x, "year", n = NA_integer_))) + (expect_error(calendar_count_between(x, x, "year", n = -1))) + (expect_error(calendar_count_between(x, x, "year", n = 1.5))) + (expect_error(calendar_count_between(x, x, "year", n = "x"))) + (expect_error(calendar_count_between(x, x, "year", n = c(1L, 2L)))) + }) +}) + # ------------------------------------------------------------------------------ # calendar_precision() diff --git a/tests/testthat/test-date.R b/tests/testthat/test-date.R index e9815d8f..edc1f767 100644 --- a/tests/testthat/test-date.R +++ b/tests/testthat/test-date.R @@ -529,6 +529,32 @@ test_that("golden test: ensure that we never allow components of `to` to differ #> "1970-01-31" "1970-03-03" }) +# ------------------------------------------------------------------------------ +# date_count_between() + +test_that("can compute precisions at year / month / week / day", { + x <- date_build(2019, 1, 5) + + y <- date_build(2025, 1, c(4, 6)) + expect_identical(date_count_between(x, y, "year"), c(5L, 6L)) + expect_identical(date_count_between(x, y, "month"), c(71L, 72L)) + + y <- date_build(2019, 1, c(25, 26)) + expect_identical(date_count_between(x, y, "week"), c(2L, 3L)) + expect_identical(date_count_between(x, y, "day"), c(20L, 21L)) +}) + +test_that("must use a valid Date precision", { + x <- date_build(2019) + expect_snapshot((expect_error(date_count_between(x, x, "hour")))) +}) + +test_that("can't count between a Date and a POSIXt", { + x <- date_build(2019) + y <- date_time_build(2019, zone = "UTC") + expect_snapshot((expect_error(date_count_between(x, y, "year")))) +}) + # ------------------------------------------------------------------------------ # date_zone() diff --git a/tests/testthat/test-gregorian-year-day.R b/tests/testthat/test-gregorian-year-day.R index 77fd6d79..b5ec0a8f 100644 --- a/tests/testthat/test-gregorian-year-day.R +++ b/tests/testthat/test-gregorian-year-day.R @@ -295,6 +295,45 @@ test_that("can detect leap years", { expect_identical(calendar_leap_year(x), c(FALSE, TRUE, NA)) }) +# ------------------------------------------------------------------------------ +# calendar_count_between() + +test_that("can compute year counts", { + x <- year_day(2019, 1) + y <- year_day(2020, 3) + + expect_identical(calendar_count_between(x, y, "year"), 1L) +}) + +test_that("can't compute a unsupported difference precision", { + x <- year_day(2019, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "day")))) +}) + +test_that("positive / negative differences are correct", { + start <- year_day(1972, 04) + + + end <- year_day(1973, 03) + expect_identical(calendar_count_between(start, end, "year"), 0L) + + end <- year_day(1973, 04) + expect_identical(calendar_count_between(start, end, "year"), 1L) + + end <- year_day(1973, 05) + expect_identical(calendar_count_between(start, end, "year"), 1L) + + + end <- year_day(1971, 03) + expect_identical(calendar_count_between(start, end, "year"), -1L) + + end <- year_day(1971, 04) + expect_identical(calendar_count_between(start, end, "year"), -1L) + + end <- year_day(1971, 05) + expect_identical(calendar_count_between(start, end, "year"), 0L) +}) + # ------------------------------------------------------------------------------ # seq() diff --git a/tests/testthat/test-gregorian-year-month-day.R b/tests/testthat/test-gregorian-year-month-day.R index c5565d69..55611dc6 100644 --- a/tests/testthat/test-gregorian-year-month-day.R +++ b/tests/testthat/test-gregorian-year-month-day.R @@ -523,6 +523,58 @@ test_that("`abbreviate` is validated", { expect_snapshot_error(calendar_month_factor(year_month_day(2019, 1), abbreviate = c(TRUE, FALSE))) }) +# ------------------------------------------------------------------------------ +# calendar_count_between() + +test_that("can compute year and month counts", { + x <- year_month_day(2019, 1, 1) + y <- year_month_day(2020, 3, 4) + + expect_identical(calendar_count_between(x, y, "year"), 1L) + expect_identical(calendar_count_between(x, y, "month"), 14L) + expect_identical(calendar_count_between(x, y, "month", n = 2), 7L) +}) + +test_that("can't compute a unsupported count precision", { + x <- year_month_day(2019, 1, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "day")))) +}) + +test_that("positive / negative counts are correct", { + start <- year_month_day(1972, 03, 04) + + + end <- year_month_day(1973, 03, 03) + expect_identical(calendar_count_between(start, end, "year"), 0L) + expect_identical(calendar_count_between(start, end, "month"), 11L) + + end <- year_month_day(1973, 03, 04) + expect_identical(calendar_count_between(start, end, "year"), 1L) + expect_identical(calendar_count_between(start, end, "month"), 12L) + + end <- year_month_day(1973, 03, 05) + expect_identical(calendar_count_between(start, end, "year"), 1L) + expect_identical(calendar_count_between(start, end, "month"), 12L) + + + end <- year_month_day(1971, 03, 03) + expect_identical(calendar_count_between(start, end, "year"), -1L) + expect_identical(calendar_count_between(start, end, "month"), -12L) + + end <- year_month_day(1971, 03, 04) + expect_identical(calendar_count_between(start, end, "year"), -1L) + expect_identical(calendar_count_between(start, end, "month"), -12L) + + end <- year_month_day(1971, 03, 05) + expect_identical(calendar_count_between(start, end, "year"), 0L) + expect_identical(calendar_count_between(start, end, "month"), -11L) +}) + +test_that("can't compute a quarter count", { + x <- year_month_day(2019, 1, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "quarter")))) +}) + # ------------------------------------------------------------------------------ # seq() diff --git a/tests/testthat/test-gregorian-year-month-weekday.R b/tests/testthat/test-gregorian-year-month-weekday.R index 4fb8d58b..08d11d41 100644 --- a/tests/testthat/test-gregorian-year-month-weekday.R +++ b/tests/testthat/test-gregorian-year-month-weekday.R @@ -238,6 +238,58 @@ test_that("can get a month factor", { ) }) +# ------------------------------------------------------------------------------ +# calendar_count_between() + +test_that("can compute year and month counts", { + x <- year_month_weekday(2019, 1) + y <- year_month_weekday(2020, 3) + + expect_identical(calendar_count_between(x, y, "year"), 1L) + expect_identical(calendar_count_between(x, y, "month"), 14L) + expect_identical(calendar_count_between(x, y, "month", n = 2), 7L) +}) + +test_that("can't compute a unsupported count precision", { + x <- year_month_weekday(2019, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "quarter")))) +}) + +test_that("positive / negative counts are correct", { + start <- year_month_weekday(1972, 04) + + + end <- year_month_weekday(1973, 03) + expect_identical(calendar_count_between(start, end, "year"), 0L) + expect_identical(calendar_count_between(start, end, "month"), 11L) + + end <- year_month_weekday(1973, 04) + expect_identical(calendar_count_between(start, end, "year"), 1L) + expect_identical(calendar_count_between(start, end, "month"), 12L) + + end <- year_month_weekday(1973, 05) + expect_identical(calendar_count_between(start, end, "year"), 1L) + expect_identical(calendar_count_between(start, end, "month"), 13L) + + + end <- year_month_weekday(1971, 03) + expect_identical(calendar_count_between(start, end, "year"), -1L) + expect_identical(calendar_count_between(start, end, "month"), -13L) + + end <- year_month_weekday(1971, 04) + expect_identical(calendar_count_between(start, end, "year"), -1L) + expect_identical(calendar_count_between(start, end, "month"), -12L) + + end <- year_month_weekday(1971, 05) + expect_identical(calendar_count_between(start, end, "year"), 0L) + expect_identical(calendar_count_between(start, end, "month"), -11L) +}) + +test_that("can't compare a 'year_month_weekday' with day precision!", { + x <- year_month_weekday(2019, 1, 1, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "month")))) +}) + # ------------------------------------------------------------------------------ # seq() diff --git a/tests/testthat/test-iso-year-week-day.R b/tests/testthat/test-iso-year-week-day.R index 139d30e8..6fa83291 100644 --- a/tests/testthat/test-iso-year-week-day.R +++ b/tests/testthat/test-iso-year-week-day.R @@ -201,6 +201,45 @@ test_that("can compute week end", { expect_identical(calendar_end(x, "week"), expect) }) +# ------------------------------------------------------------------------------ +# calendar_count_between() + +test_that("can compute year and month counts", { + x <- iso_year_week_day(2019, 1, 1) + y <- iso_year_week_day(2020, 3, 4) + + expect_identical(calendar_count_between(x, y, "year"), 1L) +}) + +test_that("can't compute a unsupported count precision", { + x <- iso_year_week_day(2019, 1, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "week")))) +}) + +test_that("positive / negative counts are correct", { + start <- iso_year_week_day(1972, 03, 04) + + + end <- iso_year_week_day(1973, 03, 03) + expect_identical(calendar_count_between(start, end, "year"), 0L) + + end <- iso_year_week_day(1973, 03, 04) + expect_identical(calendar_count_between(start, end, "year"), 1L) + + end <- iso_year_week_day(1973, 03, 05) + expect_identical(calendar_count_between(start, end, "year"), 1L) + + + end <- iso_year_week_day(1971, 03, 03) + expect_identical(calendar_count_between(start, end, "year"), -1L) + + end <- iso_year_week_day(1971, 03, 04) + expect_identical(calendar_count_between(start, end, "year"), -1L) + + end <- iso_year_week_day(1971, 03, 05) + expect_identical(calendar_count_between(start, end, "year"), 0L) +}) + # ------------------------------------------------------------------------------ # seq() diff --git a/tests/testthat/test-posixt.R b/tests/testthat/test-posixt.R index f4297363..a043fef7 100644 --- a/tests/testthat/test-posixt.R +++ b/tests/testthat/test-posixt.R @@ -833,6 +833,83 @@ test_that("golden test: ensure that we never allow components of `to` to differ #> "1970-04-25 02:30:00 EST" "1970-04-26 03:30:00 EDT" }) +# ------------------------------------------------------------------------------ +# date_count_between() + +test_that("can compute precisions at year / month / week / day / hour / minute / second", { + x <- date_time_build(2019, 1, 5, 5, zone = "UTC") + + y <- date_time_build(2025, 1, c(4, 6), zone = "UTC") + expect_identical(date_count_between(x, y, "year"), c(5L, 6L)) + expect_identical(date_count_between(x, y, "month"), c(71L, 72L)) + + y <- date_time_build(2019, 1, c(25, 27), zone = "UTC") + expect_identical(date_count_between(x, y, "week"), c(2L, 3L)) + expect_identical(date_count_between(x, y, "day"), c(19L, 21L)) + + y <- date_time_build(2019, 1, 6, c(5, 6), c(59, 0), zone = "UTC") + expect_identical(date_count_between(x, y, "hour"), c(24L, 25L)) + expect_identical(date_count_between(x, y, "minute"), c(1499L, 1500L)) + expect_identical(date_count_between(x, y, "second"), c(89940L, 90000L)) +}) + +test_that("can use posixlt", { + x <- as.POSIXlt(date_time_build(2019, 1, 5, 5, zone = "UTC")) + y <- as.POSIXlt(date_time_build(2020, 1, 5, c(4, 5), zone = "UTC")) + expect_identical(date_count_between(x, y, "year"), c(0L, 1L)) +}) + +test_that("nonexistent times are handled correctly", { + x <- date_time_build(1970, 4, 26, 1, 59, 59, zone = "America/New_York") + y <- date_time_build(1970, 4, 26, 3, 00, 00, zone = "America/New_York") + + # sys-time for hour, minute, second + expect_identical(date_count_between(x, y, "second"), 1L) + expect_identical(date_count_between(x, y, "hour"), 0L) + + # naive-time for week and day + z <- date_time_build(1970, 5, 3, 2, 00, 00, zone = "America/New_York") + expect_identical(date_count_between(x, z, "day"), 7L) + expect_identical(date_count_between(y, z, "day"), 6L) + + # calendar (naive) for year and month + z <- date_time_build(1970, 5, 26, 2, 30, 00, zone = "America/New_York") + expect_identical(date_count_between(x, z, "month"), 1L) + expect_identical(date_count_between(y, z, "month"), 0L) +}) + +test_that("ambiguous times are handled correctly", { + x <- date_time_build(1970, 10, 25, 1, 00, 01, zone = "America/New_York", ambiguous = "earliest") + y <- date_time_build(1970, 10, 25, 1, 00, 01, zone = "America/New_York", ambiguous = "latest") + + # sys-time for hour, minute, second + expect_identical(date_count_between(x, y, "second"), 3600L) + expect_identical(date_count_between(x, y, "hour"), 1L) + + # naive-time for week and day + z <- date_time_build(1970, 11, 01, 1, 00, c(00, 02), zone = "America/New_York") + expect_identical(date_count_between(x, z, "week"), c(0L, 1L)) + expect_identical(date_count_between(y, z, "week"), c(0L, 1L)) + expect_identical(date_count_between(x, z, "day"), c(6L, 7L)) + expect_identical(date_count_between(y, z, "day"), c(6L, 7L)) + + # calendar (naive) for year and month + z <- date_time_build(1970, 11, 25, 1, 00, c(00, 02), zone = "America/New_York") + expect_identical(date_count_between(x, z, "month"), c(0L, 1L)) + expect_identical(date_count_between(y, z, "month"), c(0L, 1L)) +}) + +test_that("must use a valid POSIXt precision", { + x <- date_time_build(2019, zone = "UTC") + expect_snapshot((expect_error(date_count_between(x, x, "millisecond")))) +}) + +test_that("can't count between a POSIXt and a Date", { + x <- date_time_build(2019, zone = "UTC") + y <- date_build(2019) + expect_snapshot((expect_error(date_count_between(x, y, "year")))) +}) + # ------------------------------------------------------------------------------ # vec_arith() diff --git a/tests/testthat/test-quarterly-year-quarter-day.R b/tests/testthat/test-quarterly-year-quarter-day.R index c2405d96..3040da4d 100644 --- a/tests/testthat/test-quarterly-year-quarter-day.R +++ b/tests/testthat/test-quarterly-year-quarter-day.R @@ -203,6 +203,62 @@ test_that("can compute quarter end", { expect_identical(calendar_end(x, "quarter"), expect) }) +# ------------------------------------------------------------------------------ +# calendar_count_between() + +test_that("can compute year and month counts", { + x <- year_quarter_day(2019, 1, 1) + y <- year_quarter_day(2020, 3, 4) + + expect_identical(calendar_count_between(x, y, "year"), 1L) + expect_identical(calendar_count_between(x, y, "quarter"), 6L) + expect_identical(calendar_count_between(x, y, "quarter", n = 2), 3L) +}) + +test_that("works with different quarter start months", { + x <- year_quarter_day(2019, 1, 1, start = clock_months$march) + y <- year_quarter_day(2020, 3, 4, start = clock_months$march) + + expect_identical(calendar_count_between(x, y, "year"), 1L) + expect_identical(calendar_count_between(x, y, "quarter"), 6L) + expect_identical(calendar_count_between(x, y, "quarter", n = 2), 3L) +}) + +test_that("can't compute a unsupported count precision", { + x <- year_quarter_day(2019, 1, 1) + expect_snapshot((expect_error(calendar_count_between(x, x, "day")))) +}) + +test_that("positive / negative counts are correct", { + start <- year_quarter_day(1972, 03, 04) + + + end <- year_quarter_day(1973, 03, 03) + expect_identical(calendar_count_between(start, end, "year"), 0L) + expect_identical(calendar_count_between(start, end, "quarter"), 3L) + + end <- year_quarter_day(1973, 03, 04) + expect_identical(calendar_count_between(start, end, "year"), 1L) + expect_identical(calendar_count_between(start, end, "quarter"), 4L) + + end <- year_quarter_day(1973, 03, 05) + expect_identical(calendar_count_between(start, end, "year"), 1L) + expect_identical(calendar_count_between(start, end, "quarter"), 4L) + + + end <- year_quarter_day(1971, 03, 03) + expect_identical(calendar_count_between(start, end, "year"), -1L) + expect_identical(calendar_count_between(start, end, "quarter"), -4L) + + end <- year_quarter_day(1971, 03, 04) + expect_identical(calendar_count_between(start, end, "year"), -1L) + expect_identical(calendar_count_between(start, end, "quarter"), -4L) + + end <- year_quarter_day(1971, 03, 05) + expect_identical(calendar_count_between(start, end, "year"), 0L) + expect_identical(calendar_count_between(start, end, "quarter"), -3L) +}) + # ------------------------------------------------------------------------------ # seq() diff --git a/tests/testthat/test-time-point.R b/tests/testthat/test-time-point.R index 57960cce..8c769bac 100644 --- a/tests/testthat/test-time-point.R +++ b/tests/testthat/test-time-point.R @@ -191,6 +191,102 @@ test_that("`boundary` is validated", { expect_snapshot_error(time_point_shift(sys_days(), weekday(), boundary = c("keep", "advance"))) }) +# ------------------------------------------------------------------------------ +# time_point_count_between() + +test_that("can count units between", { + x <- as_naive_time(year_month_day(1990, 02, 03, 04)) + y <- as_naive_time(year_month_day(1995, 04, 05, 03)) + + expect_identical(time_point_count_between(x, y, "day"), 1886L) + expect_identical(time_point_count_between(x, y, "hour"), 45287L) +}) + +test_that("'week' is an allowed precision", { + x <- sys_days(0) + y <- sys_days(13:15) + + expect_identical(time_point_count_between(x, y, "week"), c(1L, 2L, 2L)) +}) + +test_that("`n` affects the result", { + x <- sys_days(0) + y <- sys_days(10) + + expect_identical(time_point_count_between(x, y, "day", n = 2L), 5L) + expect_identical(time_point_count_between(x, y, "day", n = 3L), 3L) +}) + +test_that("negative vs positive differences are handled correctly", { + one_hour <- duration_hours(1) + + x <- sys_days(0) + y <- sys_days(1) + z <- sys_days(-1) + + expect_identical(time_point_count_between(x, y - one_hour, "day"), 0L) + expect_identical(time_point_count_between(x, y, "day"), 1L) + expect_identical(time_point_count_between(x, y + one_hour, "day"), 1L) + + expect_identical(time_point_count_between(x, z - one_hour, "day"), -1L) + expect_identical(time_point_count_between(x, z, "day"), -1L) + expect_identical(time_point_count_between(x, z + one_hour, "day"), 0L) +}) + +test_that("common precision of inputs and `precision` is taken", { + expect_identical( + time_point_count_between(sys_days(0), sys_days(2) + duration_hours(1), "second"), + 176400L + ) + expect_identical( + time_point_count_between(sys_seconds(0), sys_seconds(86401), "day"), + 1L + ) +}) + +test_that("OOB results return a warning and NA", { + expect_snapshot({ + out <- time_point_count_between(sys_days(0), sys_days(1000), "nanosecond") + }) + expect_identical(out, NA_integer_) +}) + +test_that("both inputs must be time points", { + expect_snapshot({ + (expect_error(time_point_count_between(sys_days(1), 1))) + (expect_error(time_point_count_between(1, sys_days(1)))) + }) +}) + +test_that("both inputs must be compatible", { + x <- sys_days(1) + y <- naive_days(1) + + expect_snapshot((expect_error( + time_point_count_between(x, y) + ))) +}) + +test_that("`n` is validated", { + x <- sys_days(1) + + expect_snapshot({ + (expect_error(time_point_count_between(x, x, "day", n = NA_integer_))) + (expect_error(time_point_count_between(x, x, "day", n = -1))) + (expect_error(time_point_count_between(x, x, "day", n = 1.5))) + (expect_error(time_point_count_between(x, x, "day", n = "x"))) + (expect_error(time_point_count_between(x, x, "day", n = c(1L, 2L)))) + }) +}) + +test_that("`precision` must be a time point precision", { + x <- sys_days(1) + + expect_snapshot((expect_error( + time_point_count_between(x, x, "year") + ))) +}) + # ------------------------------------------------------------------------------ # seq() diff --git a/vignettes/recipes.Rmd b/vignettes/recipes.Rmd index af70e48f..ba6bdc55 100644 --- a/vignettes/recipes.Rmd +++ b/vignettes/recipes.Rmd @@ -467,6 +467,146 @@ x %>% get_day() ``` +## Computing an age in years + +To get the age of an individual in years, use `calendar_count_between()`. + +```{r} +x <- year_month_day(1980, 12, 14:16) +today <- year_month_day(2005, 12, 15) + +# Note that the month and day of the month are taken into account! +# (Time of day would also be taken into account if there was any.) +calendar_count_between(x, today, "year") +``` + +### High level API + +You can use `date_count_between()` with Date and POSIXct types. + +```{r} +x <- date_build(1980, 12, 14:16) +today <- date_build(2005, 12, 15) + +date_count_between(x, today, "year") +``` + +## Computing number of weeks since the start of the year + +`lubridate::week()` is a useful function that returns "the number of complete seven day periods that have occurred between the date and January 1st, plus one." + +There is no direct equivalent to this, but it is possible to replicate with `calendar_start()` and `time_point_count_between()`. + +```{r} +x <- year_month_day(2019, 11, 28) + +# lubridate::week(as.Date(x)) +# [1] 48 + +x_start <- calendar_start(x, "year") +x_start + +time_point_count_between( + as_naive_time(x_start), + as_naive_time(x), + "week" +) + 1L +``` + +You could also peek at the `lubridate::week()` implementation to see that this is just: + +```{r} +doy <- get_day(as_year_day(x)) +doy + +(doy - 1L) %/% 7L + 1L +``` + +### High level API + +This is actually a little easier in the high level API because you don't have to think about switching between types. + +```{r} +x <- date_build(2019, 11, 28) + +date_count_between(date_start(x, "year"), x, "week") + 1L +``` + +## Compute the number of months between two dates + +How can we compute the number of months between these two dates? + +```{r} +x <- year_month_day(2013, 10, 15) +y <- year_month_day(2016, 10, 13) +``` + +This is a bit of an ambiguous question because "month" isn't very well-defined, and there are various different interpretations we could take. + +We might want to ignore the day component entirely, and just compute the number of months between `2013-10` and `2016-10`. + +```{r} +calendar_narrow(y, "month") - calendar_narrow(x, "month") +``` + +Or we could include the day of the month, and say that `2013-10-15` to `2014-10-15` defines 1 month (i.e. you have to hit the same day of the month in the next month). + +```{r} +calendar_count_between(x, y, "month") +``` + +With this you could also compute the number of days remaining between these two dates. + +```{r} +x_close <- add_months(x, calendar_count_between(x, y, "month")) +x_close + +x_close_st <- as_sys_time(x_close) +y_st <- as_sys_time(y) + +time_point_count_between(x_close_st, y_st, "day") +``` + +Or we could compute the number of days between these two dates in units of seconds, and divide that by the average number of seconds in 1 proleptic Gregorian month. + +```{r} +# Days between x and y +days <- as_sys_time(y) - as_sys_time(x) +days + +# In units of seconds +days <- duration_cast(days, "second") +days <- as.numeric(days) +days + +# Average number of seconds in 1 proleptic Gregorian month +avg_sec_in_month <- duration_cast(duration_months(1), "second") +avg_sec_in_month <- as.numeric(avg_sec_in_month) + +days / avg_sec_in_month +``` + +### High level API + +```{r} +x <- date_build(2013, 10, 15) +y <- date_build(2016, 10, 13) +``` + +To ignore the day of the month, first shift to the start of the month, then you can use `date_count_between()`. + +```{r} +date_count_between(date_start(x, "month"), date_start(y, "month"), "month") +``` + +To utilize the day field, do the same as above but without calling `date_start()`. + +```{r} +date_count_between(x, y, "month") +``` + +There is no high level equivalent to the average length of one proleptic Gregorian month example. + ## Converting a time zone abbreviation into a time zone name It is possible that you might run into date-time strings of the form `"2020-10-25 01:30:00 IST"`, which contain a time zone _abbreviation_ rather than a full time zone name. Because time zone maintainers change the abbreviation they use throughout time, and because multiple time zones sometimes use the same abbreviation, it is generally impossible to parse strings of this form without more information. That said, if you know what time zone this abbreviation goes with, you can parse this time with `zoned_time_parse_abbrev()`, supplying the `zone`.