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!
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.
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
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.
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.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With