Fast rounding of datetime in R

Working with POSIXct objects in R can be slow. To floor a million timestamps down to the nearest quarter-of-hour takes ~7 seconds on my laptop using the usual functions: lubridate::floor_date() and clock::date_floor().

Here is a base R function that achieves the same result in 10 ms:

round_time = function(x, precision, method = round) {
  if ("POSIXct" %in% class(x) == FALSE)
    stop("x must be POSIXct")
  
  tz = attributes(x)$tzone
  secs_rounded = method(as.numeric(x) / precision) * precision
  as.POSIXct(secs_rounded, tz = tz, origin = "1970-01-01")
}

You can use it like this:

# A million timestamps:
times = Sys.time() + rnorm(10^6, sd = 10^8)

# Round down to nearest quarter-of-hour
round_time(times, precision = 60*15, method = floor)

# Round up to nearest 5 seconds
round_time(times, precision = 5, method = ceiling)

# Round to the nearest minute (method = round is default):
round_time(times, precision = 60)

Let’s benchmark it against lubridate and clock to verify the ~700x speedup:

times = Sys.time() + rnorm(10^6, sd = 10^8)
bench::mark(round_time(times, precision = 60*15, method = floor))
bench::mark(clock::date_floor(times, precision = "minute", n = 15))
bench::mark(lubridate::floor_date(times, unit = "15 minutes"))