【问题标题】:Java Best Practice for Date Manipulation/Storage for Geographically Diverse Users面向不同地域用户的日期操作/存储的 Java 最佳实践
【发布时间】:2017-02-25 19:28:06
【问题描述】:

我已阅读有关日期操纵的所有其他 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 database date datetime


    【解决方案1】:

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

    不,你确实没有读过它们。

    • 您会了解到传统的日期时间类(例如 java.util.Datejava.util.Calendar)和 Joda-Time 项目都被 java.time 类所取代(在 'java.time 上搜索的 1,890 个结果')。
    • 您将学会不将日期时间值作为从纪元开始的计数来跟踪。由于人类无法将长整数的含义解读为日期时间,因此调试和日志记录变得非常困难,因为无法发现错误。并且由于在各种软件项目中使用了许多计数粒度(整秒、毫秒、微秒、纳秒、整天等)和至少一个 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::setObjectResultSet::getObject 方法直接处理java.time 类型。

    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.Calendarjava.text.SimpleDateFormat

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

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

    从哪里获得 java.time 类?

    ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

    【讨论】:

      【解决方案2】:

      我可以用 Joda 做哪些传统 Java 做不到的事情

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

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

      因此,如果您使用的是 Java 8,您应该选择新的 API,如果不是,您应该考虑使用 Joda 至少可以让您在可能的情况下顺利升级/移植到 Java 8。

      几个例子:

      • 日期和时间类型的一致 API。
      • 日期/时间对象是不可变的,操作返回表示更改值的类型的新实例。 (如 Java 字符串)。这样可以更轻松地推断日期/时间对象的重用。
      • 通过设计避免将 DST 和时区相关值/操作与 DST 和时区无关值/操作混合。这样可以更轻松地编写始终如一且正确运行且不存在依赖于时区/语言环境/日期/时间的极端情况的代码。
      • toString() 之类的默认设置是合理的,因此可以期望序列化/反序列化能够以最小的努力正常工作。
      • 与文化/地区有关的极端情况和您还不知道的事情(例如,您知道传统的韩国日历吗?)这为您在地区/日历系统之间转换日期时间时省去了很多麻烦.此外:丰富的格式选项。
      • Instants 的概念表示“绝对”时间戳,这在使用地理分布式系统(当系统默认时钟/时区和 DST 规则可能不同时)或互操作时非常有用,因为它使用 UTC。李>

      编辑添加:

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

      System.currentTimeMillis() 有一些缺点。最大的缺点是时钟的类型定义不明确。它可以是单调时钟,可以是受 DST 和时区约束的时钟,也可以是 UTC 时间。它也不一定是准确的时钟,实际上并不能保证精确到毫秒。基本上,无论发生什么,都可以得到与当前时间相似的东西当前时间。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-08
        • 2021-08-05
        • 1970-01-01
        • 2012-10-29
        • 1970-01-01
        • 1970-01-01
        • 2010-11-10
        相关资源
        最近更新 更多