Skip to content

Commit 9b09eec

Browse files
authored
Merge pull request #242 from r-lib/feature/date-start-end
Implement start/end functions
2 parents 54d729f + bf6377e commit 9b09eec

32 files changed

+1881
-0
lines changed

NAMESPACE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ S3method(as_zoned_time,clock_naive_time)
171171
S3method(as_zoned_time,clock_sys_time)
172172
S3method(as_zoned_time,clock_zoned_time)
173173
S3method(as_zoned_time,default)
174+
S3method(calendar_end,clock_calendar)
175+
S3method(calendar_end,clock_iso_year_week_day)
176+
S3method(calendar_end,clock_year_day)
177+
S3method(calendar_end,clock_year_month_day)
178+
S3method(calendar_end,clock_year_month_weekday)
179+
S3method(calendar_end,clock_year_quarter_day)
174180
S3method(calendar_group,clock_calendar)
175181
S3method(calendar_group,clock_iso_year_week_day)
176182
S3method(calendar_group,clock_year_day)
@@ -201,6 +207,12 @@ S3method(calendar_narrow,clock_year_month_day)
201207
S3method(calendar_narrow,clock_year_month_weekday)
202208
S3method(calendar_narrow,clock_year_quarter_day)
203209
S3method(calendar_precision,clock_calendar)
210+
S3method(calendar_start,clock_calendar)
211+
S3method(calendar_start,clock_iso_year_week_day)
212+
S3method(calendar_start,clock_year_day)
213+
S3method(calendar_start,clock_year_month_day)
214+
S3method(calendar_start,clock_year_month_weekday)
215+
S3method(calendar_start,clock_year_quarter_day)
204216
S3method(calendar_widen,clock_calendar)
205217
S3method(calendar_widen,clock_iso_year_week_day)
206218
S3method(calendar_widen,clock_year_day)
@@ -209,6 +221,8 @@ S3method(calendar_widen,clock_year_month_weekday)
209221
S3method(calendar_widen,clock_year_quarter_day)
210222
S3method(date_ceiling,Date)
211223
S3method(date_ceiling,POSIXt)
224+
S3method(date_end,Date)
225+
S3method(date_end,POSIXt)
212226
S3method(date_floor,Date)
213227
S3method(date_floor,POSIXt)
214228
S3method(date_format,Date)
@@ -227,6 +241,8 @@ S3method(date_set_zone,Date)
227241
S3method(date_set_zone,POSIXt)
228242
S3method(date_shift,Date)
229243
S3method(date_shift,POSIXt)
244+
S3method(date_start,Date)
245+
S3method(date_start,POSIXt)
230246
S3method(date_weekday_factor,Date)
231247
S3method(date_weekday_factor,POSIXt)
232248
S3method(date_zone,Date)
@@ -562,18 +578,21 @@ export(as_year_month_day)
562578
export(as_year_month_weekday)
563579
export(as_year_quarter_day)
564580
export(as_zoned_time)
581+
export(calendar_end)
565582
export(calendar_group)
566583
export(calendar_leap_year)
567584
export(calendar_month_factor)
568585
export(calendar_narrow)
569586
export(calendar_precision)
587+
export(calendar_start)
570588
export(calendar_widen)
571589
export(clock_labels)
572590
export(clock_labels_languages)
573591
export(clock_labels_lookup)
574592
export(clock_locale)
575593
export(date_build)
576594
export(date_ceiling)
595+
export(date_end)
577596
export(date_floor)
578597
export(date_format)
579598
export(date_group)
@@ -585,6 +604,7 @@ export(date_round)
585604
export(date_seq)
586605
export(date_set_zone)
587606
export(date_shift)
607+
export(date_start)
588608
export(date_time_build)
589609
export(date_time_parse)
590610
export(date_time_parse_abbrev)

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# clock (development version)
22

