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:
Calendar.getInstance()
with Clock.getInstance().getCalendarInstance()
.new Date()
with Clock.getInstance().newDate()
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.
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?
Yes.
Instant.now(
Clock.fixed(
Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
)
)
Clock
In java.timeWe have a new solution to the problem of a pluggable clock replacement to facilitate testing with faux date-time values. The java.time package in Java 8 includes an abstract class java.time.Clock
, with an explicit purpose:
to allow alternate clocks to be plugged in as and when required
You could plug in your own implementation of Clock
, though you likely can find one already made to meet your needs. For your convenience, java.time includes static methods to yield special implementations. These alternate implementations can be valuable during testing.
The various tick…
methods produce clocks that increment the current moment with a different cadence.
The default Clock
reports a time updated as frequently as milliseconds in Java 8 and in Java 9 as fine as nanoseconds (depending on your hardware). You can ask for the true current moment to be reported with a different granularity.
tickSeconds
- Increments in whole secondstickMinutes
- Increments in whole minutestick
- Increments by the passed Duration
argument.Some clocks can lie, producing a result different than that of the host OS’ hardware clock.
fixed
- Reports a single unchanging (non-incrementing) moment as the current moment.offset
- Reports the current moment but shifted by the passed Duration
argument.For example, lock in the first moment of the earliest Christmas this year. in other words, when Santa and his reindeer make their first stop. The earliest time zone nowadays seems to be Pacific/Kiritimati
at +14:00
.
LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );
Use that special fixed clock to always return the same moment. We get the first moment of Christmas day in Kiritimati, with UTC showing a wall-clock time of fourteen hours earlier, 10 AM on the prior date of the 24th of December.
Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );
instant.toString(): 2016-12-24T10:00:00Z
zdt.toString(): 2016-12-25T00:00+14:00[Pacific/Kiritimati]
See live code in IdeOne.com.
You can control which time zone is assigned by the Clock
implementation. This might be useful in some testing. But I do not recommend this in production code, where you should always specify explicitly the optional ZoneId
or ZoneOffset
arguments.
You can specify that UTC be the default zone.
ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );
You can specify any particular time zone. Specify a proper time zone name in the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter abbreviation such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
You can specify the JVM’s current default time zone should be the default for a particular Clock
object.
ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );
Run this code to compare. Note that they all report the same moment, the same point on the timeline. They differ only in wall-clock time; in other words, three ways to say the same thing, three ways to display the same moment.
System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );
America/Los_Angeles
was the JVM current default zone on the computer that ran this code.
zdtClockSystemUTC.toString(): 2016-12-31T20:52:39.688Z
zdtClockSystem.toString(): 2016-12-31T15:52:39.750-05:00[America/Montreal]
zdtClockSystemDefaultZone.toString(): 2016-12-31T12:52:39.762-08:00[America/Los_Angeles]
The Instant
class is always in UTC by definition. So these three zone-related Clock
usages have exactly the same effect.
Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );
instantClockSystemUTC.toString(): 2016-12-31T20:52:39.763Z
instantClockSystem.toString(): 2016-12-31T20:52:39.763Z
instantClockSystemDefaultZone.toString(): 2016-12-31T20:52:39.763Z
The implementation used by default for Instant.now
is the one returned by Clock.systemUTC()
. This is the implementation used when you do not specify a Clock
. See for yourself in pre-release Java 9 source code for Instant.now
.
public static Instant now() {
return Clock.systemUTC().instant();
}
The default Clock
for OffsetDateTime.now
and ZonedDateTime.now
is Clock.systemDefaultZone()
. See source code.
public static ZonedDateTime now() {
return now(Clock.systemDefaultZone());
}
The behavior of the default implementations changed between Java 8 and Java 9. In Java 8, the current moment is captured with a resolution only in milliseconds despite the classes’ ability to store a resolution of nanoseconds. Java 9 brings a new implementation able to capture the current moment with a resolution of nanoseconds – depending, of course, on the capability of your computer hardware clock.
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?
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...
While using some DateFactory pattern seems nice, it does not cover libraries you can't control - imagine Validation annotation @Past with implementation relying on System.currentTimeMillis (there is such).
That's why we use jmockit to mock the system time directly:
import mockit.Mock;
import mockit.MockClass;
...
@MockClass(realClass = System.class)
public static class SystemMock {
/**
* Fake current time millis returns value modified by required offset.
*
* @return fake "current" millis
*/
@Mock
public static long currentTimeMillis() {
return INIT_MILLIS + offset + millisSinceClassInit();
}
}
Mockit.setUpMock(SystemMock.class);
Because it's not possible to get to the original unmocked value of millis, we use nano timer instead - this is not related to wall clock, but relative time suffices here:
// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();
private static long millisSinceClassInit() {
return (System.nanoTime() - INIT_NANOS) / 1000000;
}
There is documented problem, that with HotSpot the time gets back to normal after a number of calls - here is the issue report: http://code.google.com/p/jmockit/issues/detail?id=43
To overcome this we have to turn on one specific HotSpot optimization - run JVM with this argument -XX:-Inline
.
While this may not be perfect for production, it is just fine for tests and it is absolutely transparent for application, especially when DataFactory doesn't make business sense and is introduced only because of tests. It would be nice to have built-in JVM option to run in different time, too bad it is not possible without hacks like this.
Complete story is in my blog post here: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/
Complete handy class SystemTimeShifter is provided in the post. Class can be used in your tests, or it can be used as the first main class before your real main class very easily in order to run your application (or even whole appserver) in a different time. Of course, this is intented for testing purposes mainly, not for production environment.
EDIT July 2014: JMockit changed a lot lately and you are bound to use JMockit 1.0 to use this correctly (IIRC). Definitely can't upgrade to newest version where interface is completly different. I was thinking about inlining just the necessary stuff, but as we don't need this thing in our new projects I'm not developing this thing at all.
Powermock works great. Just used it to mock System.currentTimeMillis()
.
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)
Here's what you need to do:
Add a new java.time.Clock
attribute to the tested class MyService
and make sure the new attribute will be initialized properly at default values with an instantiation block or a constructor:
import java.time.Clock;
import java.time.LocalDateTime;
public class MyService {
// (...)
private Clock clock;
public Clock getClock() { return clock; }
public void setClock(Clock newClock) { clock = newClock; }
public void initDefaultClock() {
setClock(
Clock.system(
Clock.systemDefaultZone().getZone()
// You can just as well use
// java.util.TimeZone.getDefault().toZoneId() instead
)
);
}
{
initDefaultClock(); // initialisation in an instantiation block, but
// it can be done in a constructor just as well
}
// (...)
}
Inject the new attribute clock
into the method which calls for a current date-time. For instance, in my case I had to perform a check of whether a date stored in dataase happened before LocalDateTime.now()
, which I remplaced with LocalDateTime.now(clock)
, like so:
import java.time.Clock;
import java.time.LocalDateTime;
public class MyService {
// (...)
protected void doExecute() {
LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
someOtherLogic();
}
}
// (...)
}
In the test class, create a mock clock object and inject it into the tested class's instance just before you call the tested method doExecute()
, then reset it back right afterwards, like so:
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;
public class MyServiceTest {
// (...)
private int year = 2017;
private int month = 2;
private int day = 3;
@Test
public void doExecuteTest() throws Exception {
// (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot
MyService myService = new MyService();
Clock mockClock =
Clock.fixed(
LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
);
myService.setClock(mockClock); // set it before calling the tested method
myService.doExecute(); // calling tested method
myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method
// (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
}
}
Check it in debug mode and you will see the date of 2017 Feb 3 has been correctly injected into myService
instance and used in the comparison instruction, and then has been properly reset to current date with initDefaultClock()
.
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