上記の@mehdi-yeganehのアルゴリズムでは有用な結果が得られなかったことがわかりましたが、アイデアは適切です.NTPアルゴリズム(または少なくともその弱いバージョン)を使用してサーバーとクライアントのクロックを同期させます。
これは私の最終的な実装です。サーバーの応答ヘッダーを使用して精度を高めます (間違っている場合は修正してください。私自身のテストでは、これは非常に正確です)。
ブラウザ側 (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)));
}
});
サーバー側(php、しかし何でもかまいません):
ルート「GET /ntp」のサーバーは、次のようなものを返す必要があります。
echo (string) round(microtime(true) * 1000);
PHP > 5.4 を使用している場合は、microtime() への呼び出しを保存して、次のようにしてもう少し正確にすることができます。
echo (string) round($_SERVER['REQUEST_TIME_FLOAT'] * 1000);
ノート
この方法は一種のゲットーのように見えるかもしれませんが、より良い解決策に導く可能性のあるスタック オーバーフローの回答が他にもいくつかあります。