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.Date
、java.sql.Date
或java.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/Montreal
、Africa/Casablanca
或Pacific/Auckland
。切勿使用2-4个字母的缩写,如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
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
。有什么区别?ZonedDateTime
和OffsetDateTime
都表示时间线上的一个时刻。偏移量仅为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 ) ;
上面几行中看到的odt
和zdt
都表示同一时刻,时间线上的同一点。只有它们的挂钟时间不同,因为大多数数据库在您想查看巴黎时间时存储和检索UTC。
关于java.time
java.time框架构建在Java8及更高版本中。这些类取代了诸如java.util.Date
、Calendar
、&SimpleDateFormat
。
现在处于维护模式的JodaTime项目建议迁移到java.Time类。
要了解更多信息,请参阅Oracle教程。并在Stack Overflow中搜索许多示例和解释。规范是JSR310。
您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。不需要字符串,也不需要java.sql.*
类。
从哪里获得java.time类?
- Java SE 8、Java SE 9和Java SE 10,爪哇SE 11及更高版本-标准Java API的一部分,带有捆绑实现。
- Java9添加了一些小功能和修复程序
- Java SE 6和Java SE 7
- 大多数Java.time功能都是向后移植到Java 6&7英寸ThreeTen背包
- Android
- java.time类的Android捆绑包的后续版本
- 对于早期的Android(<26),ThreeTenABP项目适用于ThreeTen Backport(如上所述)。请参阅如何使用ThreeTenABP…
ThreeTen Extra项目通过附加类扩展java.time。这个项目是将来可能添加到java.time的试验场。您可以在这里找到一些有用的类,如Interval
、YearWeek
、YearQuarter
等等。