Time Travel in Java
Do you have a clock?
Time travel! I bet that many of us would like to experience it, but guess what? — you can do it in Java! All you need is a clock…
In this article I will show you how to:
- manage the time that your application assumes to be now
- test your code which uses static methods from
java.time.*
package
What is “now” ?
Date and time may seem like just easy concepts. When you start working with different time zones, you will see that it can be really tricky. I’ve listed just a few technical dilemmas below:
- you live in time zone A, and your production infrastructure is running in time zone B.
LocalDateTime.now()
returns different results on your local machine, and on production; - your database server runs in a different time zone than yours — your SQL was prepared and tested locally, but may apply a different offset on the actual server;
- your service and your database run in a different time zones — which side should apply the offset when saving the date and time? Your service or your database? What if neither (or both) apply the offset shift?
- you store everything in UTC — great! But what if someone wants to know what the client’s time was when some action was performed?
As you can see, it’s not so trivial neither in a complex systems, nor… locally! Let’s have a look at a common use case.
Now + something
Business requirements are often time-based — e.g. if you buy something, you can pay for it 30 days later. Your system must somehow calculate this due date — you would typically see the following solution:
The problem is that LocalDateTime.now()
method is static and not so easy to mock. So how to test it? This is already getting tricky, but it’s still possible.
If you don’t want to mock up static methods, and precision to seconds is “good enough” for you, you can come up with the following idea:
These are the logs produced by the test — as you can see, there is a difference in milliseconds. In this case, the seconds match, but if you run the test again, they may not.
actualDueDate 2022-10-03T23:46:50.307535
expectedDueDate 2022-10-03T23:46:50.306746
Problems
- testing and assertions against the results of static methods— as you can see, this is more complex, because you are not 100% sure what was the result obtained by static methods
- problems with time-dependent requirements — sometimes business requirements are really complex and strongly time-dependent, e.g. prices may be dynamic, something is cheaper in the evening on weekdays but only in the winter, etc. In such cases you want to compare date&time with as much precision as possible.
Ideas
Here are some solutions and “solutions” to both of these problems. First, let’s consider the ones that may work, but are not so relevant in my opinion:
Working, but not great
- pass “now” as an argument to a method — ouch, that hurts. Yes, it will make testing easier, but… code complexity increases because your method takes one additional argument. Why do method clients have to provide you with this date&time? What if some clients use this as an advantage and, for example, pass the year 2100 as “now” (as in the previous example with the date)?
- mocking up static methods — this can be done with some test libraries or AOP, but… in my opinion, if you need to mock a static method to test your business requirements, you’re doing something wrong. Staticity is great for utility methods, but any use of a static method is a hidden dependency.
Better, but not the best
- custom
TimeService
wrapping static method. You can inject this service into other services and mock it in tests;
Pros:
- visible dependence — you are aware of the time dependence
- easy to test and mock — in your tests, and you can just mock
TimeService
behaviour
Cons:
- additional class used (probably) throughout the application
- this class can grow quickly — maybe it’s not a big deal, but the number of wrapped methods can grow quickly if you use a lot of static methods from
java.time
package. Imagine that this class has 20 public methods - to me it sounds like a good old utility class, but tied with a nice ribbon. It’s also not too Solid. - it’s a new dependency for other classes
The (in my opinion) best
- using
java.time.Clock
class as a time provider - makes testing easier and allows time travel
As you can read in the javadoc:
The use of the clock is optional. All key date-time classes also have a now() factory method that uses the system clock in the default time zone. The primary purpose of this abstraction is to allow alternative clocks to be connected if needed. Applications use an object to get the current time instead of a static method. This can simplify testing.
and
Best practice for applications is to pass clock to any method that requires the current time and time zone.
Sounds like a great solution!
Pros:
Clock
class comes from JDK - no 3rd party library is needed- allows time travel — you can simulate that the application is running at any point in time
Cons:
- this is a new dependency for other classes (but is unlikely to be overcome)
Clock bean configuration
The bellow examples show how to configure Clock
bean using Spring configuration.
Local date&time
Using this configuration will return local date&time results.
Fixed clock
Using this configuration will return fixed results —calling the now() method will always return the same time.
UTC
Time travel
If you want to travel in time and configure what is now for your service, here’s how you can achieve it:
This configuration registers Clock
bean in the context of the application. If you do not set any of the time-travel.**
properties, your app will use the system’s default time zone. If you set the time-travel.**
properties, for example, as follows:
time-travel.enabled=true
time-travel.instant=2022-09-01T12:00:00.00Z
time-travel.zone=UTC
then now in your application will always be 2022–09–01T12:00:00.
Summary
In this article I described:
- what problems with date, time and time zones you may have
- why these great static methods from the
java.time
package are not the easiest to test - how to deal with time in the app and time travel
- what is the best (in my opinion) way to handle time in Java
Resources and further reading:
- https://www.baeldung.com/java-override-system-time
- https://www.baeldung.com/java-clock
- https://mincong.io/2020/05/24/java-clock/
- https://dev.to/rnowif/controlling-the-time-in-java-43kh
- https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time
- https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html
Words by Aleksander Kołata, Senior Full Stack Developer at Altimetrik Poland
https://www.linkedin.com/in/aleksander-kolata/
Copywriting by Kinga Kuśnierz, Content Writer at Altimetrik Poland