R如何用小数秒格式化POSIXct

我相信R不正确地格式POSIXcttypes与小数秒。 我通过R-BUG提交这个作为一个增强请求,并被刷新为“我们认为当前的行为是正确的 – 删除了错误”。 虽然我非常感谢他们已经做的和继续做的工作,但是我想让其他人对这个问题有所了解,也许就如何更有效地说明问题提出build议。

这里是一个例子:

> tt <- as.POSIXct('2011-10-11 07:49:36.3') > strftime(tt,'%Y-%m-%d %H:%M:%OS1') [1] "2011-10-11 07:49:36.2" 

也就是说,tt被创build为POSIXct时间的小数部分.3秒。 当用十进制数字打印时,显示的数值是.2。 我用毫秒精度的时间标记工作了很多,这使我很头痛,时间往往比实际值低一个档次。

这是发生了什么事情:POSIXct是自纪元以来的浮点数。 所有的整数值都是精确处理的,但是在基数为2的浮点数中,与.3最接近的值比.3略小。 strftime()对格式%OSn的规定行为是向下取整到所要求的十进制数字,所以显示的结果是.2。 对于其他小数部分,浮点值略高于input的值,显示器给出了预期的结果:

  > tt <- as.POSIXct('2011-10-11 07:49:36.4') > strftime(tt,'%Y-%m-%d %H:%M:%OS1') [1] "2011-10-11 07:49:36.4" 

开发者的观点是,对于时间types,我们应该总是舍入到要求的精度。 例如,如果时间是11:59:59.8那么打印格式为%H:%M应该是“11:59”而不是“12:00”, %H:%M:%S应该给“11: 59:59“不是”12:00:00“。 我同意整数秒和格式标志%S ,但我认为这种行为应该是不同的devise为小数部分秒的格式标志。 我希望%OSnn = 0时使用从最近到最近的行为,而%S使用向下舍入,因此使用格式%H:%M:%OS0打印11:59:59.8会给出“12: 00:00" 。 这不会影响秒数的整数,因为这些总是精确地表示,但它更自然地处理小数秒的舍入误差。

这是如何处理小数部分的打印,例如C,因为整数铸造舍弃:

  double x = 9.97; printf("%d\n",(int) x); // 9 printf("%.0f\n",x); // 10 printf("%.1f\n",x); // 10.0 printf("%.2f\n",x); // 9.97 

我对在其他语言和环境中如何处理小数秒做了一个快速调查,真的似乎没有达成共识。 大多数结构都是针对秒数的整数而devise的,小数部分是事后考虑的。 在我看来,在这种情况下,R开发者做了一个不完全不合理的select,实际上并不是最好的select,与其他地方的浮点数显示不一致。

什么是人们的思想? R的行为是否正确? 这是你自己devise的方式吗?

一个根本的问题是,POSIXct表示不如POSIXlt表示更精确,POSIXct表示在格式化之前转换为POSIXlt表示。 下面我们看到,如果我们的string直接转换为POSIXlt表示,那么它会正确输出。

 > as.POSIXct('2011-10-11 07:49:36.3') [1] "2011-10-11 07:49:36.2 CDT" > as.POSIXlt('2011-10-11 07:49:36.3') [1] "2011-10-11 07:49:36.3" 

我们还可以看到,通过查看两种格式的二进制表示和0.3的通常表示之间的差异。

 > t1 <- as.POSIXct('2011-10-11 07:49:36.3') > as.numeric(t1 - round(unclass(t1))) - 0.3 [1] -4.768372e-08 > t2 <- as.POSIXlt('2011-10-11 07:49:36.3') > as.numeric(t2$sec - round(unclass(t2$sec))) - 0.3 [1] -2.831069e-15 

有趣的是,看起来这两个表示实际上比0.3的通常表示要less,但是第二个表示要么足够接近,要么截断的方式与我在这里想象的不同。 鉴于此,我不担心浮点表示困难; 他们可能仍然会发生,但如果我们谨慎使用我们的代表,他们将希望最小化。

罗伯特对四舍五入输出的渴望只是一个输出问题,可以通过多种方式解决。 我的build议是这样的:

 myformat.POSIXct <- function(x, digits=0) { x2 <- round(unclass(x), digits) attributes(x2) <- attributes(x) x <- as.POSIXlt(x2) x$sec <- round(x$sec, digits) format.POSIXlt(x, paste("%Y-%m-%d %H:%M:%OS",digits,sep="")) } 

这从一个POSIXctinput开始,首先转到所需的数字; 它然后转换为POSIXlt并再次轮回。 第一次四舍五入确保所有单位在分钟/小时/天的边界上适当增加; 转换为更精确的表示后,第二轮四舍五入。

 > options(digits.secs=1) > t1 <- as.POSIXct('2011-10-11 07:49:36.3') > format(t1) [1] "2011-10-11 07:49:36.2" > myformat.POSIXct(t1,1) [1] "2011-10-11 07:49:36.3" > t2 <- as.POSIXct('2011-10-11 23:59:59.999') > 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" 

最后一边:你知道标准允许最多两个闰秒吗?

 > as.POSIXlt('2011-10-11 23:59:60.9') [1] "2011-10-11 23:59:60.9" 

好的,还有一件事。 由于OP提交的错误( Bug 14579 ),5月份的行为实际发生了变化; 在那之前它做了圆的小数秒。 不幸的是,这意味着有时可能会达到一秒,这是不可能的。 在错误报告中,它应该已经翻到了下一个分钟,最多到了60。 决定截断而不是圆的一个原因是,它是从POSIXlt表示打印的,每个单元单独存储。 因此,滚动到下一个分钟/小时等是比简单的舍入操作更困难。 为了方便起见,我们需要在POSIXctexpression式中循环,然后转换回来,正如我所build议的那样。

我遇到了这个问题,于是开始寻找解决scheme。 @亚伦的回答是好的,但是仍然会有很长的一段时间。

以下是根据formatoption("digits.secs")正确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, ...) } 

10 ^( – d-1)的模糊因子来自这里:由亚伦精确地转换字符 – > POSIXct – >字符亚毫秒date时间 。

一些例子:

 f <- "%Y-%m-%d %H:%M:%OS" f3 <- "%Y-%m-%d %H:%M:%OS3" f6 <- "%Y-%m-%d %H:%M:%OS6" 

从几乎相同的问题:

 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" 

从上面:

 > 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" 

真正的乐趣是在2038年的某个日子。 我认为这是因为我们在尾数损失了一点精度。 请注意秒字段的值。

 > 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" 

此代码似乎适用于我尝试过的其他边缘情况。 在Aaron的答案format.POSIXctmyformat.POSIXct之间的format.POSIXct是从POSIXct转换到POSIXlt与秒字段完好无损。

这指出了该转换中的一个错误。 我没有使用as.POSIXlt()不可用的任何数据。

更新

这个错误在静态函数localtime0 src/main/datetime.c:434中,但是我不确定是否有正确的修正:

433-434行:

 day = (int) floor(d/86400.0); left = (int) (d - day * 86400.0 + 0.5); 

这个额外的0.5是四舍五入的罪魁祸首。 请注意, t3的亚秒值超过.5。 localtime0只处理秒数,在localtime0返回后秒数被join。

如果double呈现的是整数值, localtime0将返回正确的结果。

    Interesting Posts