29

What’s the best way to get monotonically increasing time in JavaScript? I’m hoping for something like Java’s System.nanoTime().

Date() obviously won’t work, as it’s affected by system time changes.

In other words, what I would like is for a <= b, always:

a = myIncreasingTime.getMilliseconds();
...
// some time later, maybe seconds, maybe days
b = myIncreasingTime.getMilliseconds();

At best, even when using the UTC functions in Date(), it will return what it believes is the correct time, but if someone sets the time backward, the next call to Date() can return a lesser value. System.nanoTime() does not suffer from this limitation (at least not until the system is rebooted).

Modification: [2012-02-26: not intended to affect the original question, which has a bounty]

I am not interested knowing the “wall time”, I’m interested in knowing elapsed time with some accuracy, which Date() cannot possibly provide.

danorton
  • 11,804
  • 7
  • 44
  • 52
  • What do you mean by session? Where do you run this code (browser, server)? – gblazex Feb 25 '12 at 10:39
  • [W3Schools](http://www.w3schools.com/js/js_timing.asp) show something similar. – undefined Feb 24 '12 at 15:49
  • Sorry, I had worded that poorly. Hopefully the change I just added is clearer. – danorton Feb 26 '12 at 01:17
  • 1
    @xyu: w3schools is not really a good site - http://w3fools.com - the fact that they pass a string to `setTimeout` clearly proves that. – ThiefMaster Feb 26 '12 at 14:08
  • @ThiefMaster Yes I know. But I meant the idea of increasing a variable using `setTimeout`. – undefined Feb 26 '12 at 14:12
  • I have added the requirement of accuracy to my original query. As there was a bounty introduced by someone else, I note that my requirement is not intended to affect the bounty. – danorton Feb 26 '12 at 18:40

4 Answers4

25

You could use window.performance.now() - since Firefox 15, and window.performance.webkitNow() - Chrome 20]

var a = window.performance.now();
//...
var delay = window.performance.now() - a;
fernandohur
  • 7,014
  • 11
  • 48
  • 86
4esn0k
  • 9,789
  • 7
  • 33
  • 40
  • This function is also available in IE10. – danorton Nov 07 '14 at 22:16
  • Web worker uses different time origin. https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin – jiafeng fu May 22 '20 at 06:16
  • 1
    Bit of warning, this doesn't actually appear to be monotonic in the sense that I was hoping for (the POSIX monotonic clock). Specifically, it does not account for time spent in sleep mode. – Adrian Günter Nov 30 '22 at 03:19
  • It should account for time spent in sleep mode, but there are bugs in browsers. See [this note](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#ticking_during_sleep). – Marv-CZ Mar 16 '23 at 10:51
7

You could wrap Date() or Date.now() so as to force it to be monotonic (but inaccurate). Sketch, untested:

var offset = 0;
var seen = 0;
function time() {
  var t = Date.now();
  if (t < seen) {
    offset += (seen - t);
  }
  seen = t;
  return t + offset;
}

If the system clock is set back at a given moment, then it will appear that no time has passed (and an elapsed time containing that interval will be incorrect), but you will at least not have negative deltas. If there are no set-backs then this returns the same value as Date.now().

This might be a suitable solution if you're writing a game simulation loop, for example, where time() is called extremely frequently — the maximum error is the number of set-backs times the interval between calls. If your application doesn't naturally do that, you could explicitly call it on a setInterval, say (assuming that isn't hosed by the system clock), to keep your accuracy at the cost of some CPU time.


It is also possible that the clock will be set forward, which does not prevent monotonicity but might have equally undesirable effects (e.g. a game spending too long trying to catch up its simulation at once). However, this is not especially distinguishable from the machine having been asleep for some time. If such a protection is desired, it just means changing the condition next to the existing one, with a constant threshold for acceptable progress:

if (t > seen + leapForwardMaximum) {
  offset += (seen - t) + leapForwardMaximum;
}

I would suggest that leapForwardMaximum should be set to more than 1000 ms because, for example, Chrome (if I recall correctly) throttles timers in background tabs to fire not more than once per second.

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
  • Yes, good point and answered as I originally asked. I have modified my original question to add the accuracy requirement. – danorton Feb 26 '12 at 18:38
  • 1
    A kind-of obvious comment that isn't an issue for the original question in the way it is asked, but it might be important for some none-the-less: This solution does not handle the case where the user sets the clock forward, i.e. it does not prevent large positive jumps in the returned time. – Markus A. Nov 17 '15 at 22:07
  • @MarkusA. It seemed worth discussing, so I added a section on that. – Kevin Reid Nov 17 '15 at 22:20
4

Javascript itself does not have any functionality to access the nanoTime. You might load a java-applet to aqcuire that information, like benchmark.js has done. Maybe @mathias can shed some light on what they did there…

rodneyrehm
  • 13,442
  • 1
  • 40
  • 56
1

Firefox provides "delay" argument for setTimeout... this is the one of ways to implement monotonically increased time counter.

var time = 0;

setTimeout(function x(actualLateness) {
  setTimeout(x, 0);
  time += actualLateness;
}, 0);
4esn0k
  • 9,789
  • 7
  • 33
  • 40
  • 2
    setTimeout has no guarantee of the maximum delay. The parameter specifies a minimum, only. There is no way to conclusively determine the actual amount of time passed using setTimeout. – danorton Feb 24 '12 at 16:13
  • see https://developer.mozilla.org/en/DOM/window.setTimeout, >> Note: Prior to Gecko 13 (Firefox 13.0 / Thunderbird 13.0) , Gecko passed an extra parameter to the callback routine, indicating the "actual lateness" of the timeout in milliseconds. This non-standard parameter is no longer passed, i have updated my answer – 4esn0k Feb 26 '12 at 10:16