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

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

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

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

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

我会稍微简短一点,因为Stack Overflow已经多次覆盖所有这些内容。

以UTC工作。 在Java中,这意味着通常使用Instant类。 Instant类表示UTC时间轴上的一个时刻,分辨率为纳秒 (最多九(9)位小数)。

 Instant instant = Instant.now(); 

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

 myPreparedStatement.setObject( … , instant ); 

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

 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.time

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

现在处于维护模式的Joda-Time项目建议迁移到java.time。

要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。

从哪里获取java.time类?

  • Java SE 8SE 9及更高版本
    • 内置。
    • 带有捆绑实现的标准Java API的一部分。
    • Java 9增加了一些小function和修复。
  • Java SE 6SE 7
    • 许多java.timefunction都被反向移植到ThreeTen-Backport中的 Java 6和7。
  • Android的
    • ThreeTenABP项目特别适用于Android的ThreeTen-Backport (如上所述)。
    • 请参阅如何使用….

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

我怎么能用Joda做传统Java无法做到的事情

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

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

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

几个例子:

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

编辑添加:

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

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

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