Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Accurate Time in JavaScript

4.72/5 (13 votes)
30 Jun 2014CPOL2 min read 27.9K   188  
Getting the accurate time in JavaScript from Google server

Introduction

What if we want the accurate time when the client and server time are not accurate? The only workaround that I could find was json-time API. But json-time had a big Achilles Heel. That was json-time is hosted on Google and most of my clients couldn't call the API. 

Image 1

I couldn't find any alternative API and I lost hope, until I saw this comment in James Padolseys' blog:

Why not do a HTTP Head request to google.com to get the current time? I have used it in apps that need to check the correct time and never found different from ntp servers.

So I could find what I need in google.com header:

Image 2

I liked the idea and here is an effort to access the accurate time in JavaScript from Google server.

Writing the Code Step by Step

Retrieving data by XMLHttpRequest and access to the response HEADER seems very easy so I write the first version:

JavaScript
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);

But as a result, I get NULL instead of date! I found the problem after viewing browser console log:

MLHttpRequest cannot load http://www.google.com/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://fiddle.jshell.net' is therefore not allowed access.

So I need URL with CORS support. I searched really hard but I couldn't find any good URL after days of searching. I changed my strategy and searched for another solution to capture the date header. Finally, I found the solution. When I want to retrieve data from URL that does not exist, I recieve HTTP/1.1 404 Not Found error but this time I can read date header correctly. So I changed URL from http://www.google.com to http://www.googleapis.com:

JavaScript
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);​

Image 3

OK now I have what I want but it's not a good way to send a request to another server any time I need time. We can calculate local time and server time difference and store it in local variable:

JavaScript
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);

Or store it in localstorage:

JavaScript
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);

Storing TimeDiff in localstorage raises another problem. We need a mechanism to resync the difference in a timely manner:

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

Network delay is the other concern, and to take care of it, I add AcceptedDelay and RetryCount to code: 

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

Also, we can add half of network delay to time diff to handle network delay better:

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

Using the Code (Show the Date and Time) 

I want to show the Time in any DOM element with "RealServerTime" class, so I use document.getElementsByClassName to find all nodes:

JavaScript
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);

}

The last step is to wrap all code in anonymous function to hide all local variable and functiuons:

JavaScript
(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);
    }
})();

Points of Interest

This is the first time I enjoyed seeing HTTP/1.1 404 Not Found error.

If anyone knows a better solution to achieve a more accurate time, I will be happy to hear about it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)