JavaScript 中的精确时间






4.72/5 (13投票s)
从 Google 服务器获取 JavaScript 中的精确时间。
引言
如果我们需要在客户端和服务器时间不准确的情况下获取准确的时间呢? 我能找到的唯一解决方法是 json-time API。 但 json-time
有一个很大的阿喀琉斯之踵。 就是 json-time
托管在 Google 上,我的大多数客户端无法调用该 API。
我找不到任何替代的 API,并感到绝望,直到我在 James Padolseys 的博客中看到这条评论
为什么不向 google.com 发送 HTTP Head 请求来获取当前时间呢? 我已经在需要检查正确时间的应用程序中使用过它,并且从未发现与 NTP 服务器有任何差异。
所以我可以在 google.com 的头部信息中找到我需要的东西
我喜欢这个想法,这里是一个从 Google 服务器访问准确时间的 JavaScript 尝试。
逐步编写代码
使用 XMLHttpRequest
检索数据并访问响应 HEADER
似乎非常简单,所以我编写了第一个版本
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.google.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
alert(xmlhttp.getResponseHeader("Date"));
}
}
xmlhttp.send(null);
但结果,我得到的是 NULL
而不是 date
! 我在查看浏览器控制台日志后找到了问题
MLHttpRequest 无法加载 http://www.google.com/。 请求的资源上没有 'Access-Control-Allow-Origin' 头部信息。 因此,'http://fiddle.jshell.net' 不允许访问。
所以我需要支持 CORS 的 URL。 我搜索了很久,但经过几天的搜索,我找不到任何好的 URL。 我改变了策略,并搜索了另一种捕获日期头信息的方法。 最后,我找到了解决方案。 当我想从不存在的 URL 检索数据时,我收到 HTTP/1.1 404 Not Found 错误,但这次我可以正确读取日期头部信息。 所以我将 URL 从 http://www.google.com 更改为 http://www.googleapis.com
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
alert(xmlhttp.getResponseHeader("Date"));
}
}
xmlhttp.send(null);
好的,现在我得到了我想要的东西,但每次需要时间时向另一个服务器发送请求不是一个好的方法。 我们可以计算本地时间和服务器时间差,并将其存储在本地变量中
var TimeDiff;
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
TimeDiff=new Date(xmlhttp.getResponseHeader("Date")) - (new Date());
alert(TimeDiff);
}
}
xmlhttp.send(null);
或者将其存储在 localstorage
中
var TimeDiffKey = 'Local-Server-TimeDiff';
var TimeDiff;
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
TimeDiff=new Date(xmlhttp.getResponseHeader("Date")) - (new Date());
window.localStorage.setItem(TimeDiffKey, TimeDiff);
}
}
xmlhttp.send(null);
将 TimeDiff
存储在 localstorage
中会引发另一个问题。 我们需要一种及时地重新同步差异的机制
var SyncTimeframe = 1000 * 60 * 60 * 3; // 3 Hours
var LastSyncKey = 'LastSyncWithTimeServer';
var TimeDiffKey = 'Local-Server-TimeDiff';
if (window.localStorage.getItem(LastSyncKey) == null) {
window.localStorage.setItem(LastSyncKey, '' + (new Date(0)));
}
LastSync = new Date(window.localStorage.getItem(LastSyncKey));
if ( Math.abs((new Date()) - LastSync) > SyncTimeframe) {
SyncTime();
}
else {
ShowTime();
}
function SyncTime() {
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
TimeDiff=new Date(xmlhttp.getResponseHeader("Date")) - (new Date());
window.localStorage.setItem(LastSyncKey, '' + (new Date()));
window.localStorage.setItem(TimeDiffKey, TimeDiff);
ShowTime();
}
}
xmlhttp.send(null);
}
function ShowTime(){
alert(window.localStorage.getItem(TimeDiffKey));
}
网络延迟是另一个问题,为了解决它,我在代码中添加了 AcceptedDelay
和 RetryCount
:
var SyncTimeframe = 1000 * 60 * 60 * 3; // 3 Hours
var LastSyncKey = 'LastSyncWithTimeServer';
var TimeDiffKey = 'Local-Server-TimeDiff';
var RetryMax = 3;
var RetryCount = 0;
var AcceptedDelay = 500;
if (window.localStorage.getItem(LastSyncKey) == null) {
window.localStorage.setItem(LastSyncKey, '' + (new Date(0)));
}
LastSync = new Date(window.localStorage.getItem(LastSyncKey));
if ( Math.abs((new Date()) - LastSync) > SyncTimeframe) {
SyncTime();
}
else {
ShowTime();
}
function SyncTime() {
var StartTime = new Date();
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
TimeDiff=new Date(xmlhttp.getResponseHeader("Date")) - (new Date());
if (++RetryCount < 3 && (new Date()) - StartTime > AcceptedDelay) {
SyncTime();
}
else {
window.localStorage.setItem(LastSyncKey, '' + (new Date()));
window.localStorage.setItem(TimeDiffKey, TimeDiff);
ShowTime();
}
}
}
xmlhttp.send(null);
}
function ShowTime(){
alert(window.localStorage.getItem(TimeDiffKey));
}
此外,我们可以将一半的网络延迟添加到时间差中,以更好地处理网络延迟
var SyncTimeframe = 1000 * 60 * 60 * 3; // 3 Hours
var LastSyncKey = 'LastSyncWithTimeServer';
var TimeDiffKey = 'Local-Server-TimeDiff';
var RetryMax = 3;
var RetryCount = 0;
var AcceptedDelay = 500;
if (window.localStorage.getItem(LastSyncKey) == null) {
window.localStorage.setItem(LastSyncKey, '' + (new Date(0)));
}
LastSync = new Date(window.localStorage.getItem(LastSyncKey));
if ( Math.abs((new Date()) - LastSync) > SyncTimeframe) {
SyncTime();
}
else {
ShowTime();
}
function SyncTime() {
var StartTime = new Date();
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
TimeDiff=new Date(xmlhttp.getResponseHeader("Date")) -
(new Date()) + ((new Date()) - StartTime) / 2;
if (++RetryCount < 3 && (new Date()) - StartTime > AcceptedDelay) {
SyncTime();
}
else {
window.localStorage.setItem(LastSyncKey, '' + (new Date()));
window.localStorage.setItem(TimeDiffKey, TimeDiff);
ShowTime();
}
}
}
xmlhttp.send(null);
}
function ShowTime(){
alert(window.localStorage.getItem(TimeDiffKey));
}
使用代码(显示日期和时间)
我想在具有 "RealServerTime
" 类名的任何 DOM 元素中显示 Time
,所以我使用 document.getElementsByClassName
来查找所有节点
var SyncTimeframe = 1000 * 60 * 60 * 3; // 3 Hours
var LastSyncKey = 'LastSyncWithTimeServer';
var TimeDiffKey = 'Local-Server-TimeDiff';
var RetryMax = 3;
var RetryCount = 0;
var AcceptedDelay = 500;
if (window.localStorage.getItem(LastSyncKey) == null) {
window.localStorage.setItem(LastSyncKey, '' + (new Date(0)));
}
LastSync = new Date(window.localStorage.getItem(LastSyncKey));
if ( Math.abs((new Date()) - LastSync) > SyncTimeframe) {
SyncTime();
}
else {
ShowTime();
}
function SyncTime() {
var StartTime = new Date();
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
TimeDiff=new Date(xmlhttp.getResponseHeader("Date")) -
(new Date()) + ((new Date()) - StartTime) / 2;
if (++RetryCount < 3 && (new Date()) - StartTime > AcceptedDelay) {
SyncTime();
}
else {
window.localStorage.setItem(LastSyncKey, '' + (new Date()));
window.localStorage.setItem(TimeDiffKey, TimeDiff);
ShowTime();
}
}
}
xmlhttp.send(null);
}
function ShowTime(){
var AllNodes=document.getElementsByClassName("RealServerTime");
var diff = parseInt(window.localStorage.getItem(TimeDiffKey), 10);
// format Date and Time
var TimeToString=(new Date(Date.now() + diff)).toTimeString().split(' ')[0];
for(var ipos=0;ipos<AllNodes.length;ipos++){
AllNodes[ipos].innerHTML=TimeToString;
}
window.setTimeout(ShowTime, 1000);
}
最后一步是将所有代码包装在匿名函数中,以隐藏所有局部变量和函数
(function () {
var SyncTimeframe = 1000 * 60 * 60 * 3; // 3 Hours
var LastSyncKey = 'LastSyncWithTimeServer';
var TimeDiffKey = 'Local-Server-TimeDiff';
var RetryMax = 3;
var RetryCount = 0;
var AcceptedDelay = 500;
if (window.localStorage.getItem(LastSyncKey) == null) {
window.localStorage.setItem(LastSyncKey, '' + (new Date(0)));
}
LastSync = new Date(window.localStorage.getItem(LastSyncKey));
if ( Math.abs((new Date()) - LastSync) > SyncTimeframe) {
SyncTime();
}
else {
ShowTime();
}
function SyncTime() {
var StartTime = new Date();
xmlhttp = new XMLHttpRequest();
xmlhttp.open("HEAD", "http://www.googleapis.com",true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
TimeDiff=new Date(xmlhttp.getResponseHeader("Date")) -
(new Date()) + ((new Date()) - StartTime) / 2;
if (++RetryCount < 3 && (new Date()) - StartTime > AcceptedDelay) {
SyncTime();
}
else {
window.localStorage.setItem(LastSyncKey, '' + (new Date()));
window.localStorage.setItem(TimeDiffKey, TimeDiff);
ShowTime();
}
}
}
xmlhttp.send(null);
}
function ShowTime(){
var AllNodes=document.getElementsByClassName("RealServerTime");
var diff = parseInt(window.localStorage.getItem(TimeDiffKey), 10);
// format Date and Time
var TimeToString=(new Date(Date.now() + diff)).toTimeString().split(' ')[0];
for(var ipos=0;ipos<AllNodes.length;ipos++){
AllNodes[ipos].innerHTML=TimeToString;
}
window.setTimeout(ShowTime, 1000);
}
})();
关注点
这是我第一次很高兴看到 HTTP/1.1 404 Not Found 错误。
如果有人知道更好的解决方案来实现更准确的时间,我将很高兴听到。