JavaScript setTimeout and changes to system time cause problems

4.6k views Asked by At

I've noticed that if I set the setTimeout for 1 minute in the future, and then change my system time to 5 minutes in the past, the setTimeout function will trigger in 6 minutes.

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

My JavaScript webpage uses a setTimeout function 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?

Edit: I am updating the page using Ajax, I dont want to refresh the entire page.

6

There are 6 answers

0
Siedrix On BEST ANSWER

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.

1
Colum On

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.

3
Martin Jespersen On

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.

0
Raynos On

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.

0
jrumbinas On

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.

0
blackmind On

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);
  });
}