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为小数部分秒的格式标志。 我希望%OSn
在n = 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。 @亚伦的回答是好的,但是仍然会有很长的一段时间。
以下是根据format
或option("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.POSIXct
和myformat.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
将返回正确的结果。