如何将 java.nio.file.attribute.FileTime 保存到 PostgreSQL 的时间戳列中?

How to save a java.nio.file.attribute.FileTime into a PostgreSQL's timestamp column?

我正在尝试了解如何将收到的文件修改日期作为 java.nio.file.attribute.FileTime 保存到 PostgreSQL 的列中,即 timestamp.

正在阅读与 Java 8 个日期和时间相关的 PostgreSQL 页面:https://jdbc.postgresql.org/documentation/head/8-date-time.html

我可以指出这一点:请注意,不支持 ZonedDateTime、Instant 和 OffsetTime / TIME [WITHOUT TIMEZONE]。

在匹配 table(参见上面的 link)中,PostgreSQL 建议 TIMESTAMP [ WITHOUT TIMEZONE ] LocalDateTime 但有些人不鼓励您使用此 Java class,例如http://blog.schauderhaft.de/2018/03/14/dont-use-localdatetime/

顺便说一句,FileTime 只能转换为 long(毫秒)或 Instant(参见 Java 文档)。

那我该怎么办?我迷路了。

tl;博士

您问的是:

how to save a file's modification date I received as a java.nio.file.attribute.FileTime into a column in PostgreSQL

使用 JDBC 4.2 或更高版本中的类型 TIMESTAMP WITH TIME ZONEOffsetDateTime 来存储由 FileTime 对象表示的时刻。

myPreparedStatement
.setObject( 
    … ,                    // Specify number of placeholder `?` in your SQL code.
    myFileTime             // Your `java.nio.file.attribute.FileTime` object.
    .toInstant()           // Convert to a `java.time.Instant` object.
    .atOffset(             // JDBC 4.2 oddly does not require support for `Instant`, so simply convert to an `OffsetDateTime` object. Same moment, both in UTC, so no value added except to match JDBC spec.
        ZoneOffset.UTC     // We must specify *some* offset, so we might as well use an offset of zero hours-minutes-seconds. The constant `ZoneOffset.UTC` represents just that offset. 
    )                      // Returns an `OffsetDateTime` object we can use with JDBC 4.2 compliant drivers. 
);

片刻

I can just pinpoint this: Note that ZonedDateTime, Instant and OffsetTime / TIME [ WITHOUT TIMEZONE ] are not supported.

该文本不是 Postgres 问题,而是 JDBC 4.2 问题。让我解释一下。

三个classInstantOffsetDateTimeZonedDateTime都代表了一个时刻,时间轴上的一个特定点。表示时刻需要与 UTC 的偏移量(UTC 的小时-分钟-秒数)或时区(特定地区的人们使用的偏移量的过去、现在和未来变化的历史)的上下文地区)。

  • Instant 始终采用 UTC(零小时-分钟-秒的偏移量)。
  • OffsetDateTime 是日期、时间和偏移量。
  • ZonedDateTime 是日期、时间和时区。

从逻辑上讲,所有这三个映射到 Postgres 中的 TIMESTAMP WITH TIME ZONE 列。 Postgres 将任何时区或偏移量信息调整为 UTC,存储 UTC 值,然后丢弃任何提供的 zone/offset.

因此您会认为 JDBC 规范需要对所有这三个方面的支持。但令人费解的是,JDBC 团队选择只需要 OffsetDateTime 的支持。这是一个不幸的决定,因为其他两种类型更常用。无论如何,您可以轻松转换。查看 to…from…at…with… 方法。

Instant instant = Instant.now() ;
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;  // Effectively the same thing as an `Instant`, a moment as seen in UTC. 
myPreparedStatement.setObject( … , odt ) ;

特定的 JDBC driver 可能 支持 Instant and/or ZonedDateTime 以及 OffsetDateTime。但如果您打算编写可移植代码以与各种驱动程序一起使用,请单独使用 OffsetDateTime

存储java.nio.file.attribute.FileTime

列类型

你说:

'm trying to understand how to save a file's modification date I received as a java.nio.file.attribute.FileTime into a column in PostgreSQL which is a timestamp.

检查数据库列的类型。

  • 您的列 必须是 TIMESTAMP WITH TIME ZONE.
  • 类型
  • 另一种类型 TIMESTAMP WITHOUT TIME ZONE 不能表示片刻,因此它 不能 存储您的 FileTime 对象的值。

Java类型

如您所述,java.nio.file.attribute.FileTime class 在 Java 8 及更高版本中添加了一个 toInstant 方法。我们需要通过 JDBC 4.2 或更高版本将这一刻存储在数据库中。

与上面相同的代码:

Instant instant = myFileTime.toInstant() ;
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;  // Effectively the same thing as an `Instant`, a moment as seen in UTC. 
myPreparedStatement.setObject( … , odt ) ;

或更短,但不一定更好:

myPreparedStatement.setObject( … , myFileTime.toInstant().atOffset( ZoneOffset.UTC ) ) ;

检索。

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

如果您需要 Instant,请转换。

Instant instant = odt.toInstant() ;

你说:

In the matching table (see link above) PostgreSQL advices TIMESTAMP [ WITHOUT TIMEZONE ] LocalDateTime

这两种类型,TIMESTAMP WITHOUT TIMEZONELocalDateTime 故意缺少任何时区或与 UTC 的偏移量的概念。因此,它们不能代表片刻。因此他们无法存储您的 FileTime 值,因为值是时间轴上的特定点。

调整到时区

如果您想通过特定时区的挂钟时间查看某个时刻,请应用 ZoneId 以获得 ZonedDateTime 对象。同一时刻,时间轴上的同一点,不同的挂钟时间和日期。

Instant instant = myFileTime.toInstant() ;
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

生成文本来表示该值。我们可以要求 java.time 自动本地化此类文本。

Locale locale = Locale.CANADA_FRENCH ; 
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime​( FormatStyle.FULL ).withLocale( locale ) ;
String output = zdt.format( f ) ;

看到这个 code run live at IdeOne.com。请注意日期和时间如何不同但代表同一时刻。

instant.toString(): 2020-12-18T00:37:55.704644Z

output: jeudi 17 décembre 2020 à 19 h 37 min 55 s heure normale de l’Est

日期时间处理很棘手

你说:

So what am I supposed to do? I'm lost.

日期时间处理令人困惑。我们的直觉理解和日常习惯对程序员做这项工作没有帮助,实际上会适得其反。

非常清楚的主要概念是:片刻片刻 .

  • 销售交易的完成、在数据库中创建的记录,或者送货上门——这些都是瞬间。
  • 说今年的圣诞节从 2020 年 12 月 25 日开始,今年 不是 时刻,因为世界各地的这一天开始时间各不相同,东部较早,东部较晚在西方。圣诞老人开始在太平洋岛屿送货,随着日历逐小时翻到新的一天,圣诞老人向西飞去。
  • 设置六个月后的牙科预约,应该从下午 3 点开始,无论政客如何更改该地区与 UTC 的偏移量 — 即 不是 片刻。

另一个关键概念是程序员和系统管理员应该将 UTC(零小时-分钟-秒的偏移量)视为唯一的真实时间。所有时区都只是变化。在工作中,忘掉你当地的狭隘时间;将您办公桌上的时钟设置为 UTC。在狭隘时间和 UTC 之间来回转换会让人抓狂。


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC driver。不需要字符串,不需要 java.sql.* classes。 Hibernate 5 和 JPA 2.2 支持 java.time.

在哪里获取 java.time classes?