简体   繁体   English

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

[英]Is java.sql.Timestamp timezone specific?

I have to store UTC dateTime in DB.我必须将 UTC dateTime 存储在 DB 中。
I have converted the dateTime given in specific timezone to UTC.我已将特定时区中给出的日期时间转换为 UTC。 for that I followed the below code.为此,我遵循了以下代码。
My input dateTime is "20121225 10:00:00 Z" timezone is "Asia/Calcutta"我输入的日期时间是“20121225 10:00:00 Z”时区是“亚洲/加尔各答”
My Server/DB(oracle) is running in the same timezone(IST) "Asia/Calcutta"我的服务器/数据库(oracle)运行在同一时区(IST)“亚洲/加尔各答”

Get the Date object in this specific Timezone获取此特定时区中的日期对象

        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()));

Store into DB存入数据库

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

OUTPUT输出

DB (oracle) has stored the same given dateTime: "20121225 10:00:00 not in UTC. DB(oracle)存储了相同的给定dateTime: "20121225 10:00:00 not in UTC。

I have confirmed from the below sql.我已经从下面的 sql 中确认了。

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

My DB server also running on the same timezone "Asia/Calcutta"我的数据库服务器也在同一时区“亚洲/加尔各答”上运行

It gives me the below appearances它给了我以下外观

  1. Date.getTime() is not in UTC Date.getTime()不是 UTC
  2. Or Timestamp is has timezone impact while storing into DB What am I doing wrong here?或者时间戳在存储到数据库时有时区影响我在这里做错了什么?

One more question:还有一个问题:

Will timeStamp.toString() print in local timezone like java.util.date does? timeStamp.toString()会像java.util.date那样在本地时区打印吗? Not UTC?不是UTC?

Although it is not explicitly specified for setTimestamp(int parameterIndex, Timestamp x) drivers have to follow the rules established by the setTimestamp(int parameterIndex, Timestamp x, Calendar cal) javadoc :尽管没有明确指定setTimestamp(int parameterIndex, Timestamp x)驱动程序必须遵循setTimestamp(int parameterIndex, Timestamp x, Calendar cal) javadoc建立的规则:

Sets the designated parameter to the given java.sql.Timestamp value, using the given Calendar object.使用给定的Calendar对象将指定参数设置为给定的java.sql.Timestamp值。 The driver uses the Calendar object to construct an SQL TIMESTAMP value, which the driver then sends to the database.驱动程序使用Calendar对象构造一个 SQL TIMESTAMP值,然后驱动程序将其发送到数据库。 With a Calendar object, the driver can calculate the timestamp taking into account a custom time zone.使用Calendar对象,驱动程序可以在考虑自定义时区的情况下计算时间戳。 If no Calendar object is specified, the driver uses the default time zone, which is that of the virtual machine running the application.如果未指定Calendar对象,则驱动程序使用默认时区,即运行应用程序的虚拟机的时区。

When you call with setTimestamp(int parameterIndex, Timestamp x) the JDBC driver uses the time zone of the virtual machine to calculate the date and time of the timestamp in that time zone.当您使用setTimestamp(int parameterIndex, Timestamp x)调用时setTimestamp(int parameterIndex, Timestamp x) JDBC 驱动程序使用虚拟机的时区来计算该时区中时间戳的日期和时间。 This date and time is what is stored in the database, and if the database column does not store time zone information, then any information about the zone is lost (which means it is up to the application(s) using the database to use the same time zone consistently or come up with another scheme to discern timezone (ie store in a separate column).此日期和时间是存储在数据库中的内容,如果数据库列不存储时区信息,则有关该时区的任何信息都将丢失(这意味着使用数据库的应用程序可以使用始终使用相同的时区或提出另一种方案来识别时区(即存储在单独的列中)。

For example: Your local time zone is GMT+2.例如:您当地的时区是 GMT+2。 You store "2012-12-25 10:00:00 UTC".您存储“2012-12-25 10:00:00 UTC”。 The actual value stored in the database is "2012-12-25 12:00:00".数据库中存储的实际值为“2012-12-25 12:00:00”。 You retrieve it again: you get it back again as "2012-12-25 10:00:00 UTC" (but only if you retrieve it using getTimestamp(..) ), but when another application accesses the database in time zone GMT+0, it will retrieve the timestamp as "2012-12-25 12:00:00 UTC".您再次检索它:您将其再次检索为“2012-12-25 10:00:00 UTC”(但前提是您使用getTimestamp(..)检索它),但是当另一个应用程序在 GMT 时区访问数据库时+0,它将检索时间戳为“2012-12-25 12:00:00 UTC”。

If you want to store it in a different timezone, then you need to use the setTimestamp(int parameterIndex, Timestamp x, Calendar cal) with a Calendar instance in the required timezone.如果要将其存储在不同的时区,则需要将setTimestamp(int parameterIndex, Timestamp x, Calendar cal)与所需时区中的 Calendar 实例一起使用。 Just make sure you also use the equivalent getter with the same time zone when retrieving values (if you use a TIMESTAMP without timezone information in your database).只需确保在检索值时还使用具有相同时区的等效 getter(如果您在数据库中使用没有时区信息的TIMESTAMP )。

So, assuming you want to store the actual GMT timezone, you need to use:因此,假设您要存储实际的 GMT 时区,则需要使用:

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

With JDBC 4.2 a compliant driver should support java.time.LocalDateTime (and java.time.LocalTime ) for TIMESTAMP (and TIME ) through get/set/updateObject .对于 JDBC 4.2,兼容的驱动程序应该通过get/set/updateObject支持TIMESTAMP (和TIME )的java.time.LocalDateTime (和java.time.LocalTime )。 The java.time.Local* classes are without time zones, so no conversion needs to be applied (although that might open a new set of problems if your code did assume a specific time zone). java.time.Local*类没有时区,因此不需要应用任何转换(尽管如果您的代码确实假定了特定时区,这可能会引发一系列新问题)。

I think the correct answer should be java.sql.Timestamp is NOT timezone specific.我认为正确的答案应该是 java.sql.Timestamp 不是特定于时区的。 Timestamp is a composite of java.util.Date and a separate nanoseconds value.时间戳是 java.util.Date 和单独的纳秒值的组合。 There is no timezone information in this class.此类中没有时区信息。 Thus just as Date this class simply holds the number of milliseconds since January 1, 1970, 00:00:00 GMT + nanos.因此,就像 Date 一样,这个类只保存自 1970 年 1 月 1 日,格林威治标准时间 00:00:00 + nanos 以来的毫秒数。

In PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal) Calendar is used by the driver to change the default timezone.在 PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal) 中,驱动程序使用 Calendar 来更改默认时区。 But Timestamp still holds milliseconds in GMT.但是时间戳仍然在格林威治标准时间保持毫秒。

API is unclear about how exactly JDBC driver is supposed to use Calendar. API 不清楚 JDBC 驱动程序究竟应该如何使用 Calendar。 Providers seem to feel free about how to interpret it, eg last time I worked with MySQL 5.5 Calendar the driver simply ignored Calendar in both PreparedStatement.setTimestamp and ResultSet.getTimestamp.提供者似乎对如何解释它感到自由,例如,上次我使用 MySQL 5.5 日历时,驱动程序简单地忽略了 PreparedStatement.setTimestamp 和 ResultSet.getTimestamp 中的 Calendar。

The answer is that java.sql.Timestamp is a mess and should be avoided.答案是java.sql.Timestamp是一团糟,应该避免。 Use java.time.LocalDateTime instead.请改用java.time.LocalDateTime

So why is it a mess?那么为什么会一团糟呢? From the java.sql.Timestamp JavaDoc, a java.sql.Timestamp is a "thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL TIMESTAMP value".java.sql.Timestamp JavaDoc 中, java.sql.Timestamp是“ java.util.Date瘦包装器,它允许 JDBC API 将其识别为 SQL TIMESTAMP 值”。 From the java.util.Date JavaDoc, "the Date class is intended to reflect coordinated universal time (UTC)".java.util.Date JavaDoc 中,“ Date类旨在反映协调世界时 (UTC)”。 From the ISO SQL spec a TIMESTAMP WITHOUT TIME ZONE "is a data type that is datetime without time zone".从 ISO SQL 规范中,TIMESTAMP WITHOUT TIME ZONE“是一种没有时区的日期时间数据类型”。 TIMESTAMP is a short name for TIMESTAMP WITHOUT TIME ZONE. TIMESTAMP 是 TIMESTAMP WITHOUT TIME ZONE 的简称。 So a java.sql.Timestamp "reflects" UTC while SQL TIMESTAMP is "without time zone".所以java.sql.Timestamp “反映”UTC 而 SQL TIMESTAMP 是“没有时区”。

Because java.sql.Timestamp reflects UTC its methods apply conversions.因为java.sql.Timestamp反映了 UTC,所以它的方法应用了转换。 This causes no end of confusion.这会导致无休止的混乱。 From the SQL perspective it makes no sense to convert a SQL TIMESTAMP value to some other time zone as a TIMESTAMP has no time zone to convert from.从 SQL 的角度来看,将 SQL TIMESTAMP 值转换为其他时区是没有意义的,因为 TIMESTAMP 没有要转换的时区。 What does it mean to convert 42 to Fahrenheit?将 42 转换为华氏度是什么意思? It means nothing because 42 does not have temperature units.这没有任何意义,因为 42 没有温度单位。 It's just a bare number.这只是一个空洞的数字。 Similarly you can't convert a TIMESTAMP of 2020-07-22T10:38:00 to Americas/Los Angeles because 2020-07-22T10:30:00 is not in any time zone.同样,您无法将 2020-07-22T10:38:00 的 TIMESTAMP 转换为美洲/洛杉矶,因为 2020-07-22T10:30:00 不在任何时区。 It's not in UTC or GMT or anything else.它不是 UTC 或 GMT 或其他任何东西。 It's a bare date time.这是一个裸露的约会时间。

java.time.LocalDateTime is also a bare date time. java.time.LocalDateTime也是一个裸日期时间。 It does not have a time zone, exactly like SQL TIMESTAMP.它没有时区,与 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 .所以不要使用java.sql.Timestamp Use java.time.LocalDateTime .使用java.time.LocalDateTime

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

For Mysql, we have a limitation.对于 Mysql,我们有一个限制。 In the driver Mysql doc , we have :驱动程序 Mysql doc 中,我们有:

The following are some known issues and limitations for MySQL Connector/J: When Connector/J retrieves timestamps for a daylight saving time (DST) switch day using the getTimeStamp() method on the result set, some of the returned values might be wrong.以下是 MySQL Connector/J 的一些已知问题和限制: 当 Connector/J 使用结果集上的 getTimeStamp() 方法检索夏令时 (DST) 切换日的时间戳时,某些返回值可能是错误的。 The errors can be avoided by using the following connection options when connecting to a database:在连接到数据库时,可以通过使用以下连接选项来避免这些错误:

useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC

So, when we do not use this parameters and we call setTimestamp or getTimestamp with calendar or without calendar, we have the timestamp in the jvm timezone.因此,当我们不使用此参数并使用日历或不使用日历调用setTimestamp or getTimestamp时,我们将获得 jvm 时区中的时间戳。

Example :例子 :

The jvm timezone is GMT+2. jvm 时区是 GMT+2。 In the database, we have a timestamp : 1461100256 = 19/04/16 21:10:56,000000000 GMT在数据库中,我们有一个时间戳: 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

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

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

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

Instead of Oracle, when we use the same calls, we have :而不是 Oracle,当我们使用相同的调用时,我们有:

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

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

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

NB : It is not necessary to specify the parameters for Oracle.注意:不必为 Oracle 指定参数。

It is specific from your driver.它是特定于您的驱动程序的。 You need to supply a parameter in your Java program to tell it the time zone you want to use.您需要在 Java 程序中提供一个参数来告诉它您要使用的时区。

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

Further this:进一步这个:

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

May also be of value in handling the conversion properly.在正确处理转换方面也可能有价值。 Taken from here取自这里

You can use the below method to store the timestamp in database specific to your desired zone/zone Id.您可以使用以下方法将时间戳存储在特定于所需区域/区域 ID 的数据库中。

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

A common mistake people do is use LocaleDateTime to get the timestamp of that instant which discards any information specif to your zone even if you try to convert it later.人们经常犯的一个错误是使用LocaleDateTime来获取那个瞬间的时间戳,即使您稍后尝试转换它也会丢弃任何特定于您的区域的信息。 It does not understand the Zone.它不了解区域。

Please note Timestamp is of the class java.sql.Timestamp .请注意Timestamp属于java.sql.Timestamp类。

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM