tags:

views:

566

answers:

6

Is there a way, either in code or with JVM arguments, to override the current time, as presented via System.currentTimeMillis, other than manually changing the system clock on the host machine?

A little background:

We have a system that runs a number of accounting jobs that revolve much of their logic around the current date (ie 1st of the month, 1st of the year, etc)

Unfortunately, a lot of the legacy code calls functions such as new Date() or Calendar.getInstance(), both of which eventually call down to System.currentTimeMillis.

For testing purposes, right now, we are stuck with manually updating the system clock to manipulate what time and date the code thinks that the test is being run.

So my question is:

Is there a way to override what is returned by System.currentTimeMillis? For example, to tell the JVM to automatically add or subtract some offset before returning from that method?

Thanks in advance!

+2  A: 

There really isn't a way to do this directly in the VM, but you could all something to programmatically set the system time on the test machine. Most (all?) OS have command line commands to do this.

Jeremy Raymond
For example, on windows the `date` and `time` commands. On Linux the `date` command.
Jeremy Raymond
Why shouldn't it be possible to do this with AOP or instrumentalization (been there done that)?
jarnbjo
+7  A: 

I strongly recommend that instead of messing with the system clock, you bite the bullet and refactor that legacy code to use a replaceable clock. Ideally that should be done with dependency injection, but even if you used a replaceable singleton you would gain testability.

This could almost be automated with search and replace for the singleton version:

  • Replace Calendar.getInstance() with Clock.getInstance().getCalendarInstance().
  • Replace new Date() with Clock.getInstance().newDate()
  • Replace System.currentTimeMillis() with Clock.getInstance().currentTimeMillis()

(etc as required)

Once you've taken that first step, you can replace the singleton with DI a bit at a time.

Jon Skeet
I can't believe you didn't show the joda-time version :(
Stephen
+1 messing with the system clock is going to cause all kinds of problems. You're also relying on nothing else (like ntpd) to sync the clock and reset it.
jcm
@Stephen: Joda Time doesn't expose a clock type as far as I can tell, unfortunately. Noda Time does :)
Jon Skeet
Thanks for the recommendation. I was figuring a refactor was going to be our best bet.
Mike Clark
jarnbjo
@jarnbjo: Well, you're welcome to your opinion of course - but this is a pretty well-established technique IMO, and I've certainly used it to great effect. Not only does it improve testability - it also makes your dependency on the system time explicit, which can be useful when getting an overview of the code.
Jon Skeet
+2  A: 

As said by Jon Skeet:

"use Joda Time" is almost always the best answer to any question involving "how do I achieve X with java.util.Date/Calendar?"

So here goes (presuming you've just replaced all your new Date() with new DateTime().toDate())

//Change to specific time
DateTimeUtils.setCurrentMillisFixed(millis);
//or set the clock to be a difference from system time
DateTimeUtils.setCurrentMillisOffset(millis);
//Reset to system time
DateTimeUtils.setCurrentMillisSystem();

If you want import a library that has an interface (see Jon's comment below), you could just use Prevayler's Clock, which will provide implementations as well as the standard interface. The full jar is only 96kB, so it shouldn't break the bank...

Stephen
No, I wouldn't recommend this - it doesn't move you towards a genuinely replaceable clock; it keeps you using statics throughout. Using Joda Time is of course a good idea, but I'd still want to go with a Clock interface or similar.
Jon Skeet
@Jon: True - for testing, it's ok, but it's better to have an interface.
Stephen
+3  A: 

Use Aspect-Oriented Programming (AOP, for example AspectJ) to weave the System class to return a predefined value which you could set within your test cases.

Or weave the application classes to redirect the call to System.currentTimeMillis() or to new Date() to another utility class of your own.

Weaving system classes (java.lang.*) is however a little bit more trickier and you might need to perform offline weaving for rt.jar and use a separate JDK/rt.jar for your tests.

It's called Binary weaving and there are also special tools to perform weaving of System classes and circumvent some problems with that (e.g. bootstrapping the VM may not work)

mhaller
Interesting idea +1
Willi
A: 

Without doing re-factoring, can you test run in an OS virtual machine instance?

Xepoch
A: 

Powermock works great. Just used it to mock System.currentTimeMillis().

ReneS