Time travel isn’t possible, but this is the next best thing.
Install delorean as a Java Agent and it will rewrite the bytecode in your JVM, replacing usages of System.currentTimeMillis with a special implementation that calculates the date factoring in an offset. Even classes like java.util.Date
are updated.
The offset can be set via a system property or via a REST API deployable via the delorean-service war file.
Once installed your JVM can travel through time.
The time the delorean will travel to can be set via system property as follows:
delorean.debug = false delorean.offset = 0 milliseconds
The offset can be expressed in the following ways:
-
1 hour
-
27 minutes
-
10 seconds
For convenience it is possible to specify a compound form, such as:
-
3 days and 2 hours
-
1 hour, 45 minutes
-
15 minutes, 23 seconds, and 10 milliseconds
Spaces are also optional between the number and the time unit, which can be nice when using the abbreviated forms:
-
1hr
-
27m
-
10s
-
3d and 2hr
-
1hr, 45min
-
15m, 23s, and 10ms
To specify an offset in the past, simply add ` ago` to the end as follows:
-
1 hour ago
-
27 minutes ago
-
10 seconds ago
The highest unit that can be expressed is a day, this a week would be specified as 7 days
and a year as 365 days
.
The webapp has the following endpoints:
GET http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/ POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/date POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/date/{date} POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/decrement POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/decrement/{delta} POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/increment POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/increment/{delta} POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/{offset}
All time calculation is done by specifying an offset from the real value of System.currentTimeMillis
. The fake time reported to the modified bytecode will be effectively System.currentTimeMillis() + offset
Each call to this endpoint flatly overwrites the previous offset. Thus, this call is idempotent. Calling it several times will result in the same offset.
The offset can be set explicitly in milliseconds as follows:
$ curl -s -X POST http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/-12345678000 | python -m json.tool { "status": { "currentTime": "2016-12-22 18:52:31", "currentTimeMillis": 1494807229356, "offset": "142 days, 21 hours, 21 minutes and 18 seconds ago", "offsetMillis": -12345678000 } }
The offset can be posted as text using a Time Expression as well:
$ curl -s -d "42 days ago" http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/ | python -m json.tool { "status": { "currentTime": "2017-04-02 17:15:21", "currentTimeMillis": 1494807321708, "offset": "42 days ago", "offsetMillis": -3628800000 } }
Note, usages of -s
and python
are simply for readability in output. Scripts would likely not use them and simply do:
$ curl -d "42 days, 12 hours and 5 seconds ago" http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/ {"status":{"currentTime":"2017-04-02 05:16:55","currentTimeMillis":1494807420559,"offset":"42 days, 12 hours and 5 seconds ago","offsetMillis":-3672005000}}
Resetting the offset is as simple as:
$ curl -d "0 ms" http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/ {"status":{"currentTime":"2017-05-14 17:24:54","currentTimeMillis":1494807894061,"offset":"0 nanoseconds","offsetMillis":0}}
It is possible to shave time from or add time to the current offset. If the current offset is 30 days
and you increment it by 1 day
, the new offset will be 31 days
.
Thus, this call is NOT idempotent. Calling it several times will modify the current offset and yield a different result. This is handy in scripts where there is a desire to loop forward or backwards N times. For example, to set the offset to one year ago and then increment forward to the present day one week at a time, you can:
curl -d "365 days ago" http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/ for n in {1..52}; do curl -d "7 days" http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/offset/increment done
The above would yeild output like the following (truncated):
{"status":{"currentTime":"2016-05-14 17:50:07","currentTimeMillis":1494809407961,"offset":"365 days ago","offsetMillis":-31536000000}} {"status":{"currentTime":"2016-05-21 17:50:07","currentTimeMillis":1494809407975,"offset":"358 days ago","offsetMillis":-30931200000}} {"status":{"currentTime":"2016-05-28 17:50:07","currentTimeMillis":1494809407989,"offset":"351 days ago","offsetMillis":-30326400000}} {"status":{"currentTime":"2016-06-04 17:50:08","currentTimeMillis":1494809408008,"offset":"344 days ago","offsetMillis":-29721600000}} {"status":{"currentTime":"2016-06-11 17:50:08","currentTimeMillis":1494809408023,"offset":"337 days ago","offsetMillis":-29116800000}} {"status":{"currentTime":"2016-06-18 17:50:08","currentTimeMillis":1494809408035,"offset":"330 days ago","offsetMillis":-28512000000}} {"status":{"currentTime":"2016-06-25 17:50:08","currentTimeMillis":1494809408048,"offset":"323 days ago","offsetMillis":-27907200000}} {"status":{"currentTime":"2016-07-02 17:50:08","currentTimeMillis":1494809408061,"offset":"316 days ago","offsetMillis":-27302400000}} {"status":{"currentTime":"2016-07-09 17:50:08","currentTimeMillis":1494809408078,"offset":"309 days ago","offsetMillis":-26697600000}} {"status":{"currentTime":"2016-07-16 17:50:08","currentTimeMillis":1494809408094,"offset":"302 days ago","offsetMillis":-26092800000}} {"status":{"currentTime":"2016-07-23 17:50:08","currentTimeMillis":1494809408109,"offset":"295 days ago","offsetMillis":-25488000000}} {"status":{"currentTime":"2016-07-30 17:50:08","currentTimeMillis":1494809408123,"offset":"288 days ago","offsetMillis":-24883200000}} {"status":{"currentTime":"2016-08-06 17:50:08","currentTimeMillis":1494809408137,"offset":"281 days ago","offsetMillis":-24278400000}} {"status":{"currentTime":"2016-08-13 17:50:08","currentTimeMillis":1494809408150,"offset":"274 days ago","offsetMillis":-23673600000}} ...
It is possible to set the offset such the that the System.currentTimeMillis appears to be at a specific date.
$ curl -d "1976-03-30" http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/date {"status":{"currentTime":"1976-03-30 00:00:00","currentTimeMillis":1494808635914,"offset":"15020 days, 16 hours, 37 minutes, 15 seconds and 914 milliseconds ago","offsetMillis":-1297787835914}}
Of course this is still an offset. Time is not frozen and will still march forward.
mingus:/tmp 05:42:13 $ curl -d "1976-03-30" http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean/date {"status":{"currentTime":"1976-03-30 00:00:00","currentTimeMillis":1494808938478,"offset":"15020 days, 16 hours, 42 minutes, 18 se... mingus:/tmp 05:42:18 $ for n in {1..5}; do curl http://localhost:8080/delorean-service-0.1-SNAPSHOT/api/delorean && sleep 2 && echo ""; done {"status":{"currentTime":"1976-03-30 00:00:02","currentTimeMillis":1494808941349,"offset":"15020 days, 16 hours, 42 minutes, 18 se... {"status":{"currentTime":"1976-03-30 00:00:04","currentTimeMillis":1494808943374,"offset":"15020 days, 16 hours, 42 minutes, 18 se... {"status":{"currentTime":"1976-03-30 00:00:06","currentTimeMillis":1494808945401,"offset":"15020 days, 16 hours, 42 minutes, 18 se... {"status":{"currentTime":"1976-03-30 00:00:08","currentTimeMillis":1494808947425,"offset":"15020 days, 16 hours, 42 minutes, 18 se... {"status":{"currentTime":"1976-03-30 00:00:10","currentTimeMillis":1494808949446,"offset":"15020 days, 16 hours, 42 minutes, 18 se...
The following date formats are supported
-
yyyy-MM-dd’T’HH:mm:ss.SSSZ
-
yyyy-MM-dd’T’HH:mm:ss
-
yyyy-MM-dd HH:mm:ss z
-
yyyy-MM-dd HH:mm z
-
yyyy-MM-dd z
-
yyyy-MM-dd HH:mm:ss
-
yyyy-MM-dd HH:mm
-
yyyy-MM-dd
For example:
-
2017-05-14T17:57:12.382-0700
-
2017-05-14T17:57:12
-
2017-05-14 17:57:12 PDT
-
2017-05-14 17:57 PDT
-
2017-05-14 PDT
-
2017-05-14 17:57:12
-
2017-05-14 17:57
-
2017-05-14
It should be noted that if the time zone is not present, you will get the server’s default local timezone. If you want UTC, you should specify UTC.
The JVM flag for adding the agent would look something like the following:
-javaagent:/Users/dblevins/.m2/repository/com/tomitribe/delorean-agent/1.0/delorean-agent-1.0.jar
For TomEE, this can be added via JAVA_OPTS
export JAVA_OPTS="-javaagent:/Users/dblevins/.m2/repository/com/tomitribe/delorean-agent/1.0/delorean-agent-1.0.jar $JAVA_OPTS"
From here the delorean-service-1.0.war
simply needs to be copied into the TomEE webapps directory.
cp /Users/dblevins/.m2/repository/com/tomitribe/delorean-service/1.0/delorean-service-1.0.war $CATALINA_HOME/webapps/