将 YYYY-MM-DD'T'HH:MM:SS:SSS+HHMM 格式的输入日期转换为带有输入时区的 java.sql.Timestamp

Convert input date in YYYY-MM-DD'T'HH:MM:SS:SSS+HHMM format to the java.sql.Timestamp with the timezone from input

我们有一个要求,其中字符串从 UI 传递到 YYYY-MM-DD'T'HH:MM:SS:SSS+HHMM 以及时区(例如:2020-01-20'T'05:40:334-0500)需要解析为 sql 时间戳以插入数据库。

所有尝试都忽略从输入发送的时间戳,并在数据库中设置时设置本地时间戳。有解决办法请指教?

@Ajay Dsouza 您是否尝试过使用 Java 8 ZonedDateTime 而不是您的用例的标准 Date 并在 statement.For 实例中明确设置时区:

ZonedDateTime receivedTimestamp = somevalue;
Timestamp ts = new Timestamp(receivedTimestamp.toInstant().toEpochMilli());
ps.setTimestamp(
   1, 
   ts, 
   Calendar.getInstance(TimeZone.getTimeZone(receivedTimestamp.getZone()))
); 

实际上,将时区从输入字符串转换为 java.sql.Timezone 时遇到的问题实际上是众所周知的 issue.It,因为默认情况下 JDBC 使用转换期间的 JVM,除非您设置 属性(在 spring-boot 或配置 hibernate 中)显式更改它,或者您可以使用我上面提供的重载准备语句方法在中显式设置时区声明。

PreparedStatement setTimestamp Java文档指出:

With a Calendar object, the driver can calculate the timestamp taking into account a custom timezone. If no Calendar object is specified, the driver uses the default timezone, which is that of the virtual machine running the application.

在 Spring-boot 中,您可以设置以下 属性 以全局设置时区(这可能不是您想要的,但是):

spring.jpa.properties.hibernate.jdbc.time_zone=UTC

java.time 和 JDBC 4.2 或更高版本

我建议您使用 java.time,现代 Java 日期和时间 API,作为您的日期和时间工作。 java.sql.Timestamp class 设计不佳,是在已经设计不佳的 java.util.Date class 之上的真正破解。幸运的是,两者也早已过时。您可能认为您需要一个 Timestamp 来将日期和时间值传输到您的 SQL 数据库。自 JDBC 4.2 起不再如此。

首先,解析您的字符串:

    DateTimeFormatter uiFormatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
            .appendPattern("XX")
            .toFormatter();

    String stringFromUi = "2020-01-20T05:40:34-0500";

    OffsetDateTime dateTime = OffsetDateTime.parse(stringFromUi, uiFormatter);

    System.out.println(dateTime);

到目前为止的输出:

2020-01-20T05:40:34-05:00

不过,我已经更正了您的字符串。它非常接近 ISO 8601 格式,因此我确信它旨在符合该标准。如果您打算在那里使用毫秒,没问题,代码也可以使用秒的小数部分,例如 2020-01-20T05:40:30.334-0500。如果你能说服你的 UI 人发送在偏移量中带有冒号的字符串,例如 2020-01-20T05:40:34-05:00,你将不需要格式化程序但可以使用单参数 OffsetDateTime.parse(String).

接下来,我假设您要求 Timestamp 与您的数据库进行交互。并且由于您要求 Timestamp 以及来自输入 的时区,我进一步假设 SQL 侧的数据类型是 timestamp with time zone。建议用于时间戳。在这种情况下,自 JDBC 4.2 起,您直接将我们的 OffsetDateTime 传递给 JDBC 驱动程序。举个简单的例子:

    PreparedStatement ps = yourDatabaseConnection.prepareStatement(
            "insert into your_table(your_timestamp_with_time_zone_column) values (?);");
    ps.setObject(1, dateTime);
    int rowsInserted = ps.executeUpdate();

如果您确实需要 Timestamp 用于一些您现在无力更改的遗留 API,首先要知道 java.sql.Timestamp 不能有时区或与 UTC 的偏移量.获得 Timestamp 的好方法是从 Instant 转换为另一个现代 class:

    Instant instant = dateTime.toInstant();
    Timestamp ts = Timestamp.from(instant);
    System.out.println(ts);

我的时区 Europe/Copenhagen 的输出是:

2020-01-20 11:40:34.0

虽然看起来有 6 个小时的错误,但它是正确的。 Europe/Copenhagen 一月份的 UTC 偏移量为 +01:00。当我们打印 Timestamp 时,我们隐式调用了它的 toString 方法。 Timestamp.toString() 使用JVM 的时区设置来呈现要返回的字符串。因此,您的日期时间字符串现在已首先从 UTC 偏移量 -05:00 转换为我们不需要关心的某种内部格式,然后转换为偏移量 +01:00,与起点相差 6 小时.

链接

  • Oracle tutorial: Date Time 解释如何使用 java.time.
  • Wikipedia article: ISO 8601
  • 关于带有时区或偏移量的 Timestamp 的其他问题:
    • Java SQL Timestamp to ZonedDateTime
    • How to generate a java.sql.Timestamp in UTC for a query comparison?
    • (这个甚至有几个答案)