Question

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.

Was it helpful?

Solution

As I understood your question, you want to set a time zone on the server to test the correct hours during daylight time switches instead of using the standard server time zone. To achieve that, the time zone needs to be able to use daylight saving offsets.

The time zone you used on the server in your example

TimeZone zone = TimeZone.getTimeZone("GMT");

does not use daylight time: zone.useDaylightTime() returns false.

Try using a specific time zone instead, e.g.

TimeZone zone = TimeZone.getTimeZone("Europe/Chisinau");

has daylight saving offsets.

You can also use time zones in Joda Time's DateTime classes and avoid using Java's Calendar and Date classes.

OTHER TIPS

Specify the Time Zone

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 3-4 Letter Time Zone Codes

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" );

Think/Use UTC

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.

24 Hours Versus 1 Day

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.

Example Code

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

Why not to use an ordinary java.util.Calendar?

    final Calendar startTime = new GregorianCalendar( 2012, Calendar.OCTOBER, 27, 2, 12, 0 );
    startTime.setTimeZone( TimeZone.getTimeZone( strTimeZone ) );
    System.out.println( startTime.getTimeInMillis() );
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top