95

我必须将 UTC dateTime 存储在数据库中。
我已将特定时区中给出的 dateTime 转换为 UTC。为此,我遵循了以下代码。
我的输入日期时间是“20121225 10:00:00 Z”时区是“亚洲/加尔各答”
我的服务器/数据库(oracle)在同一个时区(IST)“亚洲/加尔各答”中运行

获取此特定时区中的 Date 对象

        String date = "20121225 10:00:00 Z";
        String timeZoneId = "Asia/Calcutta";
        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

        DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
                    //This date object is given time and given timezone
        java.util.Date parsedDate = dateFormatLocal.parse(date + " "  
                         + timeZone.getDisplayName(false, TimeZone.SHORT));

        if (timeZone.inDaylightTime(parsedDate)) {
            // We need to re-parse because we don't know if the date
            // is DST until it is parsed...
            parsedDate = dateFormatLocal.parse(date + " "
                    + timeZone.getDisplayName(true, TimeZone.SHORT));
        }

       //assigning to the java.sql.TimeStamp instace variable
        obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));

存入数据库

        if (tsSchedStartTime != null) {
            stmt.setTimestamp(11, tsSchedStartTime);
        } else {
            stmt.setNull(11, java.sql.Types.DATE);
        }

输出

DB(oracle)存储了与dateTime: "20121225 10:00:00UTC不同的相同内容。

我已经从下面的 sql 中确认了。

     select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable

我的数据库服务器也在同一时区“亚洲/加尔各答”上运行

它给了我以下外观

  1. Date.getTime()不是 UTC
  2. 或者时间戳在存储到数据库时有时区影响我在这里做错了什么?

还有一个问题:

timeStamp.toString()在当地时区打印java.util.date吗?不是UTC?

4

7 回答 7

113

尽管没有明确指定setTimestamp(int parameterIndex, Timestamp x)驱动程序必须遵循setTimestamp(int parameterIndex, Timestamp x, Calendar cal)javadoc建立的规则:

java.sql.Timestamp使用给定Calendar对象将指定参数设置为给定值。驱动程序使用该Calendar对象构造一个 SQLTIMESTAMP值,然后驱动程序将其发送到数据库。使用Calendar对象,驱动程序可以在考虑自定义时区的情况下计算时间戳。如果未Calendar指定对象,则驱动程序使用默认时区,即运行应用程序的虚拟机的时区。

当您使用setTimestamp(int parameterIndex, Timestamp x)JDBC 驱动程序调用时,使用虚拟机的时区来计算该时区中时间戳的日期和时间。此日期和时间是存储在数据库中的内容,如果数据库列不存储时区信息,则有关该区域的任何信息都会丢失(这意味着由使用数据库的应用程序来使用一致的时区或提出另一种方案来识别时区(即存储在单独的列中)。

例如:您当地的时区是 GMT+2。您存储“2012-12-25 10:00:00 UTC”。数据库中存储的实际值为“2012-12-25 12:00:00”。您再次检索它:您再次将其取回为“2012-12-25 10:00:00 UTC”(但仅当您使用 检索它getTimestamp(..)时),但是当另一个应用程序在 GMT+0 时区访问数据库时,它将检索时间戳为“2012-12-25 12:00:00 UTC”。

如果要将其存储在不同的时区,则需要将setTimestamp(int parameterIndex, Timestamp x, Calendar cal)与所需时区中的日历实例一起使用。只需确保在检索值时也使用具有相同时区的等效 getter(如果您TIMESTAMP在数据库中使用没有时区信息)。

因此,假设您要存储实际的 GMT 时区,您需要使用:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
stmt.setTimestamp(11, tsSchedStartTime, cal);

对于 JDBC 4.2,兼容的驱动程序应该支持java.time.LocalDateTime(and java.time.LocalTime) for TIMESTAMP(and TIME) through get/set/updateObject。这些java.time.Local*类没有时区,因此不需要应用转换(尽管如果您的代码确实假设了特定时区,这可能会引发一系列新问题)。

于 2012-12-28T14:11:02.117 回答
38

我认为正确的答案应该是 java.sql.Timestamp 不是特定于时区的。时间戳是 java.util.Date 和单独的纳秒值的组合。此类中没有时区信息。因此,就像 Date 这个类只保存自 1970 年 1 月 1 日 00:00:00 GMT + nanos 以来的毫秒数。

在 PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal) 中,驱动程序使用日历来更改默认时区。但是时间戳在格林威治标准时间仍然保持毫秒。

API 不清楚 JDBC 驱动程序应该如何使用 Calendar。提供者似乎对如何解释它感到自由,例如,上次我使用 MySQL 5.5 Calendar 时,驱动程序在 PreparedStatement.setTimestamp 和 ResultSet.getTimestamp 中都忽略了 Calendar。

于 2012-12-28T14:42:50.793 回答
11

