Martin Konecny
Martin Konecny

Reputation: 59691

JavaScript timer (setTimeout) not affected by system time changes

I've noticed that if I call the JavaScript setTimeout() function with a 1 minute delay, and then change my system time to 5 minutes in the past, the callback function will trigger in 6 minutes.

I did this because I wanted to see what happens during a daylight savings change to the system clock.

My webpage calls setTimeout() to automatically refresh the page every 5 seconds, and if daylight savings were to occur, then the page information would freeze for an hour. Is there a workaround which respects the delay even when such rare changes to the clock occur?

I am updating the page using Ajax, I don't want to refresh the entire page.

Upvotes: 22

Views: 4906

Answers (6)

Siedrix
Siedrix

Reputation: 641

What I will do is change from opening a new Ajax request every 5 seconds to using Comet (long polling). It is a better option because the server will push the new results to the browser every time that they are needed, this can be every 5 seconds on server side or every time new information is available.

Even better would be to use web sockets and have Comet as fall back. This is easy to implement with Faye or socket.io.

There are other options, like CometD or Ape.

Upvotes: 4

jrumbinas
jrumbinas

Reputation: 426

I recently encountered this problem too. In my case time change might cause serious financial loses so I had to take it seriously.

Some background:

  • IE and Opera handles systems time change properly (no side effects to setTimeout(), setInterval()).
  • FF < 4.0 and all Webkit based browser fails to handle changing time to the past (exactly as you described). Note: timers in Chrome however stops only after some time ~2-60s.

My solution: use some event from user interaction (i.e. mousemove, mouseover, etc) to trigger time change check.

Clock.prototype.checkLocalTime = function(){
  var diff = Date.now()- this.lastTimestamp;
  if ( diff < 0 || diff > 20000) { // if time shifted back or more than 20s to the future
    console.warn('System time changed! By ' + diff + 'ms' );
    this.restartTimer();
    this.getServerTime();
  }
  this.lastTimestamp = time;
}

As you can notice I restart timer and additionally synchronize time with server.

Upvotes: 2

blackmind
blackmind

Reputation: 1296

I found that using window.performance gave me more accurate results and kept things consistent when the system time changes.

I used this as a way to get the current time inside a setInterval tick so that a countdown timer would not be affected by a system time change. I was calculating it based on number of ticks from a given time to attempt at preventing cheating on the timer (getting more time) by changing the system clock

getCurrentTime = () => {
  return window.performance ?
    performance.timing.navigationStart + performance.now() : Date.now();
}

tick() {
  let now = this.getCurrentTime();

  if (this.calibrateTime) { //calibrate with a given server time
    now -= this.timeCalibration;
  }

  const elapsedSeconds = (now - this.startedAt) / 1000;

  if (elapsedSeconds > this.props.timeLimit + this.props.submitBuffer) {
    clearInterval(this.interval);
    this.props.onTimeUp();
  }

  let secondsRemaining = this.props.timeLimit - Math.floor(elapsedSeconds);
  if (secondsRemaining < 0) secondsRemaining = 0;

}

startTimer() {
  if (!this.startedAt) {
    this.startedAt = Date.now();
  }

  this.setState({ started: true }, () => {
    this.interval = setInterval(this.tick.bind(this), 500);
  });
}

Upvotes: 1

Martin Jespersen
Martin Jespersen

Reputation: 26183

Use setInterval instead of setTimeout and your problems should be resolved :)

Update:

If you really cannot use either setTimeout or setInterval, you could try to have a hidden iframe that loads a simple HTML page that looks something like this:

<html>
    <head>
        <meta http-equiv="refresh" content="300"/>
        <script>
            document.domain='same as parent page';
            top.ajax_function_you_wish_to_trigger();
        </script>
    </head>
    <body>
    </body>
</html>

If you are lucky the meta refresh won't cause you the same problems.

Another solution would be to move the event firing to the server via server push. There are many reasons not to do this, not the least that you'd centralize the events and make them a burden on the server, but it could be considered a last resort.

Upvotes: 4

Raynos
Raynos

Reputation: 169541

You can fudge and do an if (Date in range foo) check. Basically check whether the current time stamp is within 5 seconds of daylight saving time and then just refresh the page immediately rather then by using setTimeout.

This way users get a minor annoyance near a known time change but the page won't freeze for an hour.

Upvotes: 0

Colum
Colum

Reputation: 3914

You can use the meta tag refresh to do this. This code would refresh the page every 5 seconds:

<meta http-equiv="refresh" content="5"> 

As for the javascript question, it might just be the way the browser handles the setTimeout. It tells the browser to execute the code at a set time, and then when the clock changes, the code still executes at the time before the clocked changed? Just a guess, but I will have to keep that in mind next time I use setTimeout.

Edit: Ok, that would not work for ajax Edit Again: This is a really good question. I am sure that setTimeout functions as I said above, but wow, this is a brain teaser.

Upvotes: 0

Related Questions