2014-09-26 114 views
2

我正在开发一个Java应用程序,它位于与我的不同时区的服务器上的MySQL数据库,并且我试图决定在我的数据库上使用DATETIME还是TIMESTAMP。Java和MySQL中的时间戳和时区转换

在阅读Should I use field 'datetime' or 'timestamp'?MySQL documentation之类的问题之后,我决定TIMESTAMP对我更好,因为它将值转换为UTC存储,并返回到当前时区进行检索。

另外,正如用户Jesper在this线程中解释的那样,java.util.Date对象在内部只有一个UTC时间戳(即Epoch以来的毫秒数),而当您执行toString()时,它会根据您的当前时区。

对我来说,这看起来很不错:将日期时间存储为UTC时间戳,然后根据当前时区显示它们。

我正要那样做,但后来我发现这个从Java documentation用于预处理语句,并得到了很困惑:

无效的setTimestamp(INT parameterIndex, 时间戳X, 日历CAL) throws SQLException

使用给定的Calendar对象将指定参数设置为给定的java.sql.Timestamp值 。驱动程序使用日历对象 构建SQL TIMESTAMP值,然后驱动程序将其发送到 数据库。使用Calendar对象时,驱动程序可以计算考虑到自定义时区的 时间戳。如果未指定日历对象 ,则该驱动程序使用默认时区,该时区是运行应用程序的虚拟机的 。

在此之前,我认为时间戳通常以UTC为准。为什么人们会想要一个本地化的时间戳而不是本地化的表示的呢?这不会让每个人都感到困惑吗?

这些转换是如何工作的?如果Java采用UTC时间戳并将其转换为任意时区,它如何告诉MySQL它在哪个时区?

MySQL不会假定此时间戳记是UTC,然后检索不正确的本地化值?

回答

5

日期时间处理是一个烂摊子

answer by Teo第一段是非常有见地的和正确的:在Java中的日期时间处理是一个烂摊子。所有其他语言的Ditto &我知道的开发环境。日期时间的工作是困难和棘手的,尤其是容易出错和令人沮丧的,因为我们认为它是直观的日期时间。但是,当涉及到数据类型,数据库,序列化,本地化,跨时区调整以及计算机编程随附的所有其他手续时,“直观地”不会削减它。

不幸的是,计算机行业基本上选择了忽略日期时间工作的这个问题。正如统一码需要很长时间才能被发明,因为明显的需求,业界也一直在解决日期处理问题上下功夫。

不依赖于计数纪元以来

但我必须和它的结论不同意。使用自数计数并不是最好的解决方案。使用count-since-epoch本质上是令人困惑,容易出错和不兼容的。

我们做数学,而不是使用位创建数字数据类型。我们创建字符串类来处理处理文本的细节,而不是纯粹的八位字节。我们也应该创建数据类型和类来处理日期时间值。

早期的Java团队(和他们之前的IBM & Taligent)尝试使用java.util.Date和java.util.Calendar及相关类。不幸的是,这种尝试是不够的。虽然日期时间本质上是令人困惑的,但这些课程更加混淆了。

乔达时间

据我所知,Joda-Time项目是采取的日期时间彻底,可靠和成功的方式,第一个项目。即便如此,Joda-Time的创造者并不完全满意。他们继续在Java 8中创建java.time package,并将其扩展为threeten-extra project。 Joda-Time和java.time共享相似的概念,但是不同,每个都有一些优点。

数据库问题

具体来说,java.util.Date & .Calendar类缺乏日期仅值,而时间的日和时区。他们缺乏没有日期和时区的时间价值。在Java 8之前,Java团队添加了被称为java.sql.Datejava.sql.Time类的攻击,这些类是伪装成仅限日期的日期时间值。 Joda-Time和java.time均提供LocalDateLocalTime类。

另一个具体问题是java.util.Date的分辨率为毫秒,但数据库通常使用微秒或纳秒。为了消除这种差距,Java早期团队创建了另一个黑客类别java.sql.Timestamp。虽然技术上是一个java.util.Date子类,但它也可以跟踪小数秒到纳秒分辨率。所以在进入和退出这种类型时,您可能会失去或获得更精细的秒数粒度,而不会意识到这一事实。所以这可能意味着你期望的平等值不是。

混淆的另一个来源是SQL数据类型TIMESTAMP WITH TIME ZONE。由于时区信息是而不是存储,该名称是一个误称。将名称设想为TIMESTAMP WITH RESPECT FOR TIME ZONE,因为在将日期时间值转换为UTC时,会使用任何传递的时区偏移信息。

具有纳秒级分辨率的java.time包具有一些特定功能,可以更好地与数据库进行日期时间数据通信。

我可以写更多,但是这些信息可以从搜索StackOverflow中搜索诸如joda,java.time,sql timestamp和JDBC等单词。

使用Joda-Time和JDBC的例子Postgres。 Joda-Time使用immutable objectsthread-safety。因此,我们不是修改一个实例(“mutate”),而是根据原始值创建一个新实例。

String sql = "SELECT now();"; 
… 
java.sql.Timestamp now = myResultSet.getTimestamp(1); 
DateTime dateTimeUtc = new DateTime(now , DateTimeZone.UTC); 
DateTime dateTimeMontréal = dateTimeUtc.withZone(DateTimeZone.forID("America/Montreal")); 

专注于UTC

在此之前,我还以为在时间戳UTC是按照惯例始终。为什么人们会想要一个本地化时间戳而不是本地化表示呢?这不会让每个人都感到困惑吗?

确实。 SQL标准定义了一个TIMESTAMP WITHOUT TIME ZONE,它忽略并删除任何包含的时区数据。我无法想象这有用。这位Postgres专家David E. Wheeler,says as much in recommending总是使用TIMESTAMP WITH TIME ZONE。 Wheeler引用了一个狭义的技术例外(分区),甚至在将数据保存到数据库之前将所有值转换为UTC。

最佳做法是在调整到本地化时区以便呈现给用户时以UTC来处理和存储数据。您可能有时想记住其本地时区中的原始日期时间数据;如果是这样,除了转换为UTC之外,还要保存该值

准则

的第一步,以更好的日期时间处理是避免java.util.Date & .Calendar,使用乔达时间和/或java.time,专注于UTC,学习的行为你特定的JDBC驱动程序和特定的数据库(尽管SQL标准,数据库在日期时间处理方面差别很大)。

1

你的问题是现在的问题,我认为这些天是巨大的。 DB(通过SQL)和服务器端本身(通过Java等编程语言)都提供了处理日期和时间的方法。我认为现状是高度不规范和有点混乱(个人意见:)

我的答案是部分,但我会解释为什么。

你是对的,Java的Date(和Calendar)存储时间为自Unix Epoch以来的毫秒数(这很好)。它不仅发生在Java中,而且也发生在其他编程语言中。在我看来,完美的计时架构自然而然地诞生了:Unix时代是1970年1月1日,午夜,UTC。因此,如果你选择的时间存储为自Unix纪元毫秒你有很多的好处:

  • 架构清晰:服务器端可与UTC,客户端显示通过当地时区
  • 数据库简单的时间:你存储一个数(毫秒),而不是像DateTime是否
  • 编程效率复杂的数据结构:在多数编程语言你有日期/时间对象能够构造时服用毫秒因为历元(其如你所说,允许自动转换为客户端侧的时区)

使用此方法时,我发现代码和体系结构更简单,更灵活。我停止试图理解诸如DateTime(或Timestamp)之类的东西,只有在我必须修复遗留代码时才处理它们。