答案是java.sql.Timestamp一团糟,应该避免。改为使用java.time.LocalDateTime

那么为什么会一团糟呢?在java.sql.TimestampJavaDoc 中,ajava.sql.Timestamp是“java.util.Date允许 JDBC API 将其识别为 SQL TIMESTAMP 值的瘦包装器”。在java.util.DateJavaDoc 中,“Date该类旨在反映协调世界时 (UTC)”。从 ISO SQL 规范来看,TIMESTAMP WITHOUT TIME ZONE“是一种没有时区的日期时间数据类型”。TIMESTAMP 是 TIMESTAMP WITHOUT TIME ZONE 的简称。因此java.sql.Timestamp,当 SQL TIMESTAMP 是“无时区”时,“反映”UTC。

因为java.sql.Timestamp反映了 UTC,所以它的方法应用了转换。这会导致无休止的混乱。从 SQL 的角度来看,将 SQL TIMESTAMP 值转换为其他时区是没有意义的,因为 TIMESTAMP 没有要转换的时区。将 42 转换为华氏度是什么意思?它没有任何意义,因为 42 没有温度单位。这只是一个空号。同样,您无法将 2020-07-22T10:38:00 的 TIMESTAMP 转换为 Americas/Los Angeles,因为 2020-07-22T10:30:00 不在任何时区。它不是 UTC 或 GMT 或其他任何时间。这是一个裸露的约会时间。

java.time.LocalDateTime is also a bare date time. It does not have a time zone, exactly like SQL TIMESTAMP. None of its methods apply any kind of time zone conversion which makes its behavior much easier to predict and understand. So don't use java.sql.Timestamp. Use java.time.LocalDateTime.

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);
于 2020-07-24T17:49:27.110 回答
6

对于 Mysql,我们有一个限制。在驱动程序 Mysql 文档中,我们有:

以下是 MySQL Connector/J 的一些已知问题和限制: 当 Connector/J 在结果集上使用 getTimeStamp() 方法检索夏令时 (DST) 切换日的时间戳时,某些返回值可能是错误的。连接到数据库时,可以通过使用以下连接选项来避免错误:

useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC

因此,当我们不使用此参数并且我们setTimestamp or getTimestamp使用日历或不使用日历调用时,我们在 jvm 时区中有时间戳。

例子 :

jvm 时区是 GMT+2。在数据库中,我们有一个时间戳:1461100256 = 19/04/16 21:10:56,000000000 GMT

Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "");
props.setProperty("useTimezone", "true");
props.setProperty("useLegacyDatetimeCode", "false");
props.setProperty("serverTimezone", "UTC");
Connection con = DriverManager.getConnection(conString, props);
......
Calendar nowGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar nowGMTPlus4 = Calendar.getInstance(TimeZone.getTimeZone("GMT+4"));
......
rs.getTimestamp("timestampColumn");//Oracle driver convert date to jvm timezone and Mysql convert date to GMT (specified in the parameter)
rs.getTimestamp("timestampColumn", nowGMT);//convert date to GMT 
rs.getTimestamp("timestampColumn", nowGMTPlus4);//convert date to GMT+4 timezone

第一种方法返回:1461100256000 = 19/04/2016 - 21:10:56 GMT

第二种方法返回:1461100256000 = 19/04/2016 - 21:10:56 GMT

第三种方法返回:1461085856000 = 19/04/2016 - 17:10:56 GMT

而不是 Oracle,当我们使用相同的调用时,我们有:

第一种方法返回:1461093056000 = 19/04/2016 - 19:10:56 GMT

第二种方法返回:1461100256000 = 19/04/2016 - 21:10:56 GMT

第三种方法返回:1461085856000 = 19/04/2016 - 17:10:56 GMT

注意: 不需要为 Oracle 指定参数。

于 2016-05-02T13:06:28.570 回答
6

您可以使用以下方法将时间戳存储在特定于您所需区域/区域 ID 的数据库中。

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());

人们常犯的一个错误是使用LocaleDateTime获取那个瞬间的时间戳,这会丢弃任何特定于您的区域的信息,即使您稍后尝试转换它也是如此。它不了解区域。

请注意Timestamp是类java.sql.Timestamp

于 2020-07-24T14:53:06.090 回答
5

这是特定于您的驱动程序的。您需要在 Java 程序中提供一个参数来告诉它您要使用的时区。

java -Duser.timezone="America/New_York" GetCurrentDateTimeZone

此外:

to_char(new_time(sched_start_time, 'CURRENT_TIMEZONE', 'NEW_TIMEZONE'), 'MM/DD/YY HH:MI AM')

在正确处理转换方面也可能有价值。取自这里

于 2012-12-28T14:01:46.577 回答
-1

If your problem is to get a timestamp of the local zone, you can use this:

Timestamp.from(Instant.now()).toLocalDateTime()
于 2021-03-18T09:54:24.950 回答