【问题标题】:What is the Best Practice for manipulating and storing dates in Java? [duplicate]在 Java 中操作和存储日期的最佳实践是什么? [复制]
【发布时间】:2010-01-30 15:33:55
【问题描述】:

操作和存储日期的最佳做法是什么?在企业 Java 应用程序中使用 GregorianCalendar?

寻求反馈,我会将任何好的答案整合到其他人可以使用的最佳实践中。

【问题讨论】:

标签: java date


【解决方案1】:

最佳实践通常是考虑繁重的日期对象,而是存储一个时间点。这通常是通过存储一个不受极端情况或潜在解析问题影响的值来完成的。为此,人们通常存储自我们称为纪元 (1970-01-01) 的固定点以来经过的毫秒数(或秒数)。这很常见,任何 Java API 都将始终允许您将任何类型的日期转换为自纪元以来以毫秒表示的时间。

那是为了存储。例如,如果需要,您还可以存储用户的首选时区。

现在这样的日期以毫秒为单位,例如:

System.out.println( System.currentTimeMillis() );
1264875453

当它显示给最终用户时不是很有用,这是理所当然的。

这就是为什么您使用示例 Joda time 将其转换为某种用户友好的格式,然后再显示给最终用户的原因。

您要求最佳实践,这是我的看法:在数据库中存储“日期”对象而不是以毫秒为单位的时间,使用浮点数表示货币金额。

这通常是一种巨大的代码气味。

所以 Java 中的 Joda 时间是操纵日期的方式,是的。但是 Joda 是去 store 约会的方法吗? 肯定不会

