Skip to content

Commit aa0123f

Browse files
committed
Implement calendar_start() and calendar_end()
1 parent 54d729f commit aa0123f

22 files changed

+1258
-0
lines changed

NAMESPACE

Lines changed: 14 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)
@@ -562,11 +574,13 @@ export(as_year_month_day)
562574
export(as_year_month_weekday)
563575
export(as_year_quarter_day)
564576
export(as_zoned_time)
577+
export(calendar_end)
565578
export(calendar_group)
566579
export(calendar_leap_year)
567580
export(calendar_month_factor)
568581
export(calendar_narrow)
569582
export(calendar_precision)
583+
export(calendar_start)
570584
export(calendar_widen)
571585
export(clock_labels)
572586
export(clock_labels_languages)

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/gregorian-year-day.R

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,64 @@ calendar_widen.clock_year_day <- function(x, precision) {
857857

858858
# ------------------------------------------------------------------------------
859859

860+
#' Boundaries: year-day
861+
#'
862+
#' This is a year-day method for the [calendar_start()] and
863+
#' [calendar_end()] generics. They adjust components of a calendar to the
864+
#' start or end of a specified `precision`.
865+
#'
866+
#' @inheritParams year-day-group
867+
#'
868+
#' @return `x` at the same precision, but with some components altered to be
869+
#' at the boundary value.
870+
#'
871+
#' @name year-day-boundary
872+
#'
873+
#' @examples
874+
#' # Day precision
875+
#' x <- year_day(2019:2020, 5)
876+
#' x
877+
#'
878+
#' # Compute the last day of the year
879+
#' calendar_end(x, "year")
880+
NULL
881+
882+
#' @rdname year-day-boundary
883+
#' @export
884+
calendar_start.clock_year_day <- function(x, precision) {
885+
x_precision <- calendar_precision_attribute(x)
886+
precision <- validate_precision_string(precision)
887+
888+
calendar_start_end_checks(x, x_precision, precision, "start")
889+
890+
if (precision <= PRECISION_YEAR && x_precision > PRECISION_YEAR) {
891+
x <- set_day(x, 1L)
892+
}
893+
894+
x <- calendar_start_time(x, x_precision, precision)
895+
896+
x
897+
}
898+
899+
#' @rdname year-day-boundary
900+
#' @export
901+
calendar_end.clock_year_day <- function(x, precision) {
902+
x_precision <- calendar_precision_attribute(x)
903+
precision <- validate_precision_string(precision)
904+
905+
calendar_start_end_checks(x, x_precision, precision, "end")
906+
907+
if (precision <= PRECISION_YEAR && x_precision > PRECISION_YEAR) {
908+
x <- set_day(x, "last")
909+
}
910+
911+
x <- calendar_end_time(x, x_precision, precision)
912+
913+
x
914+
}
915+
916+
# ------------------------------------------------------------------------------
917+
860918
#' Sequences: year-day
861919
#'
862920
#' @description

R/gregorian-year-month-day.R

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,81 @@ calendar_widen.clock_year_month_day <- function(x, precision) {
11361136

11371137
# ------------------------------------------------------------------------------
11381138

1139+
#' Boundaries: year-month-day
1140+
#'
1141+
#' This is a year-month-day method for the [calendar_start()] and
1142+
#' [calendar_end()] generics. They adjust components of a calendar to the
1143+
#' start or end of a specified `precision`.
1144+
#'
1145+
#' @inheritParams year-month-day-group
1146+
#'
1147+
#' @return `x` at the same precision, but with some components altered to be
1148+
#' at the boundary value.
1149+
#'
1150+
#' @name year-month-day-boundary
1151+
#'
1152+
#' @examples
1153+
#' # Hour precision
1154+
#' x <- year_month_day(2019, 2:4, 5, 6)
1155+
#' x
1156+
#'
1157+
#' # Compute the start of the month
1158+
#' calendar_start(x, "month")
1159+
#'
1160+
#' # Or the end of the month, notice that the hour value is adjusted as well
1161+
#' calendar_end(x, "month")
1162+
#'
1163+
#' # Compare that with just setting the day of the month to `"last"`, which
1164+
#' # doesn't adjust any other components
1165+
#' set_day(x, "last")
1166+
#'
1167+
#' # You can't compute the start / end at a more precise precision than
1168+
#' # the input is at
1169+
#' try(calendar_start(x, "second"))
1170+
NULL
1171+
1172+
#' @rdname year-month-day-boundary
1173+
#' @export
1174+
calendar_start.clock_year_month_day <- function(x, precision) {
1175+
x_precision <- calendar_precision_attribute(x)
1176+
precision <- validate_precision_string(precision)
1177+
1178+
calendar_start_end_checks(x, x_precision, precision, "start")
1179+
1180+
if (precision <= PRECISION_YEAR && x_precision > PRECISION_YEAR) {
1181+
x <- set_month(x, 1L)
1182+
}
1183+
if (precision <= PRECISION_MONTH && x_precision > PRECISION_MONTH) {
1184+
x <- set_day(x, 1L)
1185+
}
1186+
1187+
x <- calendar_start_time(x, x_precision, precision)
1188+
1189+
x
1190+
}
1191+
1192+
#' @rdname year-month-day-boundary
1193+
#' @export
1194+
calendar_end.clock_year_month_day <- function(x, precision) {
1195+
x_precision <- calendar_precision_attribute(x)
1196+
precision <- validate_precision_string(precision)
1197+
1198+
calendar_start_end_checks(x, x_precision, precision, "end")
1199+
1200+
if (precision <= PRECISION_YEAR && x_precision > PRECISION_YEAR) {
1201+
x <- set_month(x, 12L)
1202+
}
1203+
if (precision <= PRECISION_MONTH && x_precision > PRECISION_MONTH) {
1204+
x <- set_day(x, "last")
1205+
}
1206+
1207+
x <- calendar_end_time(x, x_precision, precision)
1208+
1209+
x
1210+
}
1211+
1212+
# ------------------------------------------------------------------------------
1213+
11391214
#' Sequences: year-month-day
11401215
#'
11411216
#' @description

0 commit comments

Comments
 (0)