2

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
mrt181
  • 5,080
  • 8
  • 66
  • 86
  • using http://www.timeanddate.com/date/durationresult.html?d1=1&m1=3&y1=2014&d2=31&m2=3&y2=2014&h1=00&i1=00&s1=00&h2=00&i2=00&s2=00 and adding 1 day = 24 hours I get 744. I don't see the point – sarah.ferguson Oct 29 '13 at 12:43
  • 3
    743 is the right answer because March 9, 2014 (in many locations) is only 23 hours long. – Charles Forsythe Oct 29 '13 at 12:44

3 Answers3

5

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() );
Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • specifying the timezone explicitly and using `.withTimeAtStartOfDay()` yields the right result – mrt181 Oct 30 '13 at 13:21
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
    }
}
Ian McLaird
  • 5,507
  • 2
  • 22
  • 31
  • 1
    toDateMidnight is deprecated. Is there a replacement? – mrt181 Oct 29 '13 at 13:51
  • I'm just using it as a shortcut for setting the minute, second, and millisecond to 0 to keep the code short. I coded this against joda-time 1.6.2, which is pretty old now. I didn't realize they'd deprecated it. – Ian McLaird Oct 29 '13 at 14:50
  • I am also not able to reproduce your result. It returns always 744. I'm using Joda-Time-2.3 – mrt181 Oct 29 '13 at 14:52
  • Try it with the updates. The first pair now uses a time zone that observes DST, the second pair does not. More than likely, you've run this on a couple of different machines whose clock settings are different. Time zones will drive you nuts for things like this. It's usually best to control them properly. – Ian McLaird Oct 29 '13 at 14:58
  • I get this result for your code `Expected 744, got: 744 Expected 744, got: 745` – mrt181 Oct 29 '13 at 15:14
  • Using Joda-Time 2.3, I still get the same result as I posted. The only way I can see to get 745 is if you're explicitly adding 1 somewhere, or if you're not setting the hours to the correct 0 values. – Ian McLaird Oct 29 '13 at 16:44
  • I copy pasted your code which produced 744 and 745, startUtc is "2014-02-28T18:00:00.000Z" and endUtc is "2014-03-31T19:00:00.000Z". – mrt181 Oct 30 '13 at 06:56
  • JodaTime explicitly notes that both the OS, as well as the JVM, must be kept up-to-date with respect to timezones in order for it to work properly. Is this potentially the case with your system? **edit** I'm also getting 744/745 hours. I'm fairly certain my system is otherwise up-to-date. Quite possibly this is an artifact of the setup of the types. Investigating. – Clockwork-Muse Oct 30 '13 at 11:06
  • 1
    It's an artifact of how the types are being setup. Per `DateTime.withZone()`: "Returns a copy of this datetime with a different time zone, preserving the millisecond instant." .... In other words (as you've already noticed), when your current timezone is _not_ the 'destination' timezone, you get different 'local' times. This example either needs to use `DateTime.withZoneRetainFields()` (which will preserve the local time for the destination zone, but isn't safe in all instances), or set up in a more time-zone safe way (like specifying the timezone and local time in the constructor). – Clockwork-Muse Oct 30 '13 at 11:29
  • @mrt181 The replacement for `toDateMidnight` and the other midnight-related methods and classes is the DateTime method [`withTimeAtStartOfDay`](http://www.joda.org/joda-time/apidocs/org/joda/time/DateTime.html#withTimeAtStartOfDay()). – Basil Bourque Apr 12 '14 at 16:50
1

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.

Charles Forsythe
  • 1,831
  • 11
  • 12