3+
* New `date_start()` and `date_end()` for computing the date at the start or
4+
end of a particular `precision`, such as the "end of the month" or
5+
the "start of the year". These are powered by `calendar_start()` and
6+
`calendar_end()`, which allow for even more flexible calendar-specific
7+
boundary generation, such as the "last moment in the fiscal quarter" (#232).
8+
39
* New `invalid_remove()` for removing invalid dates. This is just a wrapper
410
around `x[!invalid_detect(x)]`, but works nicely with the pipe (#229).
511

R/calendar.R

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,161 @@ calendar_widen_time <- function(x, x_precision, precision) {
556556

557557
# ------------------------------------------------------------------------------
558558

559+
#' Boundaries: calendars
560+
#'
561+
#' @description
562+
#' - `calendar_start()` computes the start of a calendar at a particular
563+
#' `precision`, such as the "start of the quarter".
564+
#'
565+
#' - `calendar_end()` computes the end of a calendar at a particular
566+
#' `precision`, such as the "end of the month".
567+
#'
568+
#' For both `calendar_start()` and `calendar_end()`, the precision of `x` is
569+
#' always retained.
570+
#'
571+
#' Each calendar has its own help page describing the precisions that you
572+
#' can compute a boundary at:
573+
#'
574+
#' - [year-month-day][year-month-day-boundary]
575+
#'
576+
#' - [year-month-weekday][year-month-weekday-boundary]
577+
#'
578+
#' - [iso-year-week-day][iso-year-week-day-boundary]
579+
#'
580+
#' - [year-quarter-day][year-quarter-day-boundary]
581+
#'
582+
#' - [year-day][year-day-boundary]
583+
#'
584+
#' @inheritParams calendar_group
585+
#'
586+
#' @return `x` at the same precision, but with some components altered to be
587+
#' at the boundary value.
588+
#'
589+
#' @name calendar-boundary
590+
#' @examples
591+
#' # Hour precision
592+
#' x <- year_month_day(2019, 2:4, 5, 6)
593+
#' x
594+
#'
595+
#' # Compute the start of the month
596+
#' calendar_start(x, "month")
597+
#'
598+
#' # Or the end of the month, notice that the hour value is adjusted as well
599+
#' calendar_end(x, "month")
600+
NULL
601+
602+
603+
#' @rdname calendar-boundary
604+
#' @export
605+
calendar_start <- function(x, precision) {
606+
UseMethod("calendar_start")
607+
}
608+
609+
#' @export
610+
calendar_start.clock_calendar <- function(x, precision) {
611+
stop_clock_unsupported_calendar_op("calendar_start")
612+
}
613+
614+
615+
#' @rdname calendar-boundary
616+
#' @export
617+
calendar_end <- function(x, precision) {
618+
UseMethod("calendar_end")
619+
}
620+
621+
#' @export
622+
calendar_end.clock_calendar <- function(x, precision) {
623+
stop_clock_unsupported_calendar_op("calendar_end")
624+
}
625+
626+
627+
calendar_start_end_checks <- function(x, x_precision, precision, which) {
628+
if (!calendar_is_valid_precision(x, precision)) {
629+
message <- paste0(
630+
"`precision` must be a valid precision for a '", calendar_name(x), "'."
631+
)
632+
abort(message)
633+
}
634+
635+
if (x_precision < precision) {
636+
precision <- precision_to_string(precision)
637+
x_precision <- precision_to_string(x_precision)
638+
639+
message <- paste0(
640+
"Can't compute the ", which, " of `x` (", x_precision, ") ",
641+
"at a more precise precision (", precision, ")."
642+
)
643+
abort(message)
644+
}
645+
646+
if (precision > PRECISION_SECOND && x_precision != precision) {
647+
# Computing the start/end of nanosecond precision at millisecond precision
648+
# would be inconsistent with our general philosophy that you "lock in"
649+
# the subsecond precision.
650+
precision <- precision_to_string(precision)
651+
x_precision <- precision_to_string(x_precision)
652+
653+
message <- paste0(
654+
"Can't compute the ", which, " of a subsecond precision `x` (", x_precision, ") ",
655+
"at another subsecond precision (", precision, ")."
656+
)
657+
abort(message)
658+
}
659+
660+
invisible(x)
661+
}
662+
663+
calendar_start_time <- function(x, x_precision, precision) {
664+
values <- list(
665+
hour = 0L,
666+
minute = 0L,
667+
second = 0L,
668+
millisecond = 0L,
669+
microsecond = 0L,
670+
nanosecond = 0L
671+
)
672+
673+
calendar_start_end_time(x, x_precision, precision, values)
674+
}
675+
676+
calendar_end_time <- function(x, x_precision, precision) {
677+
values <- list(
678+
hour = 23L,
679+
minute = 59L,
680+
second = 59L,
681+
millisecond = 999L,
682+
microsecond = 999999L,
683+
nanosecond = 999999999L
684+
)
685+
686+
calendar_start_end_time(x, x_precision, precision, values)
687+
}
688+
689+
calendar_start_end_time <- function(x, x_precision, precision, values) {
690+
if (precision <= PRECISION_DAY && x_precision > PRECISION_DAY) {
691+
x <- set_hour(x, values$hour)
692+
}
693+
if (precision <= PRECISION_HOUR && x_precision > PRECISION_HOUR) {
694+
x <- set_minute(x, values$minute)
695+
}
696+
if (precision <= PRECISION_MINUTE && x_precision > PRECISION_MINUTE) {
697+
x <- set_second(x, values$second)
698+
}
699+
if (precision <= PRECISION_SECOND && x_precision > PRECISION_SECOND) {
700+
if (x_precision == PRECISION_MILLISECOND) {
701+
x <- set_millisecond(x, values$millisecond)
702+
} else if (x_precision == PRECISION_MICROSECOND) {
703+
x <- set_microsecond(x, values$microsecond)
704+
} else if (x_precision == PRECISION_NANOSECOND) {
705+
x <- set_nanosecond(x, values$nanosecond)
706+
}
707+
}
708+
709+
x
710+
}
711+
712+
# ------------------------------------------------------------------------------
713+
559714
#' Precision: calendar
560715
#'
561716
#' `calendar_precision()` extracts the precision from a calendar object. It

R/date.R

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,122 @@ date_today <- function(zone) {
12291229

12301230
# ------------------------------------------------------------------------------
12311231

1232+
#' Boundaries: date and date-time
1233+
#'
1234+
#' @description
1235+
#' - `date_start()` computes the date at the start of a particular
1236+
#' `precision`, such as the "start of the year".
1237+
#'
1238+
#' - `date_end()` computes the date at the end of a particular
1239+
#' `precision`, such as the "end of the month".
1240+
#'
1241+
#' There are separate help pages for computing boundaries for dates and
1242+
#' date-times:
1243+
#'
1244+
#' - [dates (Date)][date-boundary]
1245+
#'
1246+
#' - [date-times (POSIXct/POSIXlt)][posixt-boundary]
1247+
#'
1248+
#' @inheritParams date_group
1249+
#'
1250+
#' @param x `[Date / POSIXct / POSIXlt]`
1251+
#'
1252+
#' A date or date-time vector.
1253+
#'
1254+
#' @param precision `[character(1)]`
1255+
#'
1256+
#' A precision. Allowed precisions are dependent on the input used.
1257+
#'
1258+
#' @return `x` but with some components altered to be at the boundary value.
1259+
#'
1260+
#' @name date-and-date-time-boundary
1261+
#'
1262+
#' @examples
1263+
#' # See type specific documentation for more examples
1264+
#'
1265+
#' x <- date_build(2019, 2:4)
1266+
#'
1267+
#' date_end(x, "month")
1268+
#'
1269+
#' x <- date_time_build(2019, 2:4, 3:5, 4, 5, zone = "America/New_York")
1270+
#'
1271+
#' # Note that the hour, minute, and second components are also adjusted
1272+
#' date_end(x, "month")
1273+
NULL
1274+
1275+
#' @rdname date-and-date-time-boundary
1276+
#' @export
1277+
date_start <- function(x, precision, ...) {
1278+
UseMethod("date_start")
1279+
}
1280+
1281+
#' @rdname date-and-date-time-boundary
1282+
#' @export
1283+
date_end <- function(x, precision, ...) {
1284+
UseMethod("date_end")
1285+
}
1286+
1287+
1288+
#' Boundaries: date
1289+
#'
1290+
#' @description
1291+
#' This is a Date method for the [date_start()] and [date_end()] generics.
1292+
#'
1293+
#' @inheritParams date_group
1294+
#' @inheritParams invalid_resolve
1295+
#'
1296+
#' @param x `[Date]`
1297+
#'
1298+
#' A date vector.
1299+
#'
1300+
#' @param precision `[character(1)]`
1301+
#'
1302+
#' One of:
1303+
#'
1304+
#' - `"year"`
1305+
#'
1306+
#' - `"month"`
1307+
#'
1308+
#' - `"day"`
1309+
#'
1310+
#' @return `x` but with some components altered to be at the boundary value.
1311+
#'
1312+
#' @name date-boundary
1313+
#'
1314+
#' @examples
1315+
#' x <- date_build(2019:2021, 2:4, 3:5)
1316+
#' x
1317+
#'
1318+
#' # Last day of the month
1319+
#' date_end(x, "month")
1320+
#'
1321+
#' # Last day of the year
1322+
#' date_end(x, "year")
1323+
#'
1324+
#' # First day of the year
1325+
#' date_start(x, "year")
1326+
NULL
1327+
1328+
#' @rdname date-boundary
1329+
#' @export
1330+
date_start.Date <- function(x, precision, ..., invalid = NULL) {
1331+
check_dots_empty()
1332+
x <- as_year_month_day(x)
1333+
x <- calendar_start(x, precision)
1334+
as.Date(x, invalid = invalid)
1335+
}
1336+
1337+
#' @rdname date-boundary
1338+
#' @export
1339+
date_end.Date <- function(x, precision, ..., invalid = NULL) {
1340+
check_dots_empty()
1341+
x <- as_year_month_day(x)
1342+
x <- calendar_end(x, precision)
1343+
as.Date(x, invalid = invalid)
1344+
}
1345+
1346+
# ------------------------------------------------------------------------------
1347+
12321348
#' Sequences: date and date-time
12331349
#'
12341350
#' @description

0 commit comments

Comments
 (0)