【问题标题】:Formatting java.time Instant values from PostgreSQL Timestamp values从 PostgreSQL Timestamp 值格式化 java.time Instant 值
【发布时间】:2016-01-20 16:12:05
【问题描述】:

我不熟悉 Java 8 及更高版本中的 java.time 格式,但我对 Joda-Time 相当熟悉,并且我非常熟悉 Java 的 java.util.Datejava.util.CalendarDateFormat 类以及 ISO 8601。

我正在使用PostgreSQL 9.3 和jOOQ 3.6.4,以及包含时间戳的foo 表列:

bar timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP

我使用 jOOQ 检索了 bar 值,并尝试使用 java.time 的 DateTimeFormatter.ISO_OFFSET_DATE_TIME 将其打印出来:

DateTimeFormatter timestampFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
Cursor<FooRecord> fooRecordCursor = createDSLContext().selectFrom(FOO).fetchLazy();
for(FooRecord fooRecord : fooRecordCursor) {
  System.out.println(timestampFormatter.format(fooRecord.getBar().toInstant());
}

这会抛出一个UnsupportedTemporalTypeException:

Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: Year
  at java.time.Instant.getLong(Instant.java:608)
  at java.time.format.DateTimePrintContext$1.getLong(DateTimePrintContext.java:205)
  at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298)
  at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2543)
  at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
  at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
  at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
  at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1745)
  at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1719)

但是,如果我使用自己的自定义 ISO8601DateFormat 来扩展旧式 SimpleDateFormat,我可以很好地解析该值:

final DateFormat timestampFormatter = new ISO8601DateFormat();
...
  System.out.println(timestampFormatter.format(fooRecord.getBar());

我觉得这很令人困惑:

  • jOOQ bar 字段访问器返回 java.sql.Timestamp。在 Java 8 版本中,我将其转换为 Instant,但为什么会减少可用信息量?
  • Instant 应该是一个绝对时间点 --- 它不是像 JodaTime 和 Java Date 一样简单地基于 long 偏移量吗?
  • 为什么DateTimeFormatter.ISO_OFFSET_DATE_TIME 期望来自InstantYear 字段?格式化程序不应该只是将long 偏移量转换为当前时区的日期/时间,然后从中检索年份吗?我不希望 任何 瞬间包含 Year 字段。

简而言之:如果我的基于SimpleDateFormatISO8601DateFormat 适用于来自PostgreSQL 的Timestamp,为什么DateTimeFormatter.ISO_OFFSET_DATE_TIME 不能弄清楚如何格式化相同值的Instant 版本?

【问题讨论】:

  • 你试过.toLocalDateTime()吗?
  • toLocalDatetime() 也不起作用,因为它错过了DateTimeFormatter.ISO_OFFSET_DATE_TIME 格式所需的时区字段。

标签: java postgresql date-format jooq java-time


【解决方案1】:

简答:
这是因为java.time 类将“时间点”和“人类看到的时间”这两个概念分开,而Timestamp/Date 则没有。

长答案:
你是对的,Instant 代表时间线上的一个点。这就是为什么不可能对“年/日/时间是多少?”这个问题给出正确/唯一的答案。这取决于在世界上的哪个地方提出问题:在纽约,它与悉尼不同。
但是您的DateTimeFormatter 正是在问这个问题。这就是为什么您会收到UnsupportedTemporalTypeException

另一方面,Date 及其子类Timestamp 混淆了这两个概念。在内部存储long“时间点”时,如果询问他们的年份,他们会“回答”。通常他们假设系统的本地时区将long-offset 固定到特定时区。
这很容易出错,让我们来介绍Calendar、JodaTime 和 java.time。

现在,为什么您的DateTimeFormatter 不够聪明,无法将Instant 与默认时区对齐?
DateTimeFormatterTemporalAccessor 接口上工作,并且在Instant、@ 等具体实现之间没有区别987654338@ 或ZonedDateTime。所有这些实现都有合法的格式化案例,我认为检查具体对象与给定格式的兼容性根本不可行,更不用说执行正确的转换了。

解决办法: 您必须自己将时间戳与时区/偏移量对齐:

System.out.println(timestampFormatter.format(
    fooRecord.getBar().toLocalDateTime().atZone(ZoneId.systemDefault()));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-30
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多