适用于地理位置不同的用户的日期操作/存储的Java最佳实践

时间:2016-10-16 21:39:08

标签: java database date datetime

我已经阅读了关于日期操作的所有其他问答,但是他们似乎都没有给我一个满意的答案。

我有一个地理位置不同的用户项目,在其某些类和数据中使用Date。问题是我正在寻找一种有效的方法来操纵各自时区中不同用户的日期,大多数答案建议使用Joda库进行Date操作,这完全不明白但是因为我还没有找到任何传统Java无法执行的操作,所以如果有人能解释我用Joda无法用传统Java做什么,那么我可以考虑使用它。

我终于找到了使用System.currentTimeMillis()将日期保存到数据库(任何数据库)的方法。这样可以避免让我担心使用数据库存储日期的时区。如果我想查询数据库中的特定日期或日期范围,我会使用我要查询的long的{​​{1}}值来执行查询:

Date

在检索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时担心时区?

提前致谢。

2 个答案:

答案 0 :(得分:23)

  

我已阅读有关日期操作的所有其他问答

不,你确实没有全部阅读。

  • 您可能已经了解到遗留日期时间类(例如java.util.Date& java.util.Calendar)和Joda-Time项目都被java.time类取代(搜索结果为1,890条) on' java.time')。
  • 您可能已经学会了不将日期时间值作为从纪元开始的数量来跟踪。由于人类无法将长整数的含义解读为日期时间,因此调试和日志记录变得非常困难,因为未发现错误。并且因为在各种软件项目中使用了许多粒度的计数(整秒,毫秒,微秒,纳秒,整天等)和至少一个couple dozen of epochs,所以会产生模糊的数据,导致错误,错误解释,和困惑。
  • 您将学会在数据库中使用日期时间类型来跟踪日期时间值。
  • 您将学会以UTC格式工作和存储日期时间值。仅在逻辑需要或用户期望的情况下调整到时区进行演示。 “全球化思考,本地化。”
  • 你会了解到,虽然是勇敢的行业第一次努力,但遗留日期时间类设计糟糕,令人困惑且麻烦。有关讨论,请参阅What's wrong with Java Date & Time API?。 Joda-Time是业界第一个很好的日期时间库,它启发了它的替代,即Java 8及更高版本中内置的java.time类。

我有点简短,因为所有这些已经在Stack Overflow上已经涵盖很多次

次。

以UTC身​​份工作。在Java中,这意味着常用Instant类。 Instant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

Instant instant = Instant.now();

任何严肃的数据库(如Postgres)都会以UTC格式跟踪日期时间值。 JDBC驱动程序处理从数据库内部存储数据转换为Java类型的详细信息。符合JDBC 4.2及更高版本的JDBC驱动程序可以通过PreparedStatement::setObject&直接处理java.time类型。 ResultSet::getObject方法。

myPreparedStatement.setObject( … , instant );

对于不兼容的驱动程序,回退到使用诸如java.sql.Timestamp之类的java.sql类型与数据库通信,并通过添加到旧类的新方法转换为/从java.time类型转换。数据库如何处理日期时间值的内部细节可能与java.time的完全不同。在大多数情况下,JDBC驱动程序会隐藏您的所有细节。但是一个关键问题是解决方案,您应该在数据库中学习。 java.time类处理日期时间,分辨率最高为nanoseconds,但您的数据库可能不会。例如,Postgres使用microseconds的分辨率。因此,来回往往意味着数据丢失。您希望在java.time类上使用截断方法来匹配您的数据库。

myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) );

所以,没有涉及时区。所以没有“担心处理数据库日期时的时区”。

如果您希望通过某个地区wall-clock time的镜头看到同一时刻,请应用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.time

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

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

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

从哪里获取java.time类?

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

答案 1 :(得分:2)

  

我可以用Joda做些什么来完成传统的Java

在一般情况下,它并不是关于传统Java能做什么或不做什么的。它更多地是关于库API如何使您编写比传统Java更好(更健壮和更正确)的代码。

从Java 8开始,Joda API或多或少被复制/采用,只更改了包名称并将其合并到Java 8 SE标准库中。

因此,如果您使用的是Java 8,那么您应该选择新的API,如果没有,您应该考虑使用Joda至少可以为您提供一条平滑的升级/移植到Java 8的途径。

一些例子:

  • 日期和时间类型的一致API。
  • 日期/时间对象是不可变的,操作返回表示更改值的类型的新实例。 (像Java Strings一样)。这样可以更容易地推断重用日期/时间对象。
  • 通过设计避免混合DST& DST& amp;与时区相关的值/操作时区不可知论者。这使得编写一致且正确的代码变得容易得多,并且没有依赖于时区/区域/日期/时间的角落案例。
  • Sane默认使用toString()这样的内容,因此序列化/反序列化可以用最少的工作量正常工作。
  • 依赖于文化/语言环境的角落案例以及您尚未意识到的事情(例如,您是否了解传统的韩国日历?),这可以在您在语言环境之间转换日期时间时节省大量麻烦/日历系统。另外:丰富的格式化选项。
  • Instant代表'绝对'的概念使用地理分布式系统时(当系统默认时钟/时区和DST规则可能不同时)或互操作因为它使用UTC时有用的时间戳。

编辑添加:

  

根据我读过的一些观点,有些人强调说,存储System.currentTimeMillis()绝对不是最佳做法,但是,出于某种原因,他们都错过了为什么不推荐这样做。我错过了什么吗?

System.currentTimeMillis()有一些缺点。最大的缺点是时钟类型定义不明确。它可能是一个单调时钟,它可能是受DST和时区影响的时钟,也可能是UTC时间。它也不一定是精确的时钟,实际上并不能保证精确到毫秒级。无论发生什么事情都可以作为当前时间当前时间的外表,基本上。

这意味着,如果您想使用多个服务器来处理传入的请求,那么当您必须考虑在服务器System.currentTimeMillis()的上下文中使用A的输出时,它会变得棘手。第二天,你的程序在不同的服务器B上运行,比如说。