java - MySql datetime不存储为UTC,而是存储在服务器时区中

标签 java mysql datetime timezone

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

从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,因为它缺少任何time zoneoffset-from-UTC指示符。

养成尽可能在日期时间文本中使用标准ISO 8601格式的习惯。对于带有日期的日期,则为YYYY-MM-DDTHH: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的格式指定proper time zone name,例如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框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendarSimpleDateFormat

现在位于Joda-Time中的maintenance mode项目建议迁移到java.time类。

要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

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

在哪里获取java.time类?


Java SE 8Java SE 9Java SE 10Java SE 11和更高版本-具有捆绑实现的标准Java API的一部分。


Java 9添加了一些次要功能和修复。

Java SE 6Java SE 7


大多数java.time功能都被反向移植到ThreeTen-Backport中的Java 6和7。

Android


更高版本的Android捆绑了java.time类的实现。
对于较早的Android(<26),ThreeTenABP项目改编为ThreeTen-Backport(如上所述)。请参见How to use ThreeTenABP…



ThreeTen-Extra项目使用其他类扩展了java.time。该项目是将来可能向java.time添加内容的试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

关于java - MySql datetime不存储为UTC,而是存储在服务器时区中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54205320/

相关文章:

java - 如何使用 Netbeans 构建这个 RMI 项目?

MYSQL 选取每个日期的最晚时间

python - 如何在Python中将时间四舍五入到小数点后一位毫秒

PHP:如何从模式 H:i (17:45) 创建时间戳?

mysql - 查询没有时间的 date_created 与没有时间的 last_updated 相同

java - Android Studio 中的错误 : setContentView(R.layout.main)

Java MySQL SSL 错误

java - JUnit @BeforeClass 注释在 IDE 中编译正常,但 Maven 构建失败

java - 从 Linux 上 NetBeans 中的 Java 应用程序连接到 MariaDB (Mageia)

php - 即使代码正确,也无法选择数据库表