3

I need to show the server time on a clock. Below is the code i currently have. I get the server time with Ajax call. The problem is that if the user changes it's local/computer clock it will also update the script's clock which is not ok - it should continue without changing and i'm stuck. I've tried passing the serverTime within the setTimeout so it get's used every time as a reference but no luck with that.

var serverTime  = 1490856278000;
var localTime   = +Date.now();
var timeDiff    = serverTime - localTime;
var realTime;
var date;
var hours;
var minutes;
var seconds;
setInterval(function () {
  realTime = +Date.now() + timeDiff;
  date     = new Date(realTime);
  hours    = date.getHours();
  minutes  = date.getMinutes();
  seconds  = date.getSeconds();
  document.getElementById('clock').innerHTML = hours + ':' + minutes + ':' + seconds;
}, 1000);
<div id="clock"></div>
g5wx
  • 700
  • 1
  • 10
  • 30
  • you should periodically query the servers time, not just a single time. – Daniel A. White Mar 30 '17 at 12:03
  • What is the problem with the user changing his local time? He can always break your page. – Bergi Mar 30 '17 at 12:03
  • Yeah, but i don't want the clock to change if it does. – g5wx Mar 30 '17 at 12:03
  • 1
    Since your interval is every `1000ms`, you could reasonably expect it to run within a small window of that value. Compare the `Date.now()` you get to whatever `Date.now()` you got last time around, and if it changes by an unexpected amount (more than 1.5s, less than 0.5s, or negative, for example) then re-request the time from the server to recalibrate. – Niet the Dark Absol Mar 30 '17 at 12:03
  • @NiettheDarkAbsol Seems as a good option, thanks – g5wx Mar 30 '17 at 12:05
  • 1
    Btw, you probably will want to use `getUTCHours`, `getUTCMinutes` and `getUTCSeconds` instead of the local ones that could be tinkered with already by a changing time zone. – Bergi Mar 30 '17 at 12:06
  • Thanks for the notice. – g5wx Mar 30 '17 at 12:09

4 Answers4

3

You should be able to compare each realTime with the last one in your setInterval. If the difference is far from the 1000ms that it is supposed to be, do an ajax call to query the server time again and renew the timeDiff.


Also you can try to use performance.now instead of Date.now. The higher resolution is unnecessary and possibly expensive, but MDN states that

unlike Date.now(), the values returned by Performance.now() always increase at a constant rate, independent of the system clock (which might be adjusted manually or skewed by software like NTP)

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2

Using How to create an accurate timer in javascript? and Bergi's answer I prepared an another way. I think you don't have to use the local time at all:

var serverTime  = 1490856278000;
var expected = serverTime;
var date;
var hours;
var minutes;
var seconds;
var now = performance.now();
var then = now;
var dt = 0;
var nextInterval = interval = 1000; // ms

setTimeout(step, interval);
function step() {
    then = now;
    now = performance.now();
    dt = now - then - nextInterval; // the drift
    
    nextInterval = interval - dt;
    serverTime += interval;
    date     = new Date(serverTime);
    hours    = date.getUTCHours();
    minutes  = date.getUTCMinutes();
    seconds  = date.getUTCSeconds();
    document.getElementById('clock').innerHTML = hours + ':' + minutes + ':' + seconds;

    console.log(nextInterval, dt); //Click away to another tab and check the logs after a while
    now = performance.now();

    setTimeout(step, Math.max(0, nextInterval)); // take into account drift
}
<div id="clock"></div>
Community
  • 1
  • 1
Oskar
  • 2,548
  • 1
  • 20
  • 22
  • The problem with that is that [`setInterval` drifts away](http://stackoverflow.com/a/29972322/1048572). – Bergi Mar 30 '17 at 12:13
  • Ach, ok. I'll try to adjust my answer, not sure if we need that local time. – Oskar Mar 30 '17 at 12:14
  • Using server time only seems like a quick and easy solution. @Bergi you think it can drift away in 15 minutes badly? I plan to refresh the page every 15 min. – g5wx Mar 30 '17 at 12:22
  • @g5wx If the tab is not active (in background), yes it can happen, otherwise you will hardly notice. But I thought the whole point of making pages interactive with JS is so that you *don't* need to constantly refresh them :-) – Bergi Mar 30 '17 at 12:24
  • Hehe, yes it is :) – g5wx Mar 30 '17 at 12:25
  • Thanks @Oskar for your solution. – g5wx Mar 30 '17 at 12:26
  • @Bergi What if we use the performance.now() for computing the dt and adjusting the time interval? – Oskar Mar 30 '17 at 12:42
  • @g5wx the pleasure is mine, I learned something new today thanks to your question, I also updated my answer, but not sure if it helps with dt. – Oskar Mar 30 '17 at 12:43
  • @Oskar Yes, you can use the "accurate timer" approach with `performance.now` as well – Bergi Mar 30 '17 at 12:45
  • 1
    @Bergi, I just checked and fixed my code to be more reliable and drift-proof, when I click away from the SO tab it shows the correct time. You can check the dt in console.log. Thanks again for your answer. – Oskar Mar 30 '17 at 13:18
  • but when will then = now?! – Jake Gaston Feb 09 '18 at 19:42
0

The time will change because Date.now(); is getting it's time from the Client machine. There are no AJAX calls in your script.

Panomosh
  • 884
  • 2
  • 8
  • 18
0

More Updated with AM & PM

var serverTime = 1490856278000;
    var expected = serverTime;
    var date;
    var h;
    var m;
    var s;
    var now = performance.now();
    var then = now;
    var dt = 0;
    var nextInterval = (interval = 1000);

    setTimeout(step, interval);
    function step() {
      then = now;
      now = performance.now();
      dt = now - then - nextInterval;
      nextInterval = interval - dt;
      serverTime += interval;
      date = new Date(serverTime);
      h = date.getHours();
      m = date.getMinutes();
      s = date.getSeconds();

      var session = "AM";

      if (h == 0) {
        h = 12;
      }

      if (h > 12) {
        h = h - 12;
        session = "PM";
      }

      h = h < 10 ? "0" + h : h;
      m = m < 10 ? "0" + m : m;
      s = s < 10 ? "0" + s : s;

      var time = h + ":" + m + ":" + s + " " + session;

      document.getElementById("NowTime").innerHTML = time;

      now = performance.now();

      setTimeout(step, Math.max(0, nextInterval));
    }