Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing correct timezone handling

Tags:

java

timezone

utc

We are working with large amounts of data, all tagged in UTC (in Java). Between reading this data, storing it in a database and getting it out again, it happened, that some data was off one hour during daylight saving time. As UTC has no concept of daylight saving time this was clearly a bug within the software. Once known, it's easy to fix.

However, it'd be nice to have some unit/integration tests that work regardless of the current time difference - e.g. I'd like to change the local time zone and run some methods over and over again within these different time zones to make sure UTC is handled correctly.

As the tests should run automatically and - preferably - within one Testsuite, I'm wondering how to best test for correct behaviour. It'd be easy to change local settings like time zone upon restarting the JVM, but running this within a test suite is not that easy.

Does anybody know of a test environment, library or pattern supporting this scenario? We're usually working with JUnit, but are open for adding another environment/technique if it helps getting rid of problems like this. I suppose that it's rather an integration- than unit test.

Edit: There are already two very helpful answers, but I guess there must be more techniques out there. Does anybody have authoritative information about when/how often TimeZone.getDefault will be called (see the comments for Jon Skeets answers)?

Note: Even though this question has an accepted answer, I was not completely sure, which answer to accept. Even with this acceptance I'd like to see more ideas and techniques.

Thanks for your input!

like image 297
Olaf Kock Avatar asked Jan 25 '09 17:01

Olaf Kock


3 Answers

Java allows you to set the default timezone (java.util.TimeZone.setDefault). I've written tests before to set the timezone to a variety of different options and check that everything still works. Be careful though - if you're parallelising most of your unit tests, you'll need to make these ones sequential.

I suggest you test in some timezones with daylight saving time applies, and some without. Using an Australian timezone is good as well, as DST applies at the opposite time of the year to the northern hemisphere.

like image 119
Jon Skeet Avatar answered Oct 21 '22 08:10

Jon Skeet


I would recommend you check out JodaTime which provides some sugar to help manage Date / Time / TimeZone type issues more legibly in your code.

We use these throughout test and production since how it boosts the native Java API for Date/Time issues is unparalleled. Using these in tests works fine within JUnit

like image 43
j pimmel Avatar answered Oct 21 '22 10:10

j pimmel


Your one-hour-off problem is indeed likely due to time zone adjustment being applied. I am guessing you were (a) exchanging strings rather than objects with the database, (b) using the terrible legacy date-time classes, or (c) are using tools or middleware that are lying to you about the values retrieved from the database because of the well-intentioned by confusing anti-feature of applying a default time zone after retrieval from the database and before displaying/reporting to you.

java.time

The solution is to always use the modern java.time classes, and use a JDBC driver compliant with JDBC 4.2 or later.

Capture the current moment in UTC.

Instant instant = Instant.now() ;  

Your JDBC driver may or may not support Instant. The JDBC 4.2 spec inexplicably requires support for OffsetDateTime but not the two more commonly used types Instant & ZonedDateTime. No problem, as we can easily convert.

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

This OffsetDateTime is still in UTC (an offset of zero hours-minutes-seconds) because we specified the ZoneOffset.UTC constant.

Save to database.

myPreparedStatement.setObject( … , odt ) ;

Retrieval.

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

Adjust to a time zone for presentation to the user.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant​( z ) ;

Generate output for user-interface, automatically localized.

Locale locale = Locale.JAPAN ;  // Specify a `Locale` to determine 
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG ).withLocale( locale ) ;
String output = zdt.format( f ) ;

Notice that in none of this code did we use a String as a date-time value. If you exchange these smart objects rather than dumb strings with your database, you will have no injection of time zone adjustments.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes. Hibernate 5 & JPA 2.2 support java.time.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
like image 26
Basil Bourque Avatar answered Oct 21 '22 08:10

Basil Bourque