69

I have a task to show digital clock (with minutes precision) on HTML page in some fixed timezone (MSK or MSD - depending on current date). I'd like to avoid relying on client system clock, so some synchronization with server is required. HTTP server sends Date header in each response so we can send an AJAX GET or HEAD request to any URL of our site to get server date, calculate the difference with client date and use it when updating clock with setTimeout(). There are other issues remains: timezone switching for daylight settings, latency accounting for very slow connections.

Any idea to this task the simpliest way? I'd prefer to solve it without server-side programming.

n0s
  • 793
  • 1
  • 6
  • 7

9 Answers9

47

You can calculate exact time with NTP (Network Time Protocol) in your codes,

i try to explain for you:

  1. We have ClientTime on Sending Request (for example 4/3/2012 13:56:10.123)
  2. You send ClientTime to Server
  3. We have Round-trip time for request, i called it RequestTime (for example: It takes 5 seconds)
  4. In Server, We calculate Difference time between Server and Client (for example: It ServerTime - ClientTime = ServerClientDifferenceTimeWithRequestTime), you should now this Difference including Round-trip request time in step 3 then you should remove round trip time from Difference
  5. Server Send response that include ServerClientDifferenceTimeWithRequestTime and ServerTime
  6. We have Round-trip time for response, i called it ResponseTime (for example: It takes 3 seconds)
  7. In client, We calculate Difference time between Server and Client again (for example: It ServerTime - ClientTime = ServerClientDifferenceTimeWithResponseTime), again: you should now this Difference including Round-trip response time in step 6
  8. We have Now time in Client
  9. You should Calculate simple equations in client:

X(SyncedTime) = Now + (ServerClientDifferenceTimeWithRequestTime - RquestTime)

X(SyncedTime) = Now + (ServerClientDifferenceTimeWithResponseTime - ResponseTime)

Now - ClientTime = RquestTime + ResponseTime =>

Now - (ServerClientDiffRq - RquestTime) = Now - (ServerClientDiffRs - ResponseTime)

if you solve it you found this:

ResponseTime = (ServerClientDifferenceTimeWithRequestTime - Now + ClientTime + - ServerClientDifferenceTimeWithResponseTime )/2

and then you can found synced time or server time in client with this equation:

X(SyncedTime) = Now + (ServerClientDifferenceTimeWithResponseTime - ResponseTime)

