MySQL日期时间不存储为UTC,而是存储在服务器时区中



MySQL数据库:将Java Date存储到DATETIME类型的数据库列中,例如

Table Foo
-----------------------
id         |  time
-----------------------
bigint(20)   datetime

日期是

01/15/19 19:00:00 (Time Zone: UTC+1:00),
DateFormat df = new SimpleDateFormat('MM/dd/yy HH:mm:ss');
df.setTimeZone("Europe/Paris");   // UTC+1:00
Date date = df.parse("01/15/19 19:00:00");
PreparedStatement st = new PreparedStatement("insert into Foo (id, time) values (?, ?)";
st.setObject(1, 1);
st.setObject(2, date);
st.executeUpdate();  

预计将转换为

01/15/19 18:00:00 UTC

并存储在数据库中。

MySQL时区为SYSTEM (UTC-6:00)。JVM时区为UTC-6:00。两者都在同一台机器上运行。存储的值为01/15/19 12:00:00。为什么它存储在服务器时区(而不是UTC)?

SQL查询SELECT time FROM Foo;给出以下结果:

time
-------------------
2019-01-15 12:00:00

更改服务器时区不会影响选择值。

mysql> set time_zone='+8:00';
select time from Foo;
time
-------------------
2019-01-15 12:00:00

tl;dr

使用现代java.time类,不要使用java.util.Datejava.sql.Datejava.sql.Timestamp

myPreparedStatement                   // With JDBC 4.2, wa can directly exchange java.time objects with the database.
.setObject(                           // Pass a `OffsetDateTime` object representing the moment we want stored in the database.
LocalDateTime.parse(              // Parse the input string lacking an indicator of offset or zone.
"2019-01-19T19:00"            // When using strings in standard ISO 8601 format, no need to specify a formatting pattern.
)                                 // Returns a `LocalDateTime`, an ambiguous value without real meaning.
.atZone(                          // Apply a time zone to give meaning to the `LocalDateTime`. 
ZoneId.of( "Europe/Paris" )   // Here we are saying "the 7 PM on that date as seen on the clock on the wall of someone standing in Paris France".
)                                 // Returns a `ZonedDateTime` object.
.toOffsetDateTime()               // Returns an `OffsetDateTime` object as demanded by the JDBC spec, stripping off the time zone to leave on the hours-minutes-seconds from UTC.
)

java.time

您使用的是糟糕的日期-时间类,这些类在几年前被java.time类所取代。尝试调试/理解遗留类真的没有意义,因为它们一团糟。

LocalDateTime

将您的输入字符串解析为LocalDateTime,因为它缺少任何时区或UTC偏移量的指示符。

养成习惯,尽可能使用标准ISO 8601格式的日期-时间文本。对于一天中有时间的日期,应该是YYYY-MM-DDTH:MM:SS,其中T将日期部分与时间部分分隔开。

String input = "2019-01-19T19:00" ;
LocalDateTime ldt = LocalDateTime.parse( input ) ; // No need to specify formatting pattern when using standard ISO 8601 formats.

ZonedDateTime

LocalDateTime类也缺乏任何区域或偏移的概念。所以这个类的对象不代表一个时刻,不是时间线上的一个点。它代表了全球各个时区约26-27小时内的潜在时刻。

你似乎肯定知道,这个日期和时间意味着代表某个特定时区的某个时刻。因此,应用一个时区,为我们模糊的LocalDateTime对象赋予意义。

Continent/Region的格式指定适当的时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用2-4个字母的缩写,如ESTIST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "Europe/Paris" ) ;  

如果您想使用JVM的当前默认时区,请询问它并将其作为参数传递。如果省略,代码将变得模糊不清,我们不确定您是否打算使用默认值,或者您是否像许多程序员一样,没有意识到这个问题。

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

现在我们可以应用ZoneId来获得ZonedDateTime

ZonedDateTime zdt = ldt.atZone( z ) ;

OffsetDateTime

您的JDBC可能接受ZonedDateTime对象,但JDBC 4.2规范要求它接受OffsetDateTime。有什么区别?ZonedDateTimeOffsetDateTime都表示时间线上的一个时刻。偏移量仅为UTC之前或之后的小时分秒数。时区远不止这些。时区是特定地区人民使用的偏移量的过去、现在和未来变化的历史。因此,时区总是更可取的。除了这里,我们使用JDBC与数据库交换java.time对象,我们使用OffsetDateTime编写标准代码。

OffsetDateTime odt = zdt.toOffsetDateTime() ;  // Convert from moment with time zone to a moment with merely an offset-from-UTC.

现在我们可以把这一时刻交给你准备好的发言。

myPreparedStatement( … , odt ) ;  // Pass a `OffsetDateTime` moment as the value for a placeholder in the SQL of your prepared statement.

检索。

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

指定所需的时区。

ZoneId z = ZoneId.of( "Europe/Paris" ) ;
ZonedDateTime zdt = odt.atZone( z ) ;

上面几行中看到的odtzdt都表示同一时刻,时间线上的同一点。只有它们的挂钟时间不同,因为大多数数据库在您想查看巴黎时间时存储和检索UTC。


关于java.time

java.time框架构建在Java8及更高版本中。这些类取代了诸如java.util.DateCalendar、&SimpleDateFormat

现在处于维护模式的JodaTime项目建议迁移到java.Time类。

要了解更多信息,请参阅Oracle教程。并在Stack Overflow中搜索许多示例和解释。规范是JSR310。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。不需要字符串,也不需要java.sql.*类。

从哪里获得java.time类?

  • Java SE 8Java SE 9Java SE 10爪哇SE 11及更高版本-标准Java API的一部分,带有捆绑实现。
    • Java9添加了一些小功能和修复程序
  • Java SE 6Java SE 7
    • 大多数Java.time功能都是向后移植到Java 6&7英寸ThreeTen背包
  • Android
    • java.time类的Android捆绑包的后续版本
    • 对于早期的Android(<26),ThreeTenABP项目适用于ThreeTen Backport(如上所述)。请参阅如何使用ThreeTenABP…

ThreeTen Extra项目通过附加类扩展java.time。这个项目是将来可能添加到java.time的试验场。您可以在这里找到一些有用的类,如IntervalYearWeekYearQuarter等等。

相关内容

  • 没有找到相关文章

最新更新