Java和MySQL中的时间戳和时区转换

时间:2014-09-26 18:20:02

标签: java mysql timestamp

我正在与位于与我的不同时区的服务器上开发一个带有MySQL数据库的Java应用程序,我正在尝试在我的数据库中使用DATETIME或TIMESTAMP。

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

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

对我来说,这看起来是一个好习惯:将日期时间存储为UTC时间戳,然后根据当前时区显示它们。

我就是这样做的,但后来我从Java documentation找到了准备好的陈述,并且非常困惑:

  

void setTimestamp(int parameterIndex,                   时间戳x,                   日历cal)                     抛出SQLException

     

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

在此之前,我认为时间戳总是按UTC惯例。为什么有人想要一个本地化的时间戳而不是本地化的表示呢?这对每个人来说都不会让人感到困惑吗?

这些转化如何运作?如果Java采用UTC时间戳并将其转换为任意时区,那么它如何告诉MySQL它在哪个时区呢?

MySQL不会假设此时间戳是UTC格式,然后检索错误的本地化值吗?

2 个答案:

答案 0 :(得分:6)

日期时间处理是一个混乱

answer by Teo中的第一段非常有见地和正确:Java中的日期时间处理是一团糟。同样适用于所有其他语言&我所知道的开发环境。日期时间工作既困难又棘手,尤其容易出错且令人沮丧,因为我们直观地认为是日期时间。但是直觉上"在涉及数据类型,数据库,序列化,本地化,跨时区调整以及计算机编程带来的所有其他手续时,它不会削减它。

不幸的是,计算机行业基本上选择忽略日期工作的这个问题。正如鉴于显而易见的需要,Unicode花了很长时间才被发明,因此行业也在解决日期时间处理问题方面取得了成功。

不依赖于Count-Since-Epoch

但我必须不同意其结论。使用count-since-epoch并不是最好的解决方案。使用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类的hacks,这是一个伪装成仅限日期的日期时间值。 Joda-Time和java.time都通过提供LocalDateLocalTime类来纠正它。

另一个具体问题是java.util.Date的分辨率为毫秒,但数据库经常使用微秒或纳秒。在弥合这种差异的不明智的尝试中,早期的Java团队创建了另一个hack,即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。

使用带有Postgres的JDBC的Joda-Time示例。 Joda-Time使用immutable objects作为thread-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,使用Joda-Time和/或java.time,专注于UTC,并学习特定JDBC驱动程序和特定数据库的行为(数据库在日期时间处理方面差异很大,尽管有SQL标准)。

的MySQL

警告:我不使用MySQL(我是Postgres类型的人)。

根据version 8 documentationDATETIMETIMESTAMP这两种类型的不同之处在于,第一种类型缺少任何时区概念或从UTC偏移。第二个使用时区的任何指示或伴随输入的UTC偏移指示将该值调整为UTC,然后存储它,并丢弃区域/偏移信息。

所以这两种类型似乎类似于标准的SQL类型:

  • MySQL DATETIME≈SQL-standard TIMESTAMP WITHOUT TIME ZONE
  • MySQL TIMESTAMP≈SQL-standard TIMESTAMP WITH TIME ZONE

对于MySQL DATETIME,请使用Java类LocalDateTime。该类与该数据类型一样,故意缺少任何时区概念或从UTC偏移。将此类型和类用于:

  • 当您指任何区域或所有区域时,例如“圣诞节从2018年12月25日的第一时刻开始”。这可以转化为不同地方的不同时刻,因为东方早些时候比西方早起。
  • 在安排约会或活动时,政治家可能会改变时区的偏差,世界各地的政治家都表现出了倾向。在此用法中,您必须在运行时应用时区来动态计算(但不存储)在日历上显示的时刻。这样,即使政客们将时钟重新定义为提前几小时/小时,在8个月内15:00的牙科预约仍然是15:00。

对于MySQL TIMESTAMP,请使用Java类Instant,如上所示。将此类型和类用于时间轴上的特定点。

JDBC 4.2

从JDBC 4.2及更高版本开始,我们可以直接与数据库交换 java.time 对象。使用getObject& setObject方法。

myPreparedStatement.setObject( … , Instant.now() ) ;

检索。

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

然后,您可以将Instant中的UTC值调整为特定时区,以便呈现给用户。

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

关于 java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和& SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

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

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore

答案 1 :(得分:1)

你的问题是关于我认为现在这个问题很大的问题。 DB(通过SQL)和服务器端本身(通过Java等编程语言)提供了处理日期和时间的方法概要。我认为现状是高度非标准化和有点混乱(个人意见:)

我的回答是不完整的,但我会解释原因。

你是对的,Java的日期(和日历)存储时间是自Unix Epoch以来的毫秒(这很棒)。它不仅发生在Java中,也发生在其他编程语言中。在我看来,完美的计时架构自然而然地出现了:Unix时代是1970年1月1日,午夜,UTC。因此,如果您选择将时间存储为自Unix Epoch以来的毫秒数,那么您将获得很多好处:

  • 架构清晰度:服务器端使用UTC,客户端显示通过其本地时区的时间
  • 数据库简单性:存储数字(毫秒)而非复杂数据结构,如DateTimes
  • 编程效率:在大多数编程语言中,您拥有的日期/时间对象在构建时可以花费几毫秒(如您所说,允许自动转换为客户端时区)

我发现使用这种方法时代码和架构更简单,更灵活。我不再尝试理解DateTime(或Timestamp)之类的东西,只在我必须修复遗留代码时处理它们。