以微秒或纳秒精度进行 Java 日期解析

新手上路,请多包涵

根据 SimpleDateFormat 类文档Java 在其日期模式中不支持毫秒以上的时间粒度。

所以,像这样的日期字符串

  • 2015-05-09 00:10:23.999750900 // 最后9位表示纳秒

当通过模式解析时

  • yyyy-MM-dd HH:mm:ss.SSSSSSSSS // 9 个“S”符号

实际上将 . 符号后的整数解释为(将近 10 亿!)毫秒而不是纳秒,从而得到日期

  • 2015-05-20 21:52:53 UTC

即提前超过 11 天。令人惊讶的是,使用较少数量的 S 符号仍然会导致所有 9 位数字被解析(而不是最左边的 3 位代表 .SSS )。

有两种方法可以正确处理这个问题:

  • 使用字符串预处理
  • 使用自定义 SimpleDateFormat 实现

是否有任何其他方法可以通过仅向标准 SimpleDateFormat 实现提供模式来获得正确的解决方案,而无需任何其他代码修改或字符串操作?

原文由 PNS 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 2.2k
2 个回答

tl;博士

LocalDateTime.parse(                 // With resolution of nanoseconds, represent the idea of a date and time somewhere, unspecified. Does *not* represent a moment, is *not* a point on the timeline. To determine an actual moment, place this date+time into context of a time zone (apply a `ZoneId` to get a `ZonedDateTime`).
    "2015-05-09 00:10:23.999750900"  // A `String` nearly in standard ISO 8601 format.
    .replace( " " , "T" )            // Replace SPACE in middle with `T` to comply with ISO 8601 standard format.
)                                    // Returns a `LocalDateTime` object.

没有

不,您不能使用 SimpleDateFormat 来处理 纳秒

但是你的前提是……

Java 在其日期模式中不支持超过 毫秒 的时间粒度

…从 Java 8 、 9 、 10 和更高版本的内置 java.time 类开始不再适用。 Java 6 和 Java 7 也不是真的,因为大多数 java.time 功能都是向后移植的

java.time

SimpleDateFormat 和相关的 java.util.Date / .Calendar 类现在已被新的 Java.time 包中的 教程 淘汰。

新的 java.time 类支持 纳秒 分辨率。该支持包括解析和生成九位小数秒。例如,当您使用 java.time.format DateTimeFormatter API 时, S 模式字母表示秒的“分数”而不是“秒”处理纳秒值。

Instant

例如, Instant 类表示 UTC 中的时刻。它的 toString 方法使用标准 ISO 8601 格式生成一个 String 对象。最后的 Z 表示 UTC,发音为“Zulu”。

 instant.toString()  // Generate a `String` representing this moment, using standard ISO 8601 format.

2013-08-20T12:34:56.123456789Z

请注意,在 Java 8 中捕获当前时刻仅限于毫秒分辨率。 java.time 类可以 保存 以纳秒为单位的值,但只能确定以毫秒为单位的当前时间。此限制是由于 Clock 的实施。在 Java 9 及更高版本中,一个新的 Clock 实现可以更精细的分辨率抓取当前时刻,这取决于你的主机硬件和操作系统的限制,根据我的经验通常是 微秒

 Instant instant = Instant.now() ;  // Capture the current moment. May be in milliseconds or microseconds rather than the maximum resolution of nanoseconds.

LocalDateTime

您的示例输入字符串 2015-05-09 00:10:23.999750900 缺少时区指示符或与 UTC 的偏移量。这意味着它 不代表 一个时刻, 不是 时间轴上的一个点。相反,它代表了大约 26-27 小时范围内的 潜在 时刻,即全球时区范围。

将这样的输入分解为 LocalDateTime 对象。首先,将中间的 SPACE 替换为 T 以符合 ISO 8601 格式,在解析/生成字符串时默认使用。因此无需指定格式化模式。

 LocalDateTime ldt =
        LocalDateTime.parse(
            "2015-05-09 00:10:23.999750900".replace( " " , "T" )  // Replace SPACE in middle with `T` to comply with ISO 8601 standard format.
        )
;

java.sql.Timestamp

java.sql.Timestamp 类也处理纳秒分辨率,但方式很尴尬。通常最好在 java.time 类中完成工作。从 JDBC 4.2 及更高版本开始,无需再次使用 Timestamp

 myPreparedStatement.setObject( … , instant ) ;

和检索。

 Instant instant = myResultSet.getObject( … , Instant.class ) ;

OffsetDateTime

Instant 的支持不是 JDBC 规范强制要求的,但是 OffsetDateTime 是。因此,如果上面的代码在您的 JDBC 驱动程序中失败,请使用以下代码。

 OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;

和检索。

 Instant instant = myResultSet.getObject( … , OffsetDateTime.class ).toInstant() ;

如果使用较早的 4.2 之前的 JDBC 驱动程序,您可以使用 toInstantfrom 方法在 java.sql.Timestamp 之间来回切换这些新的转换方法被添加到 的遗留类中。

Java(传统和现代)和标准 SQL 中的日期时间类型表


关于 java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

现在处于 维护模式Joda-Time 项目建议迁移到 java.time 类。

要了解更多信息,请参阅 Oracle 教程。并在 Stack Overflow 中搜索许多示例和解释。规范是 JSR 310

您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。不需要字符串,不需要 java.sql.* 类。

在哪里获取 java.time 类?

ThreeTen-Extra 项目用附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可能会在这里找到一些有用的类,例如 IntervalYearWeekYearQuarter 等等

原文由 Basil Bourque 发布,翻译遵循 CC BY-SA 4.0 许可协议

我发现像这样涵盖日期时间格式的多种变体真是太棒了:

 final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);

原文由 flowgrad 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