如何在 Oracle 的 TIMESTAMPTZ 中存储 java 的 ZonedDateTime?

How to store ZonedDateTime of java in TIMESTAMPTZ in oracle?

我想将 ZonedDateTime 存储在 Oracle 中的 TIMESTAMP WITH TIME ZONE 数据类型中。 如果我试图将字符串直接存储为字符串,它会抛出无效月份。 然后我发现我可以将它转换为 java 中的 TIMESTAMPTZ 然后存储,因为我们需要将字符串转换为 TIMESTAMPTZ 及其抛出错误。

String d = "2021-10-28 02:36:08.000000 +02:00";
TIMESTAMPTZ t = new TIMESTAMPTZ(con, d);
PreparedStatement ps = con.prepareStatement(query);
ps.setObject(1,t);

Error/stack 追踪:

Exception in thread "main" java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
  at java.sql.Timestamp.valueOf(Timestamp.java:251)
  at oracle.sql.TIMESTAMPTZ.toBytes(TIMESTAMPTZ.java:1919)
  at oracle.sql.TIMESTAMPTZ.<init>(TIMESTAMPTZ.java:253)
  at OracleSelectQuery.main(OracleSelectQuery.java:21)

请有人调查一下。

java.time

following table 描述了 ANSI SQL 类型与 java.time 类型的映射:

ANSI SQL Java SE 8
DATE LocalDate
TIME LocalTime
TIMESTAMP LocalDateTime
TIME WITH TIMEZONE OffsetTime
TIMESTAMP WITH TIMEZONE OffsetDateTime

将给定的日期时间字符串解析为OffsetDateTime,如下所示:

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSS XXX", Locale.ENGLISH);
        String strDateTime = "2021-10-28 02:36:08.000000 +02:00";
        OffsetDateTime odt = OffsetDateTime.parse(strDateTime, dtf);
        System.out.println(odt);
    }
}

输出:

2021-10-28T02:36:08+02:00

ONLINE DEMO

现在,您可以将此 OffsetDateTime 存储到数据库中,如下所示:

PreparedStatement st = conn.prepareStatement("INSERT INTO mytable (columnfoo) VALUES (?)");
st.setObject(1, odt);
st.executeUpdate();
st.close();

详细了解 modern Date-Time API* from Trail: Date Time


* 如果您正在为 Android 项目工作,并且您的 Android API 级别仍然不符合 Java-8,请检查Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time

三参数 TIMESTAMPTZ(Connection, Timestamp, ZoneId) 构造函数

根据 Oracle TIMESTAMPTZ class(底部的 link)的文档,它有一个构造函数,除了连接需要一个 java.sql.Timestamp 和a java.time.ZoneId 作为参数(过时的和现代的 Java class 的有趣混合体)。由于我们可以从您的字符串中提取 ZoneOffset 并且 ZoneOffsetZoneId 的子 class,因此我们可以将此构造函数用于您的目的:

    String d = "2021-10-28 02:36:08.000000 +02:00";
    OffsetDateTime odt = OffsetDateTime.parse(d, PARSER);
    Instant inst = odt.toInstant();
    ZoneId offsetAsZoneId = odt.getOffset(); 
    TIMESTAMPTZ t = new TIMESTAMPTZ(con, Timestamp.from(inst), offsetAsZoneId);

我使用这个格式化程序进行解析:

private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
        .append(DateTimeFormatter.ISO_LOCAL_DATE)
        .appendLiteral(' ')
        .append(DateTimeFormatter.ISO_LOCAL_TIME)
        .appendLiteral(' ')
        .appendOffsetId()
        .toFormatter(Locale.ROOT);

您还可以将时区保存到 Oracle

我使用的构造函数接受 ZoneId 开启了额外的可能性,我们可以将 Europe/Paris 或 Asia/Kolkata 之类的实时时区 ID 存储到数据库中,而不仅仅是一个裸体UTC 偏移量。至少我阅读 Oracle 数据库文档的方式是,它的 timestamp with time zone 数据类型可以包含时区 ID。文档中给出的例子是America/Los_Angeles.

有关将 ZonedDateTime 转换为 TIMESTAMPTZ 的简单示例:

    ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
    Instant inst = zdt.toInstant();
    ZoneId zid = zdt.getZone(); 
    TIMESTAMPTZ t = new TIMESTAMPTZ(con , Timestamp.from(inst), zid);

链接