`lubridate::floor_date` 返回的值不准确(略低于实际的舍入单位)

编程语言 2026-07-08

我在使用数据库表,在某些表中存储时间戳,精度为毫秒。出于技术原因,这些必须是精确的,因此将毫秒以下的单位截断是有意义的(这将是一个 floor() 操作)。

lubridate::floor_date() 似乎通过将 unit = ".001s" 设置为某个值来实现四舍五入。

然而,这并不准确:它偶尔会产生低于四舍五入单位的时间戳。

示例:

options(digits.secs=7)
for (i in seq_len(100)) {
  print(lubridate::floor_date(Sys.time(), unit = ".001s"))
  # print(lubridate::floor_date(Sys.time(), unit = ".001s") + lubridate::seconds(0.00001))
  # print(format(lubridate::floor_date(Sys.time(), unit = ".001s"), format = "%Y-%m-%d %H:%M:%OS6"))
}

观测结果:(请注意我已删除重复项)

[1] "2026-06-15 11:17:51.882999 CEST"
[1] "2026-06-15 11:17:51.884 CEST"
[1] "2026-06-15 11:17:51.884999 CEST"
[1] "2026-06-15 11:17:51.885999 CEST"
[1] "2026-06-15 11:17:51.887 CEST"
[1] "2026-06-15 11:17:51.888 CEST"
[1] "2026-06-15 11:17:51.888999 CEST"
[1] "2026-06-15 11:17:51.89 CEST"

期望结果:

  • 所有stdout值应以毫秒位数字结尾,例如 51.884999 应为 51.885*
  • 不应跳过毫秒位(同样地,这是因为有些值恰好低于精确毫秒值;之所以提问,是因为 options(digits.secs=3) 实际上会跳过一些值)

我能想到这个问题可能与数值精度以及R 存储低于秒的时间戳的方式有关。

然而,正是为了在下游数据库中避免数值精度问题,我需要 floor 以达到完整的毫秒精度。

需要说明:我正在使用 lubriDATEfloor_DATE,而不是 lubriMILLISECONDS,我的应用对于这个库来说可能有点吃力。

不过,在 ?lubridate::floor_date 中明确提到可以使用秒的分数部分。

是否存在一个替代函数来对 POSIXct 实现四舍五入?(理想情况下不涉及将其转换为字符串再拼接的操作。)

解决方案

下面是一个将精确毫秒拼接到字符串中的函数:

convert_timestamp_to_ms_character <- function(ts) {
  # timestamp string in seconds
  ts_char <- strftime(ts, format = "%Y-%m-%d %H:%M:%OS0")

  # milliseconds
  ts_ms_char <- as.character(floor(unclass(ts)*1000))
  l <- nchar(ts_ms_char)
  ms <- substr(ts_ms_char, start = l-2, stop = l)

  # timezone
  tz <- format(ts, format = "%Z")

  return(paste0(c(ts_char, ".", ms, " ", tz), collapse = ""))
}

# testing:
convert_timestamp_to_ms_character(as.POSIXct("1970-01-01 12:00:00.000", tz = "Europe/London"))
convert_timestamp_to_ms_character(as.POSIXct("1970-01-01 12:00:00.001", tz = "Europe/London"))
convert_timestamp_to_ms_character(as.POSIXct("1970-01-01 12:00:00.002", tz = "Europe/London"))
convert_timestamp_to_ms_character(as.POSIXct("1970-01-01 12:00:00.999999", tz = "Europe/London"))
for (i in seq_len(100)) {
  print(convert_timestamp_to_ms_character(Sys.time()))
}

请注意,输出的类型是 character,它看起来只是一个时间戳。然而,这在导出到我的数据库时已经达到了目的。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章