java.sql.Timestamp时区是否具体?

时间:2012-12-28 13:53:41

标签: java oracle date datetime jdbc

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

在此特定时区获取日期对象

        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:00而不是UTC。

我已从下面的sql确认。

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

我的数据库服务器也运行在同一时区“Asia / Calcutta”

它给了我以下的外观

  1. Date.getTime()不在UTC
  2. 或者时间戳在存储到数据库时会产生时区影响 我在这做错了什么?
  3. 还有一个问题:

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

7 个答案:

答案 0 :(得分:95)

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

  

使用给定的java.sql.Timestamp对象将指定参数设置为给定的Calendar值。驱动程序使用Calendar对象构造SQL TIMESTAMP值,然后驱动程序将其发送到数据库。使用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)和Calendar实例。确保在检索值时也使用具有相同时区的等效getter(如果在数据库中使用TIMESTAMP时没有时区信息)。

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

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

对于JDBC 4.2,合规驱动程序应支持java.time.LocalDateTime(和java.time.LocalTimeTIMESTAMP(和TIME)到get/set/updateObjectjava.time.Local*类没有时区,因此不需要应用任何转换(尽管如果您的代码确实采用了特定的时区,这可能会打开一组新的问题。)

答案 1 :(得分:28)

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

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

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

答案 2 :(得分: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')

在正确处理转换方面也很有价值。取自here

答案 3 :(得分:4)

对于Mysql,我们有一个限制。 在driver Mysql doc中,我们有:

  

以下是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

NB: 没有必要为Oracle指定参数。

答案 4 :(得分:4)

答案是java.sql.Timestamp很烂,应该避免。请改用java.time.LocalDateTime

那为什么会一团糟?在java.sql.Timestamp JavaDoc中,java.sql.Timestamp是“ java.util.Date周围的薄包装,它允许JDBC API将其标识为SQL TIMESTAMP值”。在java.util.Date JavaDoc中,“ Date类旨在反映协调世界时(UTC)”。根据ISO SQL规范,TIMESTAMP WITH TIME ZONE“是一种没有时区的日期时间的数据类型”。 TIMESTAMP是TIMESTAMP WITHTIME TIME ZONE的简称。因此,java.sql.Timestamp在SQL TIMESTAMP为“无时区”时“反映” UTC。

由于java.sql.Timestamp反映了UTC,因此其方法会应用转化。这不会引起混乱。从SQL的角度来看,将TIMESTAMP值转换为其他时区是没有意义的,因为TIMESTAMP没有时区可以转换。将42转换为华氏温度是什么意思?这没有任何意义,因为42没有温度单位。这只是一个空白。同样,您无法将2020-07-22T10:38:00的TIMESTAMP转换为美洲/洛杉矶,因为2020-07-22T10:30:00不在任何时区。它不在UTC或GMT或其他任何版本中。这是约会时间。

java.time.LocalDateTime也是一个空日期时间。它没有时区,与SQL TIMESTAMP完全一样。它的方法都没有应用任何类型的时区转换,这使其行为更容易预测和理解。因此,请勿使用java.sql.Timestamp。使用java.time.LocalDateTime

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);

答案 5 :(得分:1)

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

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

人们经常犯的错误是使用LocaleDateTime来获取该瞬间的时间戳,该时间戳会将任何信息指定丢弃到您的区域,即使您稍后尝试对其进行转换。它不了解区域。

请注意Timestamp属于java.sql.Timestamp类。

答案 6 :(得分:-1)

如果您的问题是获取本地区域的时间戳,您可以使用:

Timestamp.from(Instant.now()).toLocalDateTime()