I show simple code but when you want write it don`t forgot use UTC date & time functions...

Server Side (for example php, c#):

PHP:

header('Content-Type: application/json; charset=utf-8');
$clientTime = $_GET["ct"] * 1; //for php 5.2.1 or up: (float)$_GET["ct"];
$serverTimestamp = round(microtime(true)*1000); // (new DateTime())->getTimestamp();
$serverClientRequestDiffTime = $serverTimestamp - $clientTime;
echo "{\"diff\":$serverClientRequestDiffTime,\"serverTimestamp\":$serverTimestamp}";

C#:

long clientTime = long.Parse(Request.Form["ct"]);
long serverTimestamp = (DateTime.Now.Ticks-(new DateTime(1970,1,1) - DateTime.MinValue).Ticks) / 10000;
long serverClientRequestDiffTime = serverTimestamp - clientTime;
Response.Write("{\"diff\":"+serverClientRequestDiffTime+",\"serverTimestamp\":"+serverTimestamp+"}");

Client Side (Javascript with Jquery):

var clientTimestamp = (new Date()).valueOf();
$.getJSON('http://yourhost.com/getdatetimejson/?ct='+clientTimestamp, function( data ) {
    var nowTimeStamp = (new Date()).valueOf();
    var serverClientRequestDiffTime = data.diff;
    var serverTimestamp = data.serverTimestamp;
    var serverClientResponseDiffTime = nowTimeStamp - serverTimestamp;
    var responseTime = (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime )/2

    var syncedServerTime = new Date((new Date()).valueOf() + (serverClientResponseDiffTime - responseTime));
    alert(syncedServerTime);
});
Mehdi Yeganeh
  • 1,914
  • 2
  • 22
  • 40
  • 3
    http://en.wikipedia.org/wiki/Network_Time_Protocol Its ntp protocol.. i used Clock synchronization algorithm for it – Mehdi Yeganeh Apr 03 '13 at 11:18
  • 1
    He's using the same basic algorithm for how NTP functions to calculate the difference between the server and client times--pretty clever stuff. See [this part](http://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm) of the NTP page. – Eric L. Oct 11 '13 at 12:27
  • 2
    I fixed some bugs you had, but this doesn't actually work. If the client clock is off this does not give the correct time as seen by the server. – Ariel Jul 10 '14 at 11:21
  • 1
    @Ariel, thanks for your edit, but you only change something that is not bug, if you dont use quotation marks in json object name in javascript, it's not matter and i pass query string with GET method, you can pass it with jquery but you should use POST method, so i edit this part back to my way, and Its works perfectly, i used it in many projects, What browser you use that can turn off computer clock?? if your browser support javascript you can use it. Anyway, Thank you for your attention. – Mehdi Yeganeh Jul 11 '14 at 12:12
  • @Ariel, for editing PHP i think you change the way and again its not bug, you do it better so i only change the type casting that works only on php 5.2.1 or up and wrote in comments, so thanks for editing php. – Mehdi Yeganeh Jul 11 '14 at 12:36
  • @MehdiYeganeh Yes it does matter if you don't use quotation marks. If you don't use them it fails completely because it is illegal `json`. If you don't like type casting then use `floatval()`. If the clock on the browser is wrong then this code does not work. Try it, you will see. Not "turn off" the clock, but just set to the wrong time. The code from Aktau does work. – Ariel Jul 13 '14 at 05:48
  • @Ariel plz check this code on your browser console: a = {cc:1,dd:"hello"} as you can see its works fine. if you want put qutation then put it on your code or if you like use type casting you can use it but its not bug, its not problem of my code as i said you before i test it and the question above is not about how should i cast string to int in php? .. and main answer is You can use NTP for syncing clocks. and the codes only are Examples and in simplest way. – Mehdi Yeganeh Jul 13 '14 at 11:36
  • @Ariel, Please read NTP protocol description and check my code again. its not important client time be wrong as you can see on Aktau code its used like me 'var t0 = (new Date()).valueOf();' we should get client timestamp to calculate differences between client time and server time and in result its not matter your client timestamp be 0 or be 10000000, only if you change your client clock between request and response it goes wrong.so if you think the answer 'Using NTP' has problem or examples is not implemented from NTP as you do it before,just edit it. Thanks for helping to developers like me. – Mehdi Yeganeh Jul 13 '14 at 11:56
  • @MehdiYeganeh I *tried it*. I'm not just complaining about nothing. If you don't put in the quotes it does not work. `JSON` is NOT `Javascript`! They have different rules. I also tried it with a clock that was wrong and it did not work - I got a time that was halfway between the client clock and the server clock. You had a *VERY* good idea doing this! But there is a bug somewhere. – Ariel Jul 13 '14 at 15:35
  • @Ariel, how do you calculate RequestTime from step 3? If either client or server time are out of sync, you can't be sure that difference between ServerTime and ClientTime will give you RequestTime - the difference will contain the offset, as well. Without that, I don't see how your algorithm could function. – Nikola Anusev Sep 28 '14 at 22:02
  • @NikolaAnusev This is not my code, you should ask Mehdi instead. If you want to implement this, use Aktau's answer - it actually works. Mehdi had a great idea, but had bugs in the implementation. Based on the things I fixed just to get it to run I think he wrote it without testing it. – Ariel Sep 29 '14 at 04:58
  • Ah, sorry about that - got a bit confused after reading all those comments. @MehdiYeganeh, could you please comment on my question? – Nikola Anusev Sep 29 '14 at 08:19
  • @Nikola Anusev, @Ariel, Tanx ariel for comment but it haven`t bug, its protocol, and implementation is true enough for find exact server time in client.and dear nikola, its really simple, just plz read NTP Protocol first (http://en.wikipedia.org/wiki/Network_Time_Protocol) to find "how is it work?".for step 3, before send request to server we save client time in variable and after get response we check difference time between request and response, its round trip time that calculated in client.its not depend server time or client time that is true or not,we just find round-trip time of request. – Mehdi Yeganeh Sep 29 '14 at 08:36
  • 1
    This only works for symmetrical delays, which are quite far from guaranteed here. – Lightness Races in Orbit Jul 09 '15 at 14:53
  • 1
    @Mehdi: Objects in JavaScript are not the same as JSON objects. The former can have unquoted keys; the latter cannot. It's as simple as that. – Lightness Races in Orbit Jul 09 '15 at 14:55
  • @Lightness Races in Orbit, tanx for see my answer, and sorry for delay response, i know about differences between javascript object & json object now, this answer is for sometimes ago, i saw your info, you are good in stack over flow, if you interest about this question & my answer.. please edit it to be better & help developers, my english is not good .. please edit my english words & codes ;) thank you :) – Mehdi Yeganeh Jul 17 '15 at 22:46
  • 4
    Downvoted because the formula on the client side is wrong. The correct formula is syncedServerTime = clientTime + (data.Diff + (data.ServerTime - nowTimeStamp)) / 2 – Rafael Feb 05 '16 at 17:32
  • @rafael, thanks for check my answer, plz see NTP, i wrote the exact same formula that defined in NTP and you don`t need to divide by 2.. i think you should read again NTP ... misunderstood ;) – Mehdi Yeganeh Feb 14 '16 at 23:21
  • 2
    @Mehdi: It should be (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp + serverClientResponseDiffTime )/2 because serverClientResponseDiffTime is calculated server side – deathpote Mar 16 '17 at 18:05
41

These two Javascript functions should do the trick for you.

var offset = 0;
function calcOffset() {
    var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    xmlhttp.open("GET", "http://stackoverflow.com/", false);
    xmlhttp.send();

    var dateStr = xmlhttp.getResponseHeader('Date');
    var serverTimeMillisGMT = Date.parse(new Date(Date.parse(dateStr)).toUTCString());
    var localMillisUTC = Date.parse(new Date().toUTCString());

    offset = serverTimeMillisGMT -  localMillisUTC;
}

function getServerTime() {
    var date = new Date();

    date.setTime(date.getTime() + offset);

    return date;
}

EDIT: removed ".replace(/^(.)[\s\S]/,"$1")".

calcOffset() calculates the offset from server time and compensates for GMT/UTC.

getServerTime() to get the local time offset to match the servers, using the local timezone.

If calcOffset() takes along time to execute you might loose some seconds precision. Maybe the execution time could be taken into account....

If you are worried about the calculated offset becoming wrong when either local time or server time change to or from daylight savings time you could recalculate a litle after every clock-hour, the system will compensate for changes in dayligt savings time. It might be necessary to wait until both the local and server clock has passed the hour.

The example only works in IE because of "Msxml2.XMLHTTP" i think.....

Fedearne
  • 6,681
  • 4
  • 26
  • 31
  • what this strange regexp is meant to do: `.replace(/^(.*)[\s\S]*/,"$1")` ? – n0s Oct 29 '09 at 14:46
  • I actually copied that part from some old source. The author doesn't remember what is does and i doesn't even appear to valid regex.... Sorry about that.... – Fedearne Oct 30 '09 at 09:04
  • 9
    Change method from "GET" to "HEAD" will cause http server to not return body. This might speed up the request. – Bogdan Gusiev Feb 10 '12 at 14:09
  • 4
    Note that `Date.parse(new Date().toUTCString());` creates a date object, converts it to UTC string, then parses that back into a date and returns milliseconds since epoch. It can be replaced by the much simpler `(new Date()).getTime();` or `new Date() * 1` – RobG Sep 27 '12 at 05:50
  • 2
    @RobG Or `Date.now()` – Max Nanasy Sep 17 '14 at 17:16
  • @MaxNanasy—that's ES5, which was not quite so ubiquitous back then (and is not supported by a good percentage of browsers in use now). It's easily polyfilled I suppose. But yes, that works too. – RobG Sep 18 '14 at 04:52
  • This doesn't actually correct for the latency like NTP, does it? – Andy Jan 22 '16 at 18:25
  • This also has the downside of requiring ActiveX tech. – Joseph Rosson Aug 01 '16 at 13:38
30

I've found that the algorithm of @mehdi-yeganeh above didn't give me useful results but the idea is sound: to use the NTP algorithm (or at least a weak version of it) to synchronize the server and client clocks.

This is my final implementation, it uses the server response headers if available for extra accuracy (please correct me if I'm wrong, my own tests say this is quite accurate).

browser-side (javascript):

// the NTP algorithm
// t0 is the client's timestamp of the request packet transmission,
// t1 is the server's timestamp of the request packet reception,
// t2 is the server's timestamp of the response packet transmission and
// t3 is the client's timestamp of the response packet reception.
function ntp(t0, t1, t2, t3) {
    return {
        roundtripdelay: (t3 - t0) - (t2 - t1),
        offset: ((t1 - t0) + (t2 - t3)) / 2
    };
}

// calculate the difference in seconds between the client and server clocks, use
// the NTP algorithm, see: http://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm
var t0 = (new Date()).valueOf();

$.ajax({
    url: '/ntp',
    success: function(servertime, text, resp) {
        // NOTE: t2 isn't entirely accurate because we're assuming that the server spends 0ms on processing.
        // (t1 isn't accurate either, as there's bound to have been some processing before that, but we can't avoid that)
        var t1 = servertime,
            t2 = servertime,
            t3 = (new Date()).valueOf();

        // we can get a more accurate version of t2 if the server's response
        // contains a Date header, which it generally will.
        // EDIT: as @Ariel rightly notes, the HTTP Date header only has 
        // second resolution, thus using it will actually make the calculated
        // result worse. For higher accuracy, one would thus have to 
        // return an extra header with a higher-resolution time. This 
        // could be done with nginx for example:
        // http://nginx.org/en/docs/http/ngx_http_core_module.html
        // var date = resp.getResponseHeader("Date");
        // if (date) {
        //     t2 = (new Date(date)).valueOf();
        // }

        var c = ntp(t0, t1, t2, t3);

        // log the calculated value rtt and time driff so we can manually verify if they make sense
        console.log("NTP delay:", c.roundtripdelay, "NTP offset:", c.offset, "corrected: ", (new Date(t3 + c.offset)));
    }
});

server-side (php, but could be anything):

Your server at route 'GET /ntp' should return something like:

echo (string) round(microtime(true) * 1000);

If you have PHP >5.4, then you can save a call to microtime() and make it a bit more accurate with:

echo (string) round($_SERVER['REQUEST_TIME_FLOAT'] * 1000);

NOTE

This way might be seen as kind of ghetto, there are some other Stack Overflow answers that could guide you towards a better solution:

Community
  • 1
  • 1
Aktau
  • 1,787
  • 19
  • 29
  • The t2 correction doesn't work well because it is only second resolution instead of millisecond like the rest. It actually makes accuracy worse. – Ariel Jul 10 '14 at 11:36
  • Thanks for the heads up. I had not even thought of the time resolution issue. I will comment it out in my answer and make note of your remark. – Aktau Jul 11 '14 at 11:07
  • +1. I actually think this should be the accepted answer - in my implementation, each response from server contains custom HTTP headers with t1 and t2 (with ms accuracy), the rest is just as you wrote. – Nikola Anusev Sep 28 '14 at 22:13
  • 4
    http://github.com/nicksardo/GoTime is another JS library similar to ServerDate but has handlers for use with Websockets, which greatly improves the precision over typical http requests. – Sardonic Dec 05 '14 at 20:39
  • If I could star answers I would. – noɥʇʎԀʎzɐɹƆ Aug 09 '15 at 22:44
  • Can you please elaborate on setting the accurate nginx date header? Is it the same for Apache? Thanks a lot @Aktau. – TechWisdom Sep 20 '15 at 14:21
  • @TechWisdom: look at some of the variables with which you can set custom header here: http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_time (look at the `$msec` and perhaps also interesting things can be done with the `$request_time` variable). – Aktau Sep 23 '15 at 22:42
  • Thanks a lot, I use a header with %t %D in htaccess for Apache. – TechWisdom Sep 24 '15 at 13:24
  • I also wish I could star @Sardonic answer- GoTime works great so far. If I need more accuracy I can implement WebSockets. – brendan Jan 21 '16 at 05:46
  • @Sardonic:with GoTime if we need the response time we shoul subtract offset or not ? – Gogo Jul 02 '20 at 21:54
12

you should remember client time between readyState==2 and readyState==3 if you are going to use ajax, because server time will be set somewhere between time on request recieved and response prepared

  • Any example with jquery or some other JS framework hiding difference in AJAX implementation? – Denis Otkidach Oct 29 '09 at 11:23
  • i didn't provided example using jquery/mootools/prototype because them all are hiding work with browser's native xmlhttprequest object and don't provide interface for handling readystatechange events – Victor Kotseruba Oct 29 '09 at 14:24
0

I'd only request the update from the server every 30s or so, if you require precision only to the minute. Don't rely on the client time at all, but use their system clock to keep the clock accurate between updates. I think you answered your own question?

It would help if we better understood what you're actually trying to do.

If you simply want a clock to display the time on the server, which is then adjusted to a certain timezone, do it clientside with offsets. Handle DST in the timezones it is applicable by using the date you receive from the server as well. If you want to determine latency, you would probably need a small script on the server to calculated the difference. But as above, it would help to understand the problem better. If precision is only to the minute, latency seems less critical.

snicker
  • 6,020
  • 6
  • 41
  • 49
  • 3
    Single synchronization is enough to get the difference. The question was about properly handling timezone and accounting latency, or a better way to solve the problem. – Denis Otkidach Oct 28 '09 at 16:30
  • Then the question is not very clear, as doubly indicated by the commenter thinking the asker wanted to change the client clock. – snicker Oct 28 '09 at 16:40
  • 1
    I believe "to show digital clock" means some HTML markup with javascript updating it. – Denis Otkidach Oct 28 '09 at 16:43
0

Thanks to @Mehdi Yeganeh and @Fedearne. I implement my function to use both logic and it's work.

https://gist.github.com/ethaizone/6abb1d437dbe406fbed6

EThaiZone
  • 301
  • 1
  • 8
  • 1
    Link-only answers are no good because they can die. If you want to post a link, you can do so as a comment under the question. – mickmackusa Sep 16 '20 at 01:06
0

Little too late but hope this might help someone!

I had a similar requirement to display a server clock irrespective of client's machine. So basically, you just play with three parameters here:

x = clientReqTimestamp = (new Date()).valueOf();  //Client Timestamp at request.
y = serverTimestamp;  //query your server for Unix Timestamp.
z = clientRespTimestamp = (new Date()).valueOf();  //Client Timestamp on receiving response.

Then do the below calculation:

var reqElapsed = Math.abs(y - x);  //time taken in milliseconds to hit the server
var respElapsed = Math.abs(z - y);  //time taken in milliseconds to get response from server
var serverNewTime = z + respElapsed;  // Voila! actual server time.

Below is the full code in action:

<script>
       
    var clientTimestamp = (new Date()).valueOf();
    
    var Data = {
        OperatorMobileNo: 'XXXXXXXXXX',
        requestClientTime: clientTimestamp
    };
    $.ajax({
        type: "POST",
        url: serviceURLx + "/XXXX/GetServerDateTime/1.0",
        dataType: "JSON",
        data: JSON.stringify(Data),
        contentType: "application/json; charset=utf-8",
        success: function (responseData) {
            debugger;
            var responseJSON = JSON.parse(JSON.stringify(responseData));
            if (responseJSON.ResponseCode === "000") {
    
                var x = clientReqTimestamp = clientTimestamp;
                    // If server time is in seconds => multiply by 1000 to convert sec to milli
                var y = serverTimestamp = responseJSON.Response.ServTimestamp * 1000;
                var z = clientRespTimestamp = (new Date()).valueOf();
                
                var reqElapsed = Math.abs(y - x);
                var respElapsed = Math.abs(z - y);

                var serverNewTime = z + respElapsed;
                
                debugger;
                //Init Server Clock
                setInterval( function() {
                    debugger;
                    var servClockT = new Date(serverNewTime += 1000);
                    document.getElementById('serverClock').innerHTML = servClockT;
                }, 1000);
            }
            else {
                swal("", "Unable To Fetch Server Time!", "info");
                console.log(responseJSON.ResponseCode);
            }
        },
        error: function () {
        }
    });
</script>
Adil Khan
  • 1
  • 1
0

If I understand question correctly we have only 3 values

  1. clientTimeWhenRequestSent
  2. serverTime
  3. clientTimeWhenResponseReceived

if assume request and response time are equal we can calculate timeDifference between server and client by:

const diff = serverTime - clientTimeWhenRequestSent 
- (clientTimeWhenResponseReceived - clientTimeWhenRequestSent)/2;

and get correct time on client with help of

const correctClienTime =  (new Date()).valueOf() + diff;
-3

I would sync the time during initialization with Internet Time Server.

http://tf.nist.gov/service/its.htm

Cheok Yan Cheng
  • 48,324
  • 124
  • 436
  • 828
  • 2
    If I had to guess, I'd say it was downvoted because the answer does not provide a way to consume the ITS from javascript – Tony Lâmpada Mar 04 '15 at 13:13