【问题标题】:How to format OffsetDateTime in java 13 or above?如何在 java 13 或更高版本中格式化 OffsetDateTime?
【发布时间】:2022-01-15 06:43:31
【问题描述】:

在 Java 11 中,时钟系统使用毫秒精度,但显然在 Java 13 及更高版本中,它使用微秒精度,这导致我的测试失败。例如,OffsetDateTime.now() 给了我这个日期“2021-12-10T10:58:05.309594500+01:00”,而当我从数据库“2021-12-10T10:58:05.309595+01:00”中读取这个日期时。我正在寻找一种方法,我可以以它们应该相等的方式格式化第一个日期。我确实希望它在 OffsetDateTime 类型而不是字符串中。

更新: 我意识到这个问题是在我将 java 版本从 11 升级到 17 而不是在本地时出现的,当 gitlab 运行测试时我遇到了这个问题。

这是测试:

@Test
    fun `can store, find and delete a failed-message`() {
        // given: a failed-message
        val failedMessage = FailedMessage(
            failedMessageId = FailedMessageId("the-subcription", "the-message-id"),
            messageAttributes = mapOf("one" to "een", "two" to "twee"),
            messagePayload = "message-payload",
            exception = "exception",
            dateTime = OffsetDateTime.now(),
            stackTrace = "stackey tracey"
        )
       
        failedMessageRepository.store(failedMessage)
      
        assertEquals(failedMessage, failedMessageRepository.find(failedMessage.failedMessageId))
}

由于日期时间不相等,此测试失败。这是日志:

<FailedMessage(failedMessageId=the-subcription-the-message-id, messageAttributes={one=een, two=twee}, messagePayload=message-payload, exception=exception, dateTime=2021-12-10T10:58:05.309594500+01:00, stackTrace=stackey tracey)>
 but was: 
<FailedMessage(failedMessageId=the-subcription-the-message-id, messageAttributes={one=een, two=twee}, messagePayload=message-payload, exception=exception, dateTime=2021-12-10T10:58:05.309595+01:00, stackTrace=stackey tracey)>

你能帮帮我吗?

【问题讨论】:

  • 使用数字较少的格式化程序,在格式化之前截断值等。IIRC,这也可能发生在 Java 11 中,具体取决于实际的 JVM 和您正在运行的平台。
  • 在你的情况下截断有用吗?像这样:OffsetDateTime.now().truncatedTo(ChronoUnit.MICROS);
  • 我相信这种模式会将 OffsetDateTime 格式化为所需的字符串 yyyy-MM-d'T'HH:mm:ss.SSSSSSSXXX
  • 格式化意味着转换为字符串。请决定是否需要任何格式。
  • 您从数据库中读取的值被截断为微秒——秒有 6 位小数。所以我看不出@TomaszPieczkowski 的建议有什么问题?

标签: java datetime java-11 java-17 offsetdatetime


【解决方案1】:

OffsetDateTime总是具有纳秒精度。 now 方法可能没有,具体取决于平台和 Java 版本。对象内部有。所以你不可能有一个OffsetDateTime 的秒数少于 9 个小数。

打印了多少个小数是一个不同的问题。打印OffsetDateTime 时真正打印的是String。如果您只是打印对象,它的toString 方法会被隐式调用以生成我们在打印中看到的字符串。 OffsetDateTime.toString() 总是为我们提供尽可能多的 3 位小数组,这是渲染完整精度所必需的。因此,如果小数点为.100000000,则打印.100。如果它们是.309594500,正如您已经看到的,所有 9 个小数都出现在字符串中。您无法更改此行为。

如果您想打印不同的字符串,可以使用带有您喜欢的小数位数的DateTimeFormatter

那么你可以做什么:

  1. 您可以修改OffsetDateTime,或者实际上,您可以像旧的一样创建一个新的,只是更多的小数为零,这反过来可能会导致toString()打印的小数更少。
  2. 您可以将OffsetDateTime 格式化为您喜欢的字符串。

以下代码 sn-ps 结合了这两个选项。对于仅格式化到最后一个非零小数的小数,我使用以下格式化程序:

private static final DateTimeFormatter ODT_FORMATTER = new DateTimeFormatterBuilder()
        .appendPattern("uuuu-MM-dd'T'HH:mm.ss")
        .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
        .appendOffsetId()
        .toFormatter(Locale.ROOT);

演示:

    OffsetDateTime dateTime = OffsetDateTime.parse("2021-12-10T10:58:05.309594500+01:00");

    int nanos = dateTime.getNano();
    OffsetDateTime with7Decimals = dateTime.withNano(nanos / 100 * 100);
    System.out.println("toString(): " + with7Decimals);
    System.out.println("Formatted:  " + with7Decimals.format(ODT_FORMATTER));
    OffsetDateTime with5Decimals = dateTime.withNano(nanos / 10000 * 10000);
    System.out.println("toString(): " + with5Decimals);
    System.out.println("Formatted:  " + with5Decimals.format(ODT_FORMATTER));

输出:

toString(): 2021-12-10T10:58:05.309594500+01:00
Formatted:  2021-12-10T10:58.05.3095945+01:00
toString(): 2021-12-10T10:58:05.309590+01:00
Formatted:  2021-12-10T10:58.05.30959+01:00

为您测试我认为没有理由使用字符串进行比较。只需在将这些小数设置为零后比较 OffsetDateTime 对象,这些小数点无论如何都会在您的数据库中丢失。

编辑:测试失败的解决方法?下一行是您获取当前时间的位置,其精度比您的数据库可以存储的精度更高。

        dateTime = OffsetDateTime.now(),

因此,将其更改为仅提供您的数据库支持的精度:

        dateTime = OffsetDateTime.now().truncatedTo(ChronoUnit.MICROS),

这应该会导致无需进一步舍入即可保存和检索时间。

为什么会出现问题? OffsetDateTime.now()在不同平台和Java版本上的精度不同。我不知道任何平台上的 Java 11 和 13 之间的区别,但没有理由不应该有一个,并且在未来的某个版本中可能会有另一个。此外,如果您在 Linux、Windows 和 Mac 之间移动,即使使用相同的 Java 版本,您也可能会遇到差异。问题的另一部分是您的数据库的有限精度,或者更准确地说,是您在数据库中用于存储时间的数据类型。如果您使用timestamp with time zone,您可能拥有数据库可以提供的最佳精度。显然,您的数据库具有微秒精度(秒为 6 位小数),而您的 Java 13 OffsetDateTime.now() 具有至少半微秒(500 纳秒)精度。因此,从 Java 13 开始,您将获得数据库无法存储的值。显然,您的数据库或数据库驱动程序随后将您的值向上取整。这导致从数据库中检索到的值与您尝试保存的值不完全相同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-09
    • 1970-01-01
    • 2020-01-06
    • 2016-03-28
    • 2021-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多