How to get correct number of hours between Joda da

2019-02-26 00:20发布

问题:

I want to get all the Daylight Saving Time (DST) hours between two dates.

This is my example code:

public static void main(String[] args) {

    Date startDate = new Date();
    Calendar startCalendar = Calendar.getInstance();
    startCalendar.setTime(startDate);
    startCalendar.set(2014, 2, 1, 0, 0, 0);
    startCalendar.set(Calendar.MILLISECOND, 0);

    Date endDate = new Date();
    Calendar endCalendar = Calendar.getInstance();
    endCalendar.setTime(endDate);
    endCalendar.set(2014, 2, 31, 0, 0, 0);
    endCalendar.set(Calendar.MILLISECOND, 0);

    DateTime startDateTime = new DateTime(startCalendar);
    DateTime endDateTime = new DateTime(endCalendar).plusDays(1);

    Hours hours = Hours.hoursBetween(startDateTime, endDateTime);

    // actual is 744
    System.out.println("Expected: 743, actual: " + hours.getHours());
}

Well I am obviously missing something but I can't spot my error.

回答1:

The main problem is that you are failing to specify a time zone.

When I run your code here in Seattle, I get 743 hours in the month of March 2014. Why? Because of my default time zone. Here on the west coast of the United States, Daylight Saving Time begins March 9, 2014, Sunday, at 02:00. See this page, Time change dates in 2014. So that day, the 9th, is effectively 23 hours long instead of 24.

But if someone in Iceland ran that exact same code, she would get 744. Why? Because the people of Iceland are too smart to bother with the nonsense of Daylight Saving Time.

Also, as a good habit, you should call the Joda-Time method withTimeAtStartOfDay() when trying to work with days. Formerly we used Joda-Time's midnight methods, but those are deprecated because some days in some calendars do not have a midnight.

Tip: Be aware of Joda-Time methods named with standard, which the doc explains means an assumption of 24-hour days. In other words, those methods ignore Daylight Saving Time shifts.

Here is some example code using Joda-Time 2.3 in Java 7.

// © 2013 Basil Bourque. This source code may be used freely forevery by anyone taking full responsibility for doing so.

// Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 7 and earlier.
// http://www.joda.org/joda-time/

// Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
// JSR 310 was inspired by Joda-Time but is not directly based on it.
// http://jcp.org/en/jsr/detail?id=310

// By default, Joda-Time produces strings in the standard ISO 8601 format.
// https://en.wikipedia.org/wiki/ISO_8601

// Time Zone list: http://joda-time.sourceforge.net/timezones.html
org.joda.time.DateTimeZone seattleTimeZone = org.joda.time.DateTimeZone.forID("America/Los_Angeles");
org.joda.time.DateTimeZone icelandTimeZone = org.joda.time.DateTimeZone.forID("Atlantic/Reykjavik");

// Switch between using 'seattleTimeZone' and 'icelandTimeZone' to see different results (23 vs 24).
org.joda.time.DateTime theNinth = new org.joda.time.DateTime( 2014, 3, 9, 0, 0, seattleTimeZone ) ; // Day when DST begins.
org.joda.time.DateTime theTenth = theNinth.plusDays( 1 ); // Day after DST begins.

// Using "hoursBetween()" method with a pair of DateTimes.
org.joda.time.Hours hoursObject = org.joda.time.Hours.hoursBetween( theNinth.withTimeAtStartOfDay(), theTenth.withTimeAtStartOfDay() );
int hoursInt = hoursObject.getHours();
System.out.println( "Expected 23 from hoursInt, got: " + hoursInt );

// Using an Interval.
org.joda.time.Interval interval = new Interval( theNinth.withTimeAtStartOfDay(), theTenth.withTimeAtStartOfDay() );
System.out.println( "Expected 23 from interval, got: " + org.joda.time.Hours.hoursIn(interval).getHours() );

// Using a Period with Standard days.
org.joda.time.Period period = new org.joda.time.Period( theNinth.withTimeAtStartOfDay(), theTenth.withTimeAtStartOfDay() );
org.joda.time.Hours standardHoursObject = period.toStandardHours();
System.out.println( "Expected 24 from standardHoursObject, got: " + standardHoursObject.getHours() );


回答2:

Welcome to the chaos that is date / time handling. Your problem is time zones. Specifically, whatever time zone you're in observes daylight saving time, and the switch (spring forward) occurs during your interval, which shortens it by an hour. See this code.

public class DateTimeTest {
    public static void main(String[] args) {
    DateTime startDateTime = new DateTime()
        .withYear(2014)
        .withMonthOfYear(3)
        .withDayOfMonth(1)
        .withHourOfDay(0)
        .withMinuteOfHour(0)
        .withSecondOfMinute(0)
        .withMillisOfSecond(0)
        .withZone(DateTimeZone.forID("US/Eastern"));

    DateTime endDateTime = new DateTime()
        .withYear(2014)
        .withMonthOfYear(3)
        .withDayOfMonth(31)
        .withHourOfDay(0)
        .withMinuteOfHour(0)
        .withSecondOfMinute(0)
        .withMillisOfSecond(0)
        .withZone(DateTimeZone.forID("US/Eastern"))
        .plusDays(1);

    System.out.println("Expected 744, got: " 
        + Hours.hoursBetween(startDateTime, endDateTime).getHours());  // 743

    DateTime startUtc = startDateTime.withZoneRetainFields(DateTimeZone.UTC);
    DateTime endUtc = endDateTime.withZoneRetainFields(DateTimeZone.UTC);

    System.out.println("Expected 744, got: " 
        + Hours.hoursBetween(startUtc, endUtc).getHours());  // 744
    }
}


回答3:

I can't figure this out either (but in Joda time?), but you could work-around. The difference in millisecond times divided by 3600000L should be the accurate number of hours. I tried and got 743.