I ran into this problem and started looking for a solution. @Aaron's answer is good, but still breaking for big dates.
Here is a code that correctly rounds seconds, according to format or option("digits.secs") :
form <- function(x, format = "", tz= "", ...) { # From format.POSIXct if (!inherits(x, "POSIXct")) stop("wrong class") if (missing(tz) && !is.null(tzone <- attr(x, "tzone"))) tz <- tzone # Find the number of digits required based on the format string if (length(format) > 1) stop("length(format) > 1 not supported") m <- gregexpr("%OS[[:digit:]]?", format)[[1]] l <- attr(m, "match.length") if (l == 4) { d <- as.integer(substring(format, l+m-1, l+m-1)) } else { d <- unlist(options("digits.secs")) if (is.null(d)) { d <- 0 } } secs.since.origin <- unclass(x) # Seconds since origin secs <- round(secs.since.origin %% 60, d) # Seconds within the minute mins <- floor(secs.since.origin / 60) # Minutes since origin # Fix up overflow on seconds if (secs >= 60) { secs <- secs - 60 mins <- mins + 1 } # Represents the prior minute lt <- as.POSIXlt(60 * mins, tz=tz, origin=ISOdatetime(1970,1,1,0,0,0,tz="GMT")); lt$sec <- secs + 10^(-d-1) # Add in the seconds, plus a fudge factor. format.POSIXlt(as.POSIXlt(lt), format, ...) }
The fudge factor 10 ^ (-d-1) is from here: Exact conversion from character> POSIXct-> to submillisecond datetimes by Aaron.
Some examples:
f <- "%Y-%m-%d %H:%M:%OS" f3 <- "%Y-%m-%d %H:%M:%OS3" f6 <- "%Y-%m-%d %H:%M:%OS6"
From an almost identical question:
x <- as.POSIXct("2012-12-14 15:42:04.577895") > format(x, f6) [1] "2012-12-14 15:42:04.577894" > form(x, f6) [1] "2012-12-14 15:42:04.577895" > myformat.POSIXct(x, 6) [1] "2012-12-14 15:42:04.577895"
Above:
> format(t1) [1] "2011-10-11 07:49:36.2" > myformat.POSIXct(t1,1) [1] "2011-10-11 07:49:36.3" > form(t1) [1] "2011-10-11 07:49:36.3" > format(t2) [1] "2011-10-11 23:59:59.9" > myformat.POSIXct(t2,0) [1] "2011-10-12 00:00:00" > myformat.POSIXct(t2,1) [1] "2011-10-12 00:00:00.0" > form(t2) [1] "2011-10-12" > form(t2, f) [1] "2011-10-12 00:00:00.0"
The real fun comes in 2038 for some dates. I guess this is because we are losing another precision in the mantissa. Pay attention to the value of the seconds field.
> t3 <- as.POSIXct('2038-12-14 15:42:04.577895') > format(t3) [1] "2038-12-14 15:42:05.5" > myformat.POSIXct(t3, 1) [1] "2038-12-14 15:42:05.6" > form(t3) [1] "2038-12-14 15:42:04.6"
This code seems to work for other cases I tried. A common thing between format.POSIXct and myformat.POSIXct in Aaron's answer is converting to from POSIXct to POSIXlt with a stored second field.
This indicates an error in this conversion. I do not use data inaccessible to as.POSIXlt() .
Update
The error in src/main/datetime.c:434 in the static function localtime0 , but I'm still not sure of the correct fix:
Lines 433-434:
day = (int) floor(d/86400.0); left = (int) (d - day * 86400.0 + 0.5);
An additional 0.5 for rounding off the value is the culprit. Note that the t3 value below is greater than .5. localtime0 deals only with seconds, and subseconds are added after localtime0 returns.
localtime0 returns correct results if the double representation is an integer value.