是java.sql.Timestamp时区特定吗?


81

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

获取此特定时区中的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

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

它给了我以下外观

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

还有一个问题:

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


4
要回答标题中的问题,不可以,java.sql.Timestamp基于UTC。显示时间戳是特定于时区的。
吉尔伯特·勒布朗克

@吉尔伯特·勒布朗克谢谢!您能否回答我“ new java.sql.Timestamp(parsedDate.getTime())”将为我的输入返回GMT时间戳对象?
卡纳加维卢·苏古玛

1
@GilbertLeBlanc尽管java.sql.Timestamp位于UTC中,但当存储在没有时区信息的TIMESTAMP字段中时,它使用虚拟机当前时区中的等效日期/时间
Mark Rotteveel

@Kanagavelu Sugumar:是的。Date类的getTime方法返回自格林尼治标准时间1970年1月1日00:00:00以来的毫秒数。
吉尔伯特·勒布朗克

@MarkRotteveel马克我不明白您的意思“在没有时区信息的TIMESTAMP字段中存储,它使用虚拟机当前时区中的等效日期/时间”。请您详细说明一下。这对我会有所帮助。
卡纳加维卢·苏古玛

Answers:


104

尽管未明确为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)在所需时区中将其与Calendar实例一起使用。只需确保在检索值时还使用具有相同时区的等效getter(如果TIMESTAMP在数据库中使用不带时区的信息)。

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

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

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


我想将时间存储在GMT中。我相信我在GMT的时间戳。所以我该怎么做?是setTimestamp(int parameterIndex,Timestamp tzGmtObj,Calendar calGmtObj)吗?
卡纳加维卢·苏古玛

如果“ setTimestamp(int parameterIndex,Timestamp x)”使用虚拟机的时区。x在代码中的作用是什么?
Kanagavelu Sugumar 2012年

1
@KanagaveluSugumar这是要设置的实际时间戳记值。驱动程序仅将Calendar对象用于时区信息,而不是其值!
Mark Rotteveel 2012年

1
太好了,它正在运作:)我花了大量时间证明(Date / Timestamp).getTime()保留时区特定时间(以毫秒为单位)。现在我知道不是。无论时区如何,它始终提供UTC毫秒。但是(Date / Timestamp).toString()是时区特定的。非常感谢。
卡纳加维卢·苏古玛

4
toString()java.lang.Date(和java.lang.Timestamp)将始终存在在你的本地时区。
Mark Rotteveel 2012年

35

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

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

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


关于MySQL 5.5的有趣评论
Alex

1
我认为从Java 8开始,这是错误的。在时间戳中(因为它从java.util.Date扩展而来),有一个Calendar属性,该属性具有一个实际上包含时区的zoneinfo属性。您仍然可以使用.getTime()获取UTC时间,因为时间戳也存储了它,但是它具有时区信息。
Jorge.V,

1
您的意思是说它保存的是自1970年1月1日UTC 00:00:00 UTC以来的毫秒数,而不是GMT。GMT具有夏令时。UNIX纪元是自1970年1月1日UTC 00:00以来的毫秒数。
柯比

6

对于Mysql,我们有一个限制。在驱动程序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

注意: 不必为Oracle指定参数。


1
我猜您可以为oracle将会话时区设置为UTC,以实现与MySQL在非日历情况下相同的转换。“ ALTER SESSION SET TIME_ZONE = 'UTC'”。
eckes 2016年

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')

在正确处理转换中也可能有价值。从这里取


您是说要我遵循 stackoverflow.com/questions/2858182/… 还是要设置我的JVM应用程序在GMT中运行?
Kanagavelu Sugumar 2012年

@KanagaveluSugumar取决于。如果要在启动时修改每个查询的时间戳,则仅在修改SOME(意味着比EVERY小)时修改它,然后按照您引用的帖子的方式修改每个查询。
Woot4Moo 2012年

3

答案是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 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的时间戳转换为美洲/洛杉矶,因为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);

说得很好。谢谢。
Ole VV

0

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

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

人们LocaleDateTime经常犯的一个错误是使用该时刻的时间戳,该时间戳会将任何信息指定丢弃到您的区域,即使您稍后尝试转换它。它不了解区域。

请注意Timestamp该类java.sql.Timestamp

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.