Skip to content

Instantly share code, notes, and snippets.

@ethaizone
Last active December 21, 2024 11:43
Show Gist options
  • Save ethaizone/6abb1d437dbe406fbed6 to your computer and use it in GitHub Desktop.
Save ethaizone/6abb1d437dbe406fbed6 to your computer and use it in GitHub Desktop.
Sync server time to client browser with JS. Implement follow Network Time Protocol.
// Original from http://stackoverflow.com/questions/1638337/the-best-way-to-synchronize-client-side-javascript-clock-with-server-date
// Improved by community and @jskowron
// Synchronize client-side clock with server time using an approximation of NTP
let serverTimeOffset = null;
function getServerTime(callback) {
if (serverTimeOffset === null) {
const scripts = document.getElementsByTagName("script");
const currentScriptURL = scripts[scripts.length - 1].src;
const clientTimestamp = Date.now(); // Client timestamp before request
const xhr = new XMLHttpRequest();
xhr.open("HEAD", `${currentScriptURL}?noCache=${clientTimestamp}`, true);
xhr.onload = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const serverDateHeader = xhr.getResponseHeader("Date");
if (serverDateHeader) {
const serverTimestamp = Date.parse(serverDateHeader);
const nowTimeStamp = Date.now(); // Timestamp after response
// Calculate offset
serverTimeOffset = serverTimestamp - ((clientTimestamp + nowTimeStamp) / 2);
// Return synchronized time
const syncedDate = new Date(Date.now() + serverTimeOffset);
if (callback) callback(syncedDate);
} else {
console.error("Server did not return a valid Date header.");
}
} else {
console.error("Failed to fetch server time:", xhr.statusText);
}
};
xhr.onerror = function () {
console.error("Network error while fetching server time.");
};
xhr.send(null);
} else {
// Return cached offset-adjusted time
const syncedDate = new Date(Date.now() + serverTimeOffset);
if (callback) callback(syncedDate);
}
}
@ethaizone
Copy link
Author

ethaizone commented Mar 27, 2017

@sela @muskkir I just update code. This error cause from last update spec that force all xmlHttpRequest must call as async.

I just update code with callback. You can implement it as promise or reactive if you need.

@ddcq
Copy link

ddcq commented Jan 16, 2020

This maths are wrong but I don't know where:

serverClientRequestDiffTime = serverTimestamp - clientTimestamp;
serverClientResponseDiffTime = nowTimeStamp - serverTimestamp;
responseTime = (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime )/2;
serverTimeOffset = (serverClientResponseDiffTime - responseTime);
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime )/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverTimestamp - clientTimestamp - nowTimeStamp + clientTimestamp - (nowTimeStamp - serverTimestamp))/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverTimestamp - nowTimeStamp - nowTimeStamp + serverTimestamp)/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (2serverTimestamp - 2nowTimeStamp)/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverTimestamp - nowTimeStamp)
serverTimeOffset = nowTimeStamp - serverTimestamp - serverTimestamp + nowTimeStamp
serverTimeOffset = 2*nowTimeStamp

@Hooponopono
Copy link

This maths are wrong but I don't know where:

serverClientRequestDiffTime = serverTimestamp - clientTimestamp;
serverClientResponseDiffTime = nowTimeStamp - serverTimestamp;
responseTime = (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime )/2;
serverTimeOffset = (serverClientResponseDiffTime - responseTime);
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime )/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverTimestamp - clientTimestamp - nowTimeStamp + clientTimestamp - (nowTimeStamp - serverTimestamp))/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverTimestamp - nowTimeStamp - nowTimeStamp + serverTimestamp)/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (2_serverTimestamp - 2_nowTimeStamp)/2
serverTimeOffset = nowTimeStamp - serverTimestamp - (serverTimestamp - nowTimeStamp)
serverTimeOffset = nowTimeStamp - serverTimestamp - serverTimestamp + nowTimeStamp
serverTimeOffset = 2*nowTimeStamp

try this:
A = B-C
D = E-B
G = (A-E+C-D)/2
F= D-G

F = (E-B)-(A-E+C-D)/2
= E-B -(B-C-E+C-E+B)/2
= E-B-(-2E+2B)/2 <== @ddcq
= E-B +E-B
= (E-B)*2

@jskowron
Copy link

jskowron commented Sep 27, 2024

The math does not seen right in the code.

Lets assume that there is some true time T. Lets assume that server knows the true time T, so the response from the server will be:
server = T + DELTA_REQUEST
Lets name the offset between the true time and the client time as OFFSET, so by definition the client time is:
client = T - OFFSET
After request and response, the time observed now on the client will be:
now = T - OFFSET + DELTA_REQUEST + DELTA_RESPONSE
where DELTA_REQUEST and DELTA_RESPONSE are the milliseconds lost for the request and response, respectively.

We can calculate OFFSET, i.e., we can estimate how the client time differs from the true time, in two ways:
OFFSET_1 = T - client = (server - DELTA_REQUEST) - client = (server - client) - DELTA_REQUEST
and symmetrically:
OFFSET_2 = T + DELTA_REQUEST - now + DELTA_RESPONSE = (server - now) + DELTA_RESPONSE

We can now estimate the real OFFSET by taking the mean value of the two estimations. We get:

OFFSET = ( OFFSET_1 + OFFSET_2 ) / 2 =
    = (server - client - DELTA_REQUEST + server - now + DELTA_RESPONSE) / 2 =
    = (server - client / 2 - now / 2)   +   (DELTA_RESPONSE - DELTA_REQUEST) / 2

If we assume that the DELTA_RESPONSE and DELTA_REQUEST are similar, than we can drop the last term as small. And we get:
OFFSET ~= server - client / 2 - now / 2

In the full names used in the code, it would be:
serverTimeOffset = serverTimestamp - clientTimestamp / 2 - nowTimeStamp / 2

Another way of getting to the same conclusion is this:

We can estimate the travel time as the mean of DELTA_REQUEST and DELTA_RESPONSE:
DELTA_TRAVEL = (DELTA_REQUEST + DELTA_RESPONSE) / 2 = (now - client ) / 2
If we assume that the travel time is a reasonable approximation of the request time, then we can write:
DELTA_REQUEST ~= DELTA_TRAVEL
And, hence, we know the OFFSET approximately as:
OFFSET ~= (server - client) - DELTA_TRAVEL

In the full names used in the code, it would be:

responseTime = (nowTimeStamp - clientTimestamp ) / 2
serverClientRequestDiffTime = serverTimestamp - clientTimestamp
serverTimeOffset = serverClientRequestDiffTime - responseTime

Which is mathematically equivalent to the previously obtained:
serverTimeOffset = serverTimestamp - clientTimestamp / 2 - nowTimeStamp / 2
after appropriate substitutions.

The equations in the original code are wrong, since the time OFFSET between the real time and client time is added twice. I have checked this be changing the clock by 1 minute on the client, and I got offset of 2 minutes in the result.

@ethaizone
Copy link
Author

@jskowron Maybe you should read this one first. https://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm
It created base on this algorithm to fix incorrect time setting in client side like clock resetted back to first second of unix timestamp and round trip network delay.

I'm not sure what you explain above consider about these cases or not but TBH. I'm not who create this implementation. It's from SO topic at first line of code. https://stackoverflow.com/a/15785110/1162506 You can check URL and ask author for that. I think that is better place to discuss for improvement.

In my case, I try to recheck it now and I still think it's still look acceptable to use except someone plan to update content inside wikipedia.

@jskowron
Copy link

@ethaizone
First, I have read the Wikipedia, but regardless, it does not change the fact that your code returns wrong values. Just test it. Set your machine clock to the wrong time by 60 seconds, while keeping the server time right, and observe that your code returns ~120 seconds instead of 60.

And now, returning to the theoretical argument: in Wikipedia article the times are t0, t1, t3, t4. These can be translated in your code to:

t0 = clientTimestamp
t1 = serverTimestamp
t2 = serverTimestamp
t3 = nowTimeStamp

Then, quoting the equation from Wikipedia, the server time offset is:

\theta = ((t1 - t0) + (t2 - t3) ) / 2 = 
    = (serverTimestamp - clientTimestamp + serverTimestamp - nowTimeStamp) / 2 =
    = serverTimestamp  - clientTimestamp / 2 - nowTimeStamp / 2

Hence, this is exaclty what is in my final equation from the previous post.

However, while citing the exact equations from your code, the result is this:

serverTimeOffset = (serverClientResponseDiffTime - responseTime) = 
    = serverClientResponseDiffTime - (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime ) / 2 =
    = serverClientResponseDiffTime - (serverTimestamp - clientTimestamp - nowTimeStamp + clientTimestamp - nowTimeStamp + serverTimestamp) / 2 =
    = (nowTimeStamp - serverTimestamp) - (2 serverTimestamp - 2 nowTimeStamp) / 2 =
    = 2 * (nowTimeStamp - serverTimestamp)  --- i.e., wrong

First, it does not use clientTimestamp at all. Second, it returns the difference doubled.

Just change the serverTimeOffset equation in your code to:
serverTimeOffset = serverTimestamp - (clientTimestamp + nowTimeStamp) / 2
and everything will be good.

Also, look at the comments of the Stack Overflow answer you have linked to. They say the equation is wrong as well.
Regardless, you don't have to believe them nor me, just check.

@ethaizone
Copy link
Author

ethaizone commented Dec 21, 2024

@jskowron Thank you for be patient with me. Actually at that time I'm very busy until point that my brain isn't working properly.
Today I read your message carefully again and I agreed with you so I improved code.

Hope that next year will be happy years for everyone and thank you for who helped to contribute in this little code snippet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment