简体   繁体   English

将时间戳转换为Java中的即时添加不必要的时间偏移

[英]Timestamp conversion to instant in Java adds unnecessary time offset

I need to be able to convert data fetched from MySQL database that is stored in a "datetime" fields into Java ZonedDateTime object. 我需要能够将从存储在“ datetime”字段中的MySQL数据库中获取的数据转换为Java ZonedDateTime对象。

ZonedDateTime dt = ZonedDateTime.ofInstant(rs.getTimestamp("Start").toInstant(), UTC_ZONE_ID)

The problem I'm having is that toInstant() adds local time offset to the Timestamp object which I don't need because the datetime is already stored in a UTC format in a database. 我遇到的问题是toInstant()Timestamp对象添加了我不需要的本地时间偏移量,因为datetime已经以UTC格式存储在数据库中。 So when I run the following code: 因此,当我运行以下代码时:

ZonedDateTime startDT = 
        ZonedDateTime.ofInstant(rs.getTimestamp("Start").toInstant(),Globals.LOCALZONEID);
System.out.println(rs.getTimestamp("start"));
System.out.println(rs.getTimestamp("start").toInstant());

I get: 我得到:

2017-06-08 13:15:00.0
2017-06-08T17:15:00Z

I need the time component to stay unchanged. 我需要时间部分保持不变。

I was unable to find any obvious solution to the problem so am I missing something here? 我找不到任何明显的解决方案,所以我在这里丢失了什么吗?

Timestamp & Instant always in UTC TimestampInstant始终在UTC中

The problem I'm having is that .toInstant() add local time offset to the Timestamp object 我遇到的问题是.toInstant()向Timestamp对象添加本地时间偏移

No it does not. 不,不是的。

Neither can have any other zone assigned. 都不能分配任何其他区域。

Do not bunch up your code into a single line. 不要将代码打包成一行。 Break out each step into separate lines so you can debug their values. 将每个步骤分成几行,以便调试它们的值。

java.sql.Timestamp ts = rs.getTimestamp("Start") ;  // Actually in UTC, but it's `toString` method applies JVM’s current default time zone while generating string.
Instant instant = ts.toInstant() ;                  // Same moment, also in UTC.
ZoneId z = ZoneId.of( "America/Montreal" ) ;        // Or call your global var: `Globals.LOCALZONEID`.
ZonedDateTime zdt = instant.atZone( z );            // Same moment, same point on timeline, but with wall-clock time seen in a particular zone.

After that, you may see the issue (or non-issue). 之后,您可能会看到问题(或没有问题)。 If not, edit your question to show the debug values of each of these variables. 如果不是,请编辑您的问题以显示每个变量的调试值。

Do not trust Timestamp::toString 不信任Timestamp::toString

Important: The java.sql.Timestamp::toString method lies . 重要说明: java.sql.Timestamp::toString方法位于 That method applies your JVM's current default time zone while generating the string. 该方法在生成字符串时将应用JVM的当前默认时区。 The actual value is always in UTC. 实际值始终以UTC为单位。 One of many reasons to avoid these troublesome legacy classes. 避免这些麻烦的旧类的众多原因之一。 Run the following code example on your own machine to see the influence of your default time zone on the textual representation of the Timestamp . 在您自己的机器上运行以下代码示例,以查看默认时区对Timestamp文本表示的影响。

Let's run a simulation of that code running live in IdeOne.com . 让我们模拟一下在IdeOne.com中实时运行的代码 The JVM at IdeOne.com defaults to UTC/GMT, so we override default by specifying the default as Pacific/Auckland arbitrarily. IdeOne.com上的JVM默认为UTC / GMT,因此我们通过将默认值任意指定为Pacific/Auckland来覆盖默认值。

Instant now = Instant.now() ;                       // Simulating fetching a `Timestamp` from database by using current moment in UTC.

TimeZone.setDefault( TimeZone.getTimeZone( "Pacific/Auckland" ) ) ;
ZoneId zoneIdDefault = ZoneId.systemDefault() ;
ZoneOffset zoneOffset = zoneIdDefault.getRules().getOffset( now ) ;

java.sql.Timestamp ts = java.sql.Timestamp.from( now ) ;  // Actually in UTC, but it's `toString` method applies JVM’s current default time zone while generating string.
Instant instant = ts.toInstant() ;                  // Same moment, also in UTC.
ZoneId z = ZoneId.of( "America/Montreal" ) ;        // Or call your global var: `Globals.LOCALZONEID`.
ZonedDateTime zdt = instant.atZone( z );            // Same moment, same point on timeline, but with wall-clock time seen in a particular zone.

Current default time zone: Pacific/Auckland 当前默认时区:太平洋/奥克兰

Current default offset-from-UTC: Pacific/Auckland | 当前的默认UTC偏移量:太平洋/奥克兰| total seconds: 43200 总秒数:43200

now.toString(): 2017-06-09T04:41:10.750Z now.toString():2017-06-09T04:41:10.750Z

ts.toString(): 2017-06-09 16:41:10.75 ts.toString():2017-06-09 16:41:10.75

instant.toString(): 2017-06-09T04:41:10.750Z Instant.toString():2017-06-09T04:41:10.750Z

z.toString(): America/Montreal z.toString():美国/蒙特利尔

zdt.toString(): 2017-06-09T00:41:10.750-04:00[America/Montreal] zdt.toString():2017-06-09T00:41:10.750-04:00 [美国/蒙特利尔]

Avoid legacy date-time classes 避免使用旧的日期时间类

The old date-time classes found outside the java.time package are troublesome, confusing, badly designed, and flawed. 在java.time包之外找到的旧的日期时间类麻烦,混乱,设计不良和有缺陷。 Avoid them whenever possible. 尽可能避免使用它们。 This includes java.sql.Timestamp . 这包括java.sql.Timestamp

Your JDBC 4.2 compliant driver can directly address java.time types by calling PreparedStatement::setObject and ResultSet::getObject . 兼容JDBC 4.2的 驱动程序可以通过调用PreparedStatement::setObjectResultSet::getObject直接寻址java.time类型。

myPreparedStatement.setObject( … , instant ) ;

… and … ……和……

Instant instant = myResultSet.getObject( … , Instant.class ) ;

If using a JDBC driver not yet updated to JDBC 4.2 and java.time, convert briefly to java.sql.Timestamp using new methods added to the old class: from ( Instant ) , toInstant() , and such. 如果使用尚未更新到JDBC 4.2和java.time的JDBC驱动程序,请使用添加到旧类中的新方法from ( Instant )toInstant()简短地转换为java.sql.Timestamp But beyond exchanging data with the database, do all your real work (business logic) in java.time objects. 但是,除了与数据库交换数据之外,还需要在java.time对象中完成所有实际工作(业务逻辑)。

myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) ) ;

… and … ……和……

Instant instant = myResultSet.getTimestamp( … ).toInstant() ;

About java.time 关于java.time

The java.time framework is built into Java 8 and later. java.time框架内置于Java 8及更高版本中。 These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat . 这些类取代了麻烦的旧的旧式日期时间类,例如java.util.DateCalendarSimpleDateFormat

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes. 现在处于维护模式Joda-Time项目建议迁移到java.time类。

To learn more, see the Oracle Tutorial . 要了解更多信息,请参见Oracle教程 And search Stack Overflow for many examples and explanations. 并在Stack Overflow中搜索许多示例和说明。 Specification is JSR 310 . 规格为JSR 310

You may exchange java.time objects directly with your database. 您可以直接与数据库交换java.time对象。 Use a JDBC driver compliant with JDBC 4.2 or later. 使用与JDBC 4.2或更高版本兼容的JDBC驱动程序 No need for strings, no need for java.sql.* classes. 不需要字符串,不需要java.sql.*类。

Where to obtain the java.time classes? 在哪里获取java.time类?

The ThreeTen-Extra project extends java.time with additional classes. ThreeTen-Extra项目使用其他类扩展了java.time。 This project is a proving ground for possible future additions to java.time. 该项目为将来可能在java.time中添加内容提供了一个试验场。 You may find some useful classes here such as Interval , YearWeek , YearQuarter , and more . 您可以在这里找到一些有用的类,比如IntervalYearWeekYearQuarter ,和更多

The old classes like java.sql.Timestamp have quite some issues with their design. 诸如java.sql.Timestamp类的旧类在设计上存在很多问题。 One of the things that often cause confusion is that Timestamp.toString() prints the time in the JVM's time zone even though the time Timestamp in it just holds a point in time with no time zone. 经常引起混乱的事情之一是Timestamp.toString()在JVM的时区中显示时间,即使其中的时间Timestamp只是一个没有时区的时间点。 To be concrete, when a Timestamp is equal to 2017-06-08T17:15:00Z in UTC and you print it on a computer running Pittsburg time (which at this time of year has an offset of -4:00 from UTC), the Timestamp.toString() is implicitly called, it reads the JVM's time zone and prints the time as 13:15:00.0 just because this time in Pennsylvania is equal to the UTC time in the Timestamp . 具体来说,当Timestamp等于UTC的2017-06-08T17:15:00Z并且您将其打印在运行匹兹堡时间的计算机上(一年中的这个时间与UTC偏移-4:00), Timestamp.toString()被隐式调用,它读取JVM的时区并将时间打印为13:15:00.0 ,因为宾夕法尼亚州的时间等于Timestamp的UTC时间。

So to cut a long story short, do not worry, both your Timestamp and your Instant are correct. 因此,总而言之,不要担心,您的TimestampInstant都是正确的。

I think this code snippet answers your question. 我认为此代码段回答了您的问题。 This takes in a String in a local time zone, converts it to UTC, and stores it in a db. 这将在本地时区中获取String,将其转换为UTC并将其存储在db中。

    //Getting the LocalDateTime Objects from String values
    DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd kk:mm"); 
    String txtStartTime = "2017-03-29 12:00";

    LocalDateTime ldtStart = LocalDateTime.parse(txtStartTime, df);


    //Convert to a ZonedDate Time in UTC
    ZoneId zid = ZoneId.systemDefault();

    ZonedDateTime zdtStart = ldtStart.atZone(zid);
    System.out.println("Local Time: " + zdtStart);
    ZonedDateTime utcStart = zdtStart.withZoneSameInstant(ZoneId.of("UTC"));
    System.out.println("Zoned time: " + utcStart);
    ldtStart = utcStart.toLocalDateTime();
    System.out.println("Zoned time with zone stripped:" + ldtStart);
    //Create Timestamp values from Instants to update database
    Timestamp startsqlts = Timestamp.valueOf(ldtStart); //this value can be inserted into database
    System.out.println("Timestamp to be inserted: " +startsqlts);

    //insertDB(startsqlts);

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

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