We use fixed time periods in the application. When the user adds a new period, it should be by default from 6:00 AM to 6:00 AM the next day.
Normally, it's 24 hours, but there is one issue : when the daylight saving change is performed, the length of that period changes. For example :
27 October 6:00 AM to 28 October 6:00 AM. In this period is performed change shift from CEST to CET time zone. Thus, this period contains 25 hours :
From 27 October 6:00 AM to 28 October 3:00 AM - there are 21 hours
at 3:00 am the time is shifted back by 1 hour, so there are 4 hours until 28 October 6:00 AM.
We encountered this issue and tried to write a unit test to prevent it from appearing again. The test passes successfully on our machines, but fails on the CI server ( it's in another time zone ).
The question is : how can we possible design our unit test independently from the machine's time zone ?
Currently, the calculation of hours span is calculated using Joda-Time:
if ((aStartDate == null) || (aEndDate == null)) {
return 0;
}
final DateTime startDate = new DateTime(aStartDate);
final DateTime endDate = new DateTime(aEndDate);
return Hours.hoursBetween(startDate, endDate).getHours();
Unit test that passes on our side but fails on CI server :
Calendar calendar = Calendar.getInstance();
calendar.set(2012, Calendar.OCTOBER, 27, 6, 0);
startDate= calendar.getTime();
calendar.set(2012, Calendar.OCTOBER, 28, 6, 0);
endDate= calendar.getTime();
We tried to use time zones for calendar :
TimeZone.setDefault(TimeZone.getTimeZone(TimeZone.getTimeZone("GMT").getID()));
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(TimeZone.getTimeZone("GMT").getID()));
calendar.set(2012, Calendar.OCTOBER, 27, 6, 0, 0);
startDate= calendar.getTime();
calendar.set(2012, Calendar.OCTOBER, 28, 6, 0, 0);
endDate= calendar.getTime();
but in this case the result startDate is 27 October 9:00 CEST and endDate is 28 October 8:00 CET, so the test fails even on our side.
Thank you in advance.
how can we possible design our unit test independently from the machine's time zone ?
Always specify a time zone.
Nearly always, your code should specify a time zone. Omit the time zone only if you truly want the user’s/server’s default time zone; even then, you should explicitly access the default time zone to make your code self-documenting.
If you omit the time zone, the JVM's default time zone will be used.
Joda-Time has various places where you either pass a DateTimeZone
object or you call the withZone
method.
Avoid using time zone codes such as CEST
and CET
. They are neither standardized nor unique. And they are often used by mistake when confusing/ignoring Daylight Saving Time (DST).
Instead, use proper time zone names. Most names are a combination of continent and major city of a time zone area, written in plain ASCII characters (without diacriticals). For example, Europe/Chisinau
.
In Joda-Time, that would be…
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Chisinau" );
You real problem is thinking in terms of local date-time values, or in trying to perform business logic with local times. Doing so is terribly difficult because of Daylight Saving Time in general and its frequent changes, along with other anomalies.
Instead, think in terms of the time-line of the Universe rather than wall-clock time. Store and process date-time values as UTC (GMT) values whenever possible. Translate to local zoned times for presentation to the user (as you would localize strings) and where needed for business purposes such as determining the beginning of a "day" as defined by a specific time zone.
So, if you really want 24 hours later, then add 24 hours and let the wall-clock time fall where it may. If you want "next day", meaning the user expects to see the same time on the clock, then add 1 day (which might turn out to be 25 hours later). Joda-Time supports both kinds of logic.
Notice is the example code below that Joda-Time supports either counting a day by 24 hours or by adjusting the time to match the same wall-clock time because of Daylight Saving Time or other anomaly.
Here is some example code using Joda-Time 2.3. The new java.time package in Java 8 should have similar capability as it was inspired by Joda-Time.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Chisinau" );
DateTime start = new DateTime( 2012, 10, 27, 6, 0, 0, timeZone );
DateTime stop = start.plusHours( 24 ); // Time will be an hour off because of Daylight Saving Time (DST) anomaly.
DateTime nextDay = start.plusDays( 1 ); // A different behavior (25 hours).
Interval interval = new Interval( start, stop );
Period period = new Period( start, stop );
int hoursBetween = Hours.hoursBetween( start, stop ).getHours();
DateTime startUtc = start.withZone( DateTimeZone.UTC );
DateTime stopUtc = stop.withZone( DateTimeZone.UTC );
Dump to console…
System.out.println( "start: " + start );
System.out.println( "stop: " + stop );
System.out.println( "nextDay: " + nextDay );
System.out.println( "interval: " + interval );
System.out.println( "period: " + period );
System.out.println( "hoursBetween: " + hoursBetween );
System.out.println( "startUtc: " + startUtc );
System.out.println( "stopUtc: " + stopUtc );
When run…
start: 2012-10-27T06:00:00.000+03:00
stop: 2012-10-28T05:00:00.000+02:00
nextDay: 2012-10-28T06:00:00.000+02:00
interval: 2012-10-27T06:00:00.000+03:00/2012-10-28T05:00:00.000+02:00
period: PT24H
hoursBetween: 24
startUtc: 2012-10-27T03:00:00.000Z
stopUtc: 2012-10-28T03:00:00.000Z