Skip to content

Commit 88d56f1

Browse files
authored
Implement as_date() and as_date_time() (#215)
1 parent 652e51d commit 88d56f1

File tree

11 files changed

+548
-0
lines changed

11 files changed

+548
-0
lines changed

NAMESPACE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ S3method(as.character,clock_year_quarter_day)
9393
S3method(as.character,clock_zoned_time)
9494
S3method(as.double,clock_duration)
9595
S3method(as.integer,clock_duration)
96+
S3method(as_date,Date)
97+
S3method(as_date,POSIXt)
98+
S3method(as_date,clock_calendar)
99+
S3method(as_date,clock_time_point)
100+
S3method(as_date,clock_zoned_time)
101+
S3method(as_date_time,Date)
102+
S3method(as_date_time,POSIXt)
103+
S3method(as_date_time,clock_calendar)
104+
S3method(as_date_time,clock_naive_time)
105+
S3method(as_date_time,clock_sys_time)
106+
S3method(as_date_time,clock_zoned_time)
96107
S3method(as_duration,clock_duration)
97108
S3method(as_duration,clock_time_point)
98109
S3method(as_iso_year_week_day,Date)
@@ -536,6 +547,8 @@ export(add_quarters)
536547
export(add_seconds)
537548
export(add_weeks)
538549
export(add_years)
550+
export(as_date)
551+
export(as_date_time)
539552
export(as_duration)
540553
export(as_iso_year_week_day)
541554
export(as_naive_time)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
a direct conversion impossible, `nonexistent` and `ambiguous` can be used
2828
to resolve any issues.
2929

30+
* New `as_date()` and `as_date_time()` for converting to Date and POSIXct
31+
respectively. Unlike `as.Date()` and `as.POSIXct()`, these functions always
32+
treat Date as a naive-time type, which results in more consistent and
33+
intuitive conversions (#209).
34+
3035
* Added two new convenient helpers (#197):
3136

3237
* `date_today()` for getting the current date (Date)

R/date.R

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,72 @@ as.Date.clock_zoned_time <- function(x, ...) {
127127

128128
# ------------------------------------------------------------------------------
129129

130+
#' Convert to a date
131+
#'
132+
#' @description
133+
#' `as_date()` is a generic function that converts its input to a date (Date).
134+
#'
135+
#' There are methods for converting date-times (POSIXct), calendars,
136+
#' time points, and zoned-times to dates.
137+
#'
138+
#' @details
139+
#' Note that clock always assumes that R's Date class is naive, so converting
140+
#' a POSIXct to a Date will always retain the printed year, month, and day
141+
#' value.
142+
#'
143+
#' @param x `[vector]`
144+
#'
145+
#' A vector.
146+
#'
147+
#' @export
148+
#' @examples
149+
#' x <- date_time_parse("2019-01-01 23:02:03", "America/New_York")
150+
#'
151+
#' # R's `as.Date.POSIXct()` method defaults to changing the printed time
152+
#' # to UTC before converting, which can result in odd conversions like this:
153+
#' as.Date(x)
154+
#'
155+
#' # `as_date()` will never change the printed time before converting
156+
#' as_date(x)
157+
#'
158+
#' # Can also convert from other clock types
159+
#' as_date(year_month_day(2019, 2, 5))
160+
as_date <- function(x) {
161+
UseMethod("as_date")
162+
}
163+
164+
#' @rdname as_date
165+
#' @export
166+
as_date.Date <- function(x) {
167+
date_standardize(x)
168+
}
169+
170+
#' @rdname as_date
171+
#' @export
172+
as_date.POSIXt <- function(x) {
173+
as.Date(as_naive_time(x))
174+
}
175+
176+
#' @rdname as_date
177+
#' @export
178+
as_date.clock_calendar <- function(x) {
179+
as.Date(x)
180+
}
181+
182+
#' @rdname as_date
183+
#' @export
184+
as_date.clock_time_point <- function(x) {
185+
as.Date(x)
186+
}
187+
188+
#' @rdname as_date
189+
#' @export
190+
as_date.clock_zoned_time <- function(x) {
191+
as.Date(x)
192+
}
193+
194+
# ------------------------------------------------------------------------------
195+
130196
#' Getters: date
131197
#'
132198
#' @description

R/posixt.R

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,109 @@ as.POSIXlt.clock_zoned_time <- function(x, ...) {
158158

159159
# ------------------------------------------------------------------------------
160160

161+
#' Convert to a date-time
162+
#'
163+
#' @description
164+
#' `as_date_time()` is a generic function that converts its input to a date-time
165+
#' (POSIXct).
166+
#'
167+
#' There are methods for converting dates (Date), calendars, time points, and
168+
#' zoned-times to date-times.
169+
#'
170+
#' @details
171+
#' Note that clock always assumes that R's Date class is naive, so converting
172+
#' a Date to a POSIXct will always attempt to retain the printed year, month,
173+
#' and day. Where possible, the resulting time will be at midnight (`00:00:00`),
174+
#' but in some rare cases this is not possible due to daylight saving time. If
175+
#' that issue ever arises, an error will be thrown, which can be resolved by
176+
#' explicitly supplying `nonexistent` or `ambiguous`.
177+
#'
178+
#' @inheritParams as-zoned-time-naive-time
179+
#'
180+
#' @param x `[vector]`
181+
#'
182+
#' A vector.
183+
#'
184+
#' @export
185+
#' @examples
186+
#' x <- as.Date("2019-01-01")
187+
#'
188+
#' # `as.POSIXct()` will always treat Date as UTC, but will show the result
189+
#' # of the conversion in your system time zone, which can be somewhat confusing
190+
#' if (rlang::is_installed("withr")) {
191+
#' withr::with_timezone("UTC", print(as.POSIXct(x)))
192+
#' withr::with_timezone("Europe/Paris", print(as.POSIXct(x)))
193+
#' withr::with_timezone("America/New_York", print(as.POSIXct(x)))
194+
#' }
195+
#'
196+
#' # `as_date_time()` will treat Date as naive, which means that the original
197+
#' # printed date will attempt to be kept wherever possible, no matter the
198+
#' # time zone. The time will be set to midnight.
199+
#' as_date_time(x, "UTC")
200+
#' as_date_time(x, "Europe/Paris")
201+
#' as_date_time(x, "America/New_York")
202+
#'
203+
#' # In some rare cases, this is not possible.
204+
#' # For example, in Asia/Beirut, there was a DST gap from
205+
#' # 2021-03-27 23:59:59 -> 2021-03-28 01:00:00,
206+
#' # skipping the 0th hour entirely.
207+
#' x <- as.Date("2021-03-28")
208+
#' try(as_date_time(x, "Asia/Beirut"))
209+
#'
210+
#' # To resolve this, set a `nonexistent` time resolution strategy
211+
#' as_date_time(x, "Asia/Beirut", nonexistent = "roll-forward")
212+
#'
213+
#'
214+
#' # You can also convert to date-time from other clock types
215+
#' as_date_time(year_month_day(2019, 2, 3, 03), "America/New_York")
216+
as_date_time <- function(x, ...) {
217+
UseMethod("as_date_time")
218+
}
219+
220+
#' @rdname as_date_time
221+
#' @export
222+
as_date_time.POSIXt <- function(x, ...) {
223+
check_dots_empty()
224+
to_posixct(x)
225+
}
226+
227+
#' @rdname as_date_time
228+
#' @export
229+
as_date_time.Date <- function(x, zone, ..., nonexistent = NULL, ambiguous = NULL) {
230+
check_dots_empty()
231+
as.POSIXct(as_naive_time(x), tz = zone, nonexistent = nonexistent, ambiguous = ambiguous)
232+
}
233+
234+
#' @rdname as_date_time
235+
#' @export
236+
as_date_time.clock_calendar <- function(x, zone, ..., nonexistent = NULL, ambiguous = NULL) {
237+
check_dots_empty()
238+
as.POSIXct(x, tz = zone, nonexistent = nonexistent, ambiguous = ambiguous)
239+
}
240+
241+
#' @rdname as_date_time
242+
#' @export
243+
as_date_time.clock_sys_time <- function(x, zone, ...) {
244+
check_dots_empty()
245+
as.POSIXct(x, tz = zone)
246+
}
247+
248+
#' @rdname as_date_time
249+
#' @export
250+
as_date_time.clock_naive_time <- function(x, zone, ..., nonexistent = NULL, ambiguous = NULL) {
251+
check_dots_empty()
252+
as.POSIXct(x, tz = zone, nonexistent = nonexistent, ambiguous = ambiguous)
253+
}
254+
255+
#' @rdname as_date_time
256+
#' @export
257+
as_date_time.clock_zoned_time <- function(x, ...) {
258+
check_dots_empty()
259+
as.POSIXct(x)
260+
}
261+
262+
# ------------------------------------------------------------------------------
263+
161264
#' Getters: date-time
162265
#'
163266
#' @description

R/utils.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ to_posixct_from_posixlt <- function(x) {
3333
as.POSIXct.POSIXlt(x)
3434
}
3535

36+
date_standardize <- function(x) {
37+
if (identical(typeof(x), "double")) {
38+
return(x)
39+
}
40+
41+
# Convert somewhat rare integer Date to double.
42+
# Preserves names.
43+
storage.mode(x) <- "double"
44+
45+
x
46+
}
47+
3648
# ------------------------------------------------------------------------------
3749

3850
ones_along <- function(x, na_propagate = FALSE) {

_pkgdown.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ reference:
2424
- date_weekday_factor
2525
- date_month_factor
2626
- date_leap_year
27+
- as_date
28+
- as_date_time
2729
- get_year.Date
2830
- get_year.POSIXt
2931
- set_year.Date

man/as_date.Rd

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)