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.
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() );
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
}
}
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.