【讨论】:

    【解决方案2】:

    Joda 是要走的路。为什么?

    1. 它具有比标准日期/时间 API 更强大、更直观的界面
    2. 日期/时间格式不存在线程问题。 java.text.SimpleDateFormat 不是线程安全的(不是很多人都知道这一点!)

    在某个阶段,Java 日期/时间 API 将被取代(由 JSR-310)。我相信这将基于 Joda 背后的工作人员所做的工作,因此您将学习影响新标准 Java API 的 API。

    【讨论】:

    • 现在,如果您不能使用 Joda(某些项目在可以引入项目的外部 JAR 方面受到限制),则使用 java.util.GregorianCalendar 来保存和操作日期(确保您帐户为 DST 做一些事情,比如在 UTC 时区维护你的日期)最佳实践?
    • 仅供参考,Joda-Time 项目现在处于维护模式,团队建议迁移到 java.time 类。
    • 真的吗?我不知道。谢谢提醒
    • 我认为在项目中添加繁重的 3rd-party 库无论如何都不能称为最佳实践
    • @ЕвгенийШевченко 原来的日期时间类真的那么糟糕,设计如此糟糕、令人困惑且有缺陷,是的,这确实是我们许多人的最佳实践将 Joda-Time jar 添加到任何新项目。幸运的是,Java 8 和 Java 9 中内置了 Joda-Time 的继承者 java.time 类。对于 Java 6 和 7,应该添加 ThreeTen-Backport 项目的库。在 ThreeTenABP 项目中,该反向移植进一步适用于早期的 Android。
    【解决方案3】:

    Joda time(与 JDK 100% 互操作)

    Joda-Time 提供了 Java 日期和时间类的优质替代品。该设计允许多个日历系统,同时仍提供简单的 API

    【讨论】:

    • 仅供参考,Joda-Time 项目现在处于维护模式,团队建议迁移到 java.time 类。
    【解决方案4】:

    UTC

    UTC 而非任何时区中思考、工作和存储数据。将 UTC 视为 一个真实时间,而所有其他时区只是变体。因此,在编码时,请忘记您自己的时区。以 UTC 执行您的业务逻辑、日志记录、数据存储和数据交换。我建议每个程序员在他们的办公桌上放第二个时钟,设置为 UTC。

    java.time

    现代方式是java.time 类。

    提到的Joda-Time 项目为 java.time 类提供了灵感,该项目现在处于维护模式,团队建议迁移到 java.time 类。

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

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

    从哪里获得 java.time 类?

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

    ISO 8601

    将日期时间值序列化为文本时,请使用ISO 8601 标准。

    例如,UTC 的日期时间是2016-10-17T01:24:35Z,其中ZZulu 的缩写,表示UTC。对于其他offset-from-UTC,小时和分钟的偏移量出现在末尾,例如2016-01-23T12:34:56+05:30。 java.time 类扩展了这种标准格式,以在方括号中附加时区名称(如果知道),例如2016-01-23T12:34:56+05:30[Asia/Kolkata]

    该标准还有许多其他方便的格式,包括durationsintervalsordinalsyear-week

    数据库

    对于数据库存储,对日期时间值使用日期时间类型,例如 SQL standard data types,主要是 DATETIMETIMESTAMP WITH TIME ZONE

    让您的JDBC driver 完成繁重的工作。驱动程序处理有关在 Java 如何处理数据的内部结构和数据库如何处理其一侧的数据之间进行调解和调整的基本细节。但请务必使用示例数据进行练习,以了解驱动程序和数据库的行为。 SQL 标准对日期时间处理的定义很少,因此行为差异很大,令人惊讶。

    如果使用符合 JDBC 4.2 及更高版本的 JDBC 驱动程序,您可以通过 ResultSet::getObjectPreparedStatement::setObject 方法直接获取和存储 java.time 类型。

    Instant instant = myResultSet.getObject( … );
    myPreparedStatement.setObject( … , instant );
    

    对于较旧的驱动程序,您需要退回到通过 java.sql 类型进行转换。寻找添加到旧类的新转换方法。例如,java.sql.Timestamp.toInstant()

    Instant instant = myResultSet.getTimestamp( … ).toInstant();
    myPreparedStatement.setObject( … , java.sql.Timestamp.from( instant ) );
    

    尽可能简短地使用 java.sql 类型。它们是一个设计糟糕的黑客,例如java.sql.Date 伪装成一个仅限日期的值,但实际上作为java.util.Date 的子类,它确实在UTC 中将时间设置为00:00:00。而且,哦,您应该忽略类文档中说的继承这一事实。一团糟。

    示例代码

    获取 UTC 中的当前时刻。

    Instant instant = Instant.now();
    

    上面显示了将 Instant 对象存储到/从数据库中获取。

    要生成 ISO 8601 字符串,只需调用 toString。默认情况下,java.time 类都使用 ISO 8601 格式来解析和生成各种日期时间值的字符串。

    String output = instant.toString();
    

    通过应用ZoneOffset 来调整到任何与UTC 的偏移量以获得OffsetDateTime。调用 toString 生成 ISO 8601 格式的字符串。

    ZoneOffset offset = ZoneOffset.ofHoursMinutes( 5 , 30 );
    OffsetDateTime odt = instant.atOffset( offset );
    

    时区是一个偏移量加上一组用于处理异常的规则,例如Daylight Saving Time (DST)。当您需要通过某个地区自己的wall-clock time 的镜头看到同一时刻时,应用时区 (ZoneId) 以获取 ZonedDateTime 对象。

    continent/region 的格式指定proper time zone name。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

    ZoneId z = ZoneId.of( "Asia/Kolkata" );
    ZonedDateTime zdt = instant.atZone( z );
    

    反过来,您可以通过调用toInstantOffsetDateTimeZonedDateTime 中提取Instant

    Instant instant = zdt.toInstant();
    

    格式化

    用于以非 ISO 8601 格式的字符串形式呈现给用户,search Stack Overflow 用于 DateTimeFormatter 类。

    虽然您可以指定自定义格式,但通常最好让 java.time 自动本地化。要进行本地化,请指定:

    • FormatStyle 确定字符串的长度或缩写。
    • Locale 确定 (a) 用于翻译日期名称、月份名称等的人类语言,以及 (b) 决定缩写、大写、标点符号等问题的文化规范。

    例子:

    Locale l = Locale.CANADA_FRENCH ; 
    DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( l );
    String output = zdt.format( f );
    

    转化

    尽可能避免使用旧的日期时间类型。但是,如果使用尚未针对 java.time 类型更新的旧代码,您可以转换为 java.time 类型/从 java.time 类型转换。详情见问题Convert java.util.Date to what “java.time” type?

    使用对象

    使用对象,而不仅仅是编码的原语和简单的字符串。例如:

    • 不要使用 1-7 来表示星期几,请使用 DayOfWeek 枚举,例如 DayOfWeek.TUESDAY
    • 不要将字符串作为日期传递,而是传递LocalDate 对象。
    • 与其传递一对整数作为年份和月份,不如传递YearMonth 对象。
    • 使用可读性更强的Month 枚举,而不是一个月的1-12,例如Month.JANUARY

    使用此类对象可以让您的代码更加自记录,确保有效值,并提供type-safety

    【讨论】:

      【解决方案5】:

      为了开始讨论,这是我的经验:

      在为典型的 3 层 Java Enterprise 项目创建标准时,我通常建议项目使用 GregorianCalendar 来处理日期。原因是 GregorianCalendar 是任何其他 Calendar 实例的事实上的标准,例如儒略历等。它是大多数国家/地区公认的日历,并且可以正确处理闰年等。最重要的是,我建议应用程序将其日期存储为 UTC,以便您可以轻松执行日期计算,例如查找两者之间的差异日期(例如,如果它存储为 EST,则必须考虑夏令时)。然后可以将日期本地化到您需要将其显示给用户的任何时区,例如,如果您是美国东海岸的一家公司,并且您希望在 EST 中显示您的时间信息,则将其本地化为 EST。

      【讨论】:

      • "e.g. Julian calendar etc" 你可以忽略它,因为它没有任何意义。有太多的极端案例开始在这里列出。例如,天文学家使用天文儒略日数。他们还使用公历。在这一点上最好少说。此外,它有助于为您的每个点使用项目符号。
      • 为什么 3 层项目与其他项目不同?
      • 仅供参考,java.util.Datejava.util.Calendarjava.text.SimpleTextFormat 等旧日期时间类现在是 legacy,被 java.time 类取代。
      猜你喜欢
      • 1970-01-01
      • 2012-10-29
      • 2017-02-25
      • 1970-01-01
      • 2011-06-08
      • 1970-01-01
      • 2013-11-11
      • 2011-12-03
      • 2022-01-18
      相关资源
      最近更新 更多