我正在与位于与我的不同时区的服务器上开发一个带有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格式,然后检索错误的本地化值吗?
答案 0 :(得分:6)
answer by Teo中的第一段非常有见地和正确:Java中的日期时间处理是一团糟。同样适用于所有其他语言&我所知道的开发环境。日期时间工作既困难又棘手,尤其容易出错且令人沮丧,因为我们直观地认为是日期时间。但是直觉上"在涉及数据类型,数据库,序列化,本地化,跨时区调整以及计算机编程带来的所有其他手续时,它不会削减它。
不幸的是,计算机行业基本上选择忽略日期工作的这个问题。正如鉴于显而易见的需要,Unicode花了很长时间才被发明,因此行业也在解决日期时间处理问题方面取得了成功。
但我必须不同意其结论。使用count-since-epoch并不是最好的解决方案。使用count-since-epoch本质上是令人困惑且容易出错并且不兼容。
long
数字并将其解读为日期时间。因此,至少可以说,验证数据和调试变得复杂。我们创建数字数据类型来进行数学运算而不是使用位。我们创建字符串类来处理处理文本而不是裸八位字节的细节。我们也应该创建数据类型和类来处理日期时间值。
早期的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.Date
和java.sql.Time
类的hacks,这是一个伪装成仅限日期的日期时间值。 Joda-Time和java.time都通过提供LocalDate
和LocalTime
类来纠正它。
另一个具体问题是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惯例。为什么人们想要一个本地化的时间戳而不是它的本地化表示?对每个人来说都不会让人感到困惑吗?
事实上。 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(我是Postgres类型的人)。
根据version 8 documentation,DATETIME
和TIMESTAMP
这两种类型的不同之处在于,第一种类型缺少任何时区概念或从UTC偏移。第二个使用时区的任何指示或伴随输入的UTC偏移指示将该值调整为UTC,然后存储它,并丢弃区域/偏移信息。
所以这两种类型似乎类似于标准的SQL类型:
DATETIME
≈SQL-standard TIMESTAMP WITHOUT TIME ZONE
TIMESTAMP
≈SQL-standard TIMESTAMP WITH TIME ZONE
对于MySQL DATETIME
,请使用Java类LocalDateTime
。该类与该数据类型一样,故意缺少任何时区概念或从UTC偏移。将此类型和类用于:
对于MySQL TIMESTAMP
,请使用Java类Instant
,如上所示。将此类型和类用于时间轴上的特定点。
从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 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和& SimpleDateFormat
现在位于Joda-Time的maintenance 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的试验场。您可以在此处找到一些有用的课程,例如Interval
,YearWeek
,YearQuarter
和more。
答案 1 :(得分:1)
你的问题是关于我认为现在这个问题很大的问题。 DB(通过SQL)和服务器端本身(通过Java等编程语言)提供了处理日期和时间的方法概要。我认为现状是高度非标准化和有点混乱(个人意见:)
我的回答是不完整的,但我会解释原因。
你是对的,Java的日期(和日历)存储时间是自Unix Epoch以来的毫秒(这很棒)。它不仅发生在Java中,也发生在其他编程语言中。在我看来,完美的计时架构自然而然地出现了:Unix时代是1970年1月1日,午夜,UTC。因此,如果您选择将时间存储为自Unix Epoch以来的毫秒数,那么您将获得很多好处:
我发现使用这种方法时代码和架构更简单,更灵活。我不再尝试理解DateTime(或Timestamp)之类的东西,只在我必须修复遗留代码时处理它们。