我已经阅读了所有其他关于日期操作的问答,但似乎没有一个能给我一个满意的答案。
我有一个具有不同地理位置用户的项目,该项目在其一些类和数据中使用Date
。事情是,我正在寻找一种有效的方法来操作日期为不同的用户在各自的时区,大多数的答案建议使用Joda库Date
操作,这很不理解,因为我仍然没有发现任何操作你不能做传统的Java,所以如果有人可以解释我可以用Joda做什么,不能用传统的Java,然后我可能会考虑使用它。
我最终采用了使用System.currentTimeMillis()
将我的日期保存到数据库(任何数据库)的方法。这将避免我担心使用数据库存储日期的时区。如果我想查询数据库中的特定日期或日期范围,我将使用我想查询的Date
的long
值执行查询:
SELECT * FROM table1 WHERE date1>=1476653369000
当检索ResultSet
时,我会使用请求数据的用户的时区将从数据库检索到的long
值格式化为可读的Date
。
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(resultSet.getLong(1));
cal.setTimeZone(TimeZone.getTimeZone("Asia/Calcutta"));
Date myDate = cal.getTime();
根据我所读到的一些观点,有些人强调存储System.currentTimeMillis()
绝对不是最佳实践,然而,由于某种原因,他们都没有说明为什么这是不可取的。我错过什么了吗?这是否会导致转换Long->Date
/Date->Long
的性能问题?在数据库中使用Long
而不是Date
时,是否有任何用例无法完成?有人能对此发表一个基本的解释吗?
另一方面,假设我一直使用Date
值在数据库中存储日期,是否有一种方法可以避免在处理数据库Date
时担心时区?
提前感谢。
我已经阅读了所有关于日期操作的其他问题
不,你肯定没有全部读过。
- 您应该已经了解到,无论是遗留的日期时间类(如
java.util.Date
&java.util.Calendar
)和Joda-Time项目被java。时间类(在'java.time'上搜索1,890个结果)。 - 您将学会不将日期时间值作为从epoch计数来跟踪。调试和日志记录变得非常困难,因为错误无法被发现,因为人类无法破译长整数作为日期-时间的含义。而且,由于在各种软件项目中使用了许多计数粒度(整秒、毫秒、微秒、纳秒、全天等等)和至少几十个epoch,因此会对数据产生歧义,并通过假设导致错误、误解和混淆。
- 您应该已经学会在数据库中使用日期时间类型来跟踪日期时间值。
- 你应该学会用UTC来工作和存储日期时间值。仅在逻辑需要或用户期望显示时调整为时区。"放眼全球,立足本地。">
- 您可能已经了解到,虽然这是一个勇敢的行业首创的努力,但遗留的日期时间类设计得很差,令人困惑,而且很麻烦。参见Java Date &API ?进行一些讨论。Joda-Time是业界第一个优秀的日期时间库,并启发了它的替代品java。
我将稍微简短一些,因为所有这些已经在Stack Overflow中讨论过很多次了。
在UTC工作。在Java中,这意味着Instant
类是常用的。Instant
类表示UTC时间轴上的时刻,分辨率为纳秒(最多九(9)位小数)。
Instant instant = Instant.now();
任何严肃的数据库,如Postgres,都使用UTC跟踪日期时间值。JDBC驱动程序处理从数据库内部存储的数据转换为Java类型的细节。符合JDBC 4.2及更高版本的JDBC驱动程序可以直接处理java。通过PreparedStatement::setObject
&ResultSet::getObject
方法。
myPreparedStatement.setObject( … , instant );
对于不兼容的驱动程序,退回使用java。sql类型,如java.sql.Timestamp
与数据库通信,并转换为/从java。时间类型通过添加到旧类中的新方法进行。数据库如何处理日期时间值的内部细节可能与java的处理方式大不相同。时间。在大多数情况下,JDBC驱动程序向您隐藏了所有的基本细节。但是一个关键问题是解决方案,您应该在数据库中研究这个问题。java。时间类处理日期时间的分辨率最高可达纳秒,但您的数据库可能没有。例如,Postgres使用微秒级的分辨率。所以来回移动意味着数据丢失。您希望在java上使用截断方法。设置类的时间以匹配数据库。
myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) );
所以,没有涉及时区。所以没有"担心时区而处理数据库日期"。
当你想通过一个地区的挂钟时间的镜头看到同一时刻时,应用ZoneId
得到ZonedDateTime
。
ZoneId z = ZoneId.of( "Asia/Kolkata" );
ZonedDateTime zdt = instant.atZone( z );
当将分区日期时间返回到数据库时,提取一个Instant
。
Instant instant = zdt.toInstant();
请注意,对于任何给定的时刻,日期以及一天中的时间在全球不同的时区都是不同的。因此,如果一个确切的时刻很重要,比如合同到期的时候,注意不要使用仅限日期的值。要么为精确时刻使用日期时间值,要么仅在日期旁边存储预期的时区,以便稍后可以计算精确时刻。
LocalDate ld = LocalDate.of( 2016, 1 , 1 );
// Determine the first moment of 2016-01-01 as it happens in Kolkata.
ZonedDateTime zdt = ld.atStartOfDay( ZoneId.of( "Asia/Kolkata" ) );
Instant instant = zdt.toInstant(); // Adjust to UTC and store.
关于java.timejava。时间框架内置于Java 8及以后版本中。这些类取代了麻烦的旧的遗留日期时间类,如java.util.Date
,.Calendar
, &java.text.SimpleDateFormat
.
Joda-Time项目,现在处于维护模式,建议迁移到java.time.
要了解更多信息,请参阅Oracle教程。并搜索Stack Overflow以获得许多示例和解释。规格为JSR 310。
从哪里获取java。时间类?
- Java SE 8andSE 9后来
- 内置。
- 标准Java API的一部分,捆绑实现。
Java 9增加了一些小的特性和修复。
- 大部分java。时间功能向后移植到Java 6;7 inThreeTen-Backport。
- ThreeTenABP项目适应ThreeTen-Backport
- 参见如何使用…
ThreeTen-Extra项目扩展了java。有额外课程的时间。这个项目是将来可能添加到java.time的试验场。您可以在这里找到一些有用的类,如Interval
、YearWeek
、YearQuarter
等。
我能用Joda做什么传统Java做不到的事情
在一般情况下,这并不是关于你能用传统Java做什么或不能做什么。它更多的是关于库API如何工作,使您比传统Java更容易编写更好(更健壮和正确)的代码。
以至于在Java 8中,Joda API或多或少被逐字复制/采用,只是更改了包名并将其合并到Java 8 SE标准库中。
因此,如果你正在使用Java 8,你应该选择新的API,如果你没有,你应该考虑使用Joda至少会为你购买一个顺利的升级/移植到Java 8的路径,当你有能力。
几个例子:
- 日期和时间类型的一致API。 日期/时间对象是不可变的,操作返回代表更改值的类型的新实例。(如Java字符串)。这样可以更容易地推断日期/时间对象的重用。
- 通过设计避免了DST &与时区相关的值/操作与DST &与时区无关的。这使得编写一致和正确工作的代码变得容易得多,并且没有依赖于时区/地区/日期/时间的极端情况。
toString()
之类的默认设置,因此序列化/反序列化可以期望以最小的努力正确工作。- 文化/地区相关的角落情况和你还没有意识到的事情(例如,你知道传统的韩国日历吗?),当你在地区/日历系统之间转换日期时间时,这为你节省了很多麻烦。还有:丰富的格式化选项。
Instant
s表示"绝对"时间戳的概念在处理地理分布的系统时很有用(当系统默认时钟/时区&DST规则可能不同)或互操作,因为它使用UTC。
编辑添加:
根据我读到的一些观点,有些人强调存储
System.currentTimeMillis()
绝对不是最佳实践,然而,由于某种原因,他们都没有说明为什么不推荐。我错过什么了吗?
System.currentTimeMillis()
有一些缺点。最大的缺点是时钟的类型定义不清。它可以是一个单调的时钟,它可以是受夏令时和时区约束的时钟,也可以是一个UTC时间。它也不一定是一个精确的时钟,它实际上不能保证精确到毫秒。只要手边有什么东西就可以作为当前时间在当前时间的表象。
这意味着,如果您想使用多个服务器来处理传入的请求,例如,当您必须考虑在第二天在另一个服务器B
上运行的程序的上下文中处理服务器A
的System.currentTimeMillis()
的输出时,它会变得棘手。