Java 日期解析具有微秒或纳秒的精度tl;博士不java.timejava.sql.Timestamp关于 java.time

2022-09-01 08:40:02

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

所以,一个日期字符串,比如

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

当通过模式解析时

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

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

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

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

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

  • 使用字符串预处理
  • 使用自定义的简单日期格式实现

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


答案 1

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 8 中的新 java.time 包(教程)所取代。java.util.Date.Calendar

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

Instant

例如,Instant 类表示 UTC 中的某个时刻。其方法使用标准 ISO 8601 格式生成对象。最后的意思是UTC,发音为“祖鲁语”。toStringStringZ

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

2013-08-20T12:34:56.123456789Z

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

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

LocalDateTime

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

将此类输入视为对象。首先,将中间的空格替换为符合 ISO 8601 格式的 a,默认情况下在解析/生成字符串时使用。因此,无需指定格式设置模式。LocalDateTimeT

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

JDBC 规范没有强制要求对 的支持,而是。因此,如果上述代码在 JDBC 驱动程序中失败,请使用以下命令。InstantOffsetDateTime

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

和检索。

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

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

Table of date-time types in Java (both legacy and modern) and in standard 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


答案 2

我发现它很棒,可以像这样覆盖日期时间格式的多种变体:

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);

推荐