Java和MySQL中的时间戳和时区转换
我正在与位于与我的不同时区的服务器上开发一个带有
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准备好的陈述,并且非常困惑:
在此之前,我认为时间戳总是按UTC惯例.为什么人们想要一个本地化的时间戳而不是它的本地化表示?这对每个人来说都不会让人感到困惑吗? 这些转换如何运作?如果Java采用UTC时间戳并将其转换为任意时区,那么它如何告诉MySQL它在哪个时区呢? MySQL不会假设此时间戳是UTC,然后检索不正确的本地化值吗? 解决方法
日期时间处理是一个混乱
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.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. 使用Joda-Time和JDBC与Postgres的示例.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
确实. 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 documentation,DATETIME和TIMESTAMP这两种类型的不同之处在于第一种类型缺少任何时区概念或从UTC偏移.第二个使用时区的任何指示或伴随输入的UTC偏移指示将该值调整为UTC,然后存储它,并丢弃区域/偏移信息. 所以这两种类型似乎类似于标准的SQL类型: > MySQLDATETIME≈SQL标准TIMESTAMP没有时区 对于MySQL DATETIME,使用Java类 >当您指任何区域或所有区域时,例如“圣诞节在2018年12月25日的第一时刻开始”.这可以转化为不同地方的不同时刻,因为东方早些时候比西方早起. 对于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日期时间课程,如 Joda-Time项目现在是maintenance mode,建议迁移到java.time课程. 要了解更多信息,请参阅Oracle Tutorial.并搜索Stack Overflow以获取许多示例和说明.规格是JSR 310. 您可以直接与数据库交换java.time对象.使用符合JDBC 4.2或更高版本的JDBC driver.不需要字符串,不需要java.sql.*类. 从哪里获取java.time类? > Java SE 8,Java SE 9,Java SE 10及更高版本 >内置. > Java SE 6和Java SE 7 >大部分java.time功能都被反向移植到Java 6& 07年7月7日. > Android >更新版本的Android捆绑java.time类的实现. ThreeTen-Extra项目使用其他类扩展了java.time.该项目是未来可能添加到java.time的试验场.您可以在这里找到一些有用的类,例如 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |