关于java:Joda-Time DateMidnight的推荐用法

Recommended use for Joda-Time's DateMidnight

LocalDate#toDateMidnight的javDoc如下:

As from v1.5, you are recommended to avoid DateMidnight and use
toDateTimeAtStartOfDay() instead because of the exception detailed
below.

This method will throw an exception if the default time zone switches
to Daylight Savings Time at midnight and this LocalDate represents
that switchover date. The problem is that there is no such time as
midnight on the required date, and as such an exception is thrown.

午夜不存在于某些时区似乎是完全避免使用DateMidnight的理由(假设您的代码没有使用已知没有这种DST情况的固定时区,并且将来不需要使用不同时区)。

但是,DateMidnight并没有被否决,JavaDoc中也没有针对DateMidnight类本身的类似建议或警告。此外,DateMidnight构造函数乐于接受一个即时和时区,这样在给定的一天中就不存在午夜,而不是像LocalDate#toDateMidnight那样抛出IllegalArgumentException。产生的DateMidnight表现得像一个DateTime,时间在一天的开始。

当某一天午夜不存在时,为什么LocalDate#toDateMidnight抛出异常而DateMidnight构造函数不存在?对于DateMidnight如果有的话,推荐的用例是什么?


没有充分的理由使用DateMidnightLocalDate是更好的选择。这是因为午夜不会在某个时区每年发生一次,会完全破坏类的可用性,并在应用程序中创建错误。

构造函数被修复以避免最坏的问题,但是看到内部毫秒值指向01:00的DateMidnight对象并不是很好。


建议使用new datetime().withTimeAtStartOfDay()。


或者最好直接使用LocalDate方法toDateTimeAtStartOfDay绕过DateTime对象的创建(与上述答案有关)。

1
new LocalDate().toDateTimeAtStartOfDay( myDateTimeZone )

DR

使用java.time类,特别是LocalDate::atStartOfDay,而不是"午夜"这一狡猾的概念。

1
2
ZoneId z = ZoneId.of("America/Montreal" );  // A time zone.
ZonedDateTime todayStart = LocalDate.now( z ).atStartOfDay( z );  // Produces a LocalDate (a whole day without time zone), then transforms into a `ZonedDateTime` (a moment on the timeline)

java.time时间

由于joda时间项目现在处于维护模式,并且团队建议迁移到java.time类,所以我将使用java.time添加示例。

如果您希望将整个一天表示为一个整体,请使用LocalDate类。LocalDate类表示没有时间和时区的仅日期值。

时区对于确定日期至关重要。在任何一个特定的时刻,日期在全球各地各不相同。例如,法国巴黎午夜几分钟后是一个新的一天,而蒙特勒魁北克仍然是"昨天"。

1
2
ZoneId z = ZoneId.of("America/Montreal" );
LocalDate today = LocalDate.now( z );

正如本页所讨论的,试图确定一天的结束是一种糟糕的做法。首先,你有一个问题,在一天的最后一秒,一个不可分割的分数。您是否解析为毫秒、微秒、纳秒或其他一些东西,因为所有这些都是常用的?而是利用新的一天的第一个时刻。

让java.time确定一天中第一个时刻的挂钟时间。不要假设时间是00:00:00,因为异常情况(如夏令时(dst))可能意味着第一个时刻是01:00:00等时间。这种DST调整目前在多个国家的时区使用。

所以,要想知道时间轴上的一个实际点,在一天开始的时候,请打电话给LocalDate::atStartOfDay。注意,这是一个比Joda Time的withTimeAtStartOfDay方法中使用的方法名的较短版本。指定ZoneId中生成ZonedDateTime对象所需/预期的时区。

1
2
ZoneId z = ZoneId.of("America/Montreal" );
ZonedDateTime zdt = today.atStartOfDay( z );

半开

那么如何表示一段时间呢?如果我想确定这一天的开始和结束,我该如何做,同时也遵循这个建议?日期时间工作中常用的解决方案是半开放式方法。在这种方法中,跨度的开始是包含的,而结束是独占的。所以"今天"是指从一天中的第一个时刻开始,一直跑到第二天的第一个时刻,但不包括第二天的第一个时刻。

1
2
ZonedDateTime zdtStartToday = LocalDate.now( z ).atStartOfDay( z );
ZonedDateTime zdtStartTomorrow = zdtStartToday.plusDays( 1 );

顺便说一下,这个额外的三个项目在这样的时间跨度内有一个方便的Interval类。

1
2
3
4
Interval todayInterval = Interval.of(
        zdtStartToday.toInstant() ,
        zdtStartTomorrow.toInstant()
    )

关于java.time

JavaTimeFr框架是在Java 8和之后构建的。这些类取代了麻烦的旧遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

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

要了解更多信息,请参阅Oracle教程。以及搜索堆栈溢出以获得许多示例和解释。规格为JSR 310。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。不需要字符串,不需要java.sql.*类。

在哪里获取java.time类?

  • JavaSE 8、Java SE 9、Java SE 10、Java SE 11,以及后来的标准JAVA API的一部分,实现了捆绑式实现。
    • Java 9增加了一些次要的特性和修复。
  • Java SE 6和Java SE 7
    • 大多数JavaTimeActudio的功能都被移植到TealEnter后端的Java 6和7中。
  • 安卓
    • java.time类的Android包实现的更高版本。
    • 对于早期的Android(<26),threetenabp项目适应threeten backport(如上所述)。看看如何使用三连珠……

threeten额外项目使用额外的类扩展java.time。这个项目是将来可能添加到java.time的一个试验场。您可以在这里找到一些有用的类,如IntervalYearWeekYearQuarter等等。


这里是一个更简单的解决方案,它将检查日期时间是否在本地时间午夜发生。

1
2
3
private boolean isAtMidnight(org.joda.time.DateTime dateTime) {
   return dateTime.toLocalDateTime().getMillisOfDay() == 0;
}


看看我的代码中的异常

1
2
Illegal instant due to time zone offset transition (daylight savings time 'gap'): 2015-03-27T00:00:00.000 (Asia/Amman)
    org.joda.time.IllegalInstantException: Illegal instant due to time zone offset transition (daylight savings time 'gap'): 2015-03-27T00:00:00.000 (Asia/Amman)

现在我用

1
2
LocalDate currentDate=new LocalDate();
someMethodSetsTheDate(currentDate.toDateTimeAtStartOfDay().toDate());

而不是

1
someMethodSetsTheDate(new DateMidnight(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth()).toDate());

现在我的建议是使用.ToDateTimeAtStartOfDay()。避免类似的例外。

请随意编辑我的答案谢谢