65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 中的精确时间

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (13投票s)

2014年6月30日

CPOL

2分钟阅读

viewsIcon

28373

downloadIcon

224

从 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));
}

网络延迟是另一个问题,为了解决它,我在代码中添加了 AcceptedDelayRetryCount

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 错误。

如果有人知道更好的解决方案来实现更准确的时间,我将很高兴听到。

© . All rights reserved.