5

I'm designing a backtesting application that will replay previously recorded data and process the data. I want to configure the date and time value in my application which will not affect the operating system. I find that there is a JVM parameter for setting the timezone of the JVM but I could not find a similar thing for date and time. Is it possible to do that?

Regards

xyzt
  • 1,201
  • 4
  • 18
  • 44

2 Answers2

6

It is not. There is no way to make e.g. System.currentTimeMillis() (and therefore, Instant.now() and other such calls as well) change behaviour without changing the OS clock.

If you want 'testable' time, you can't call those methods. Instead, make a Clock object and use that.

Thus, this is what you have to do:

  1. Find all places in your code that are hardcoded to use OS clocks. System.currentTimeMillis(), new Date(), X.now() where X is any type from the java.time package (Instant, LocalDateTime, etcetera), and crucially any library that is doing that under the hood. Replace them with the appropriate clock based call instead. System.currentTimeMillis() becomes clock.millis(). Instant.now() becomes clock.instant(), and so on.

  2. During testing, make your own clock, or use Clock.fixed for a clock that always reports the exact same time. During production, clock has to be Clock.systemUTC().

This may require the use of dependency injection frameworks. Or, make one global clock for your entire app (a class with a static Clock field that all code reads from, and that your test code sets, which defaults to systemUTC).

As far as I know there is no way to configure a clock used by System.currentTimeMillis and friends.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • 3
    *"There is no way to make e.g. System.currentTimeMillis() change behaviour without changing the OS clock"* - Yes, [there is](https://stackoverflow.com/questions/64334569/how-can-i-set-jvm-timenot-os-time-in-my-java-application/64345817#64345817). – apangin Oct 14 '20 at 02:39
4

The usual way to change date/time for a particular process is to hook corresponding system functions with an LD_PRELOAD trick. There are two basic functions in Linux to get absolute time: gettimeofday and clock_gettime. Here is an example library that intercepts these two functions.

Unfortunately, the above trick works on Linux only. But here is another portable solution specifically for Java. It relies on JVM Tool Interface.

In OpenJDK all Java APIs for getting current date/time end up in calling either System.currentTimeMillis() or VM.getNanoTimeAdjustment(). The latter is the internal JDK method to provide high resolution Clock. So, in order to change date/time for a Java application, it's enough to tweak the implementation of those two methods.

The idea is the following.

  • JVM TI agent intercepts native method bindings by subscribing to NativeMethodBind event.
  • When NativeMethodBind is called for currentTimeMillis or getNanoTimeAdjustment, the agent remembers the address of the native function and replaces it with its own one.
  • Every time the hooked methods are called, the agent first delegates to the original function and then adds the specified offset to the returned value.

The complete code for such an agent is here.

How to compile:

g++ -O2 -fPIC -shared -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -olibfaketime.so faketime.cpp

How to run:

java -agentpath:/path/to/libfaketime.so=<newtime> MainClass

where newtime is either an absolute timestamp in milliseconds from Epoch, or (when begins with + or -) a relative offset in milliseconds from current time.

The only problem is that System.currentTimeMillis is a JVM intrinsic. This means, a JIT compiled method may skip calling JNI implementation (and thus skip our hook). To avoid this, we can simply disable the corresponding intrinsic with a few more JVM options:

-XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_currentTimeMillis -XX:CompileCommand=dontinline,java.lang.System::currentTimeMillis
apangin
  • 92,924
  • 10
  • 193
  • 247
  • 2
    This is, in a word, insane, and you should most absolutely not do any of this and instead use Clock, because, you know, that's why Clock exists. So you don't have to hack the VM (because that's what this is. A hack, which relies on internal behaviour for no particularly good reason). I can use the same technology to make javac compile C code and have java start off reciting some shakespeare first. Cool trick, no doubt. But a trick that's not warranted for this problem. – rzwitserloot Oct 14 '20 at 02:44
  • @rzwitserloot JVM TI is a standard supported API designed specifically for things like this. It automates the routine that you otherwise suggest to do manually. What is insane - is to replace all calls to date/time API in the application by hand, including the vast number of 3rd party libraries that you may not even have sources for. – apangin Oct 14 '20 at 09:18
  • You can answer any question no matter how crazy with 'well, write an agent and make it completely rewrite everything'. – rzwitserloot Oct 14 '20 at 11:27
  • @rzwitserloot _some_ find it crazy, _some_ are just purely in love with these kind of things. 1+ – Eugene Oct 15 '20 at 03:22