Display server time on client in Meteor

Using Meteor, what would be an efficient way to keep a running clock (h:m:s) on the client that displays the server's time?

The JavaScript/PHP answers I've found typically involve getting the server time periodically and calculating the difference between that and the client.

What would that look like with Meteor?

UPDATE: A lot has changed since I originally posted this question. If you're interested in a pre-built solution, I recommend taking a look at Meteor Timesync by @mizzao. Install it by running meteor add mizzao:timesync in your console.

Answers


David Greenspan gets the client time in this presentation on Spark around 14:30. I've modified this code slightly to get server side time:

Javascript:

if (Meteor.isClient) {
    Meteor.startup(function () {
        setInterval(function () {
            Meteor.call("getServerTime", function (error, result) {
                Session.set("time", result);
            });
        }, 1000);
    });

    Template.main.time = function () {
        return Session.get("time");
    };
}

if (Meteor.isServer) {
    Meteor.methods({
        getServerTime: function () {
            var _time = (new Date).toTimeString();
            console.log(_time);
            return _time;
        }
    });
}

And the HTML:

<body>
  {{> main}}
</body>

<template name="main">
  {{time}}
</template>

There is very good information on this thread. I've pulled everything together into a smart package for Meteor:

https://github.com/mizzao/meteor-timesync

There are two main things I added beyond what is already here:

  • A more accurate computation of the server/client offset using NTP-style math, as opposed to just diffing the client and server time and ignoring round trip time.
  • The ability to use the server timestamps reactively to display values that will update themselves in templates and reactive computations.

Feel free to open pull requests on this, especially if you have ideas for better/more efficient ways to compute and maintain the offset.


Thanks @TimDog for the help. I've expanded that code a bit to only check the server periodically while still having a running clock displayed on the client. This is what I ended up with:

Client code:

  Meteor.startup(function () {

    function setServerTime(){

      //get server time (it's in milliseconds)
      Meteor.call("getServerTime", function (error, result) {

        //get client time in milliseconds
        localTime = new Date().getTime();

        //difference between server and client
        var serverOffset = result - localTime;

        //store difference in the session
        Session.set("serverTimeOffset", serverOffset);

      });
    }

    function setDisplayTime(){
      var offset = Session.get("serverTimeOffset");
      var adjustedLocal = new Date().getTime() + offset;
      Session.set("serverTime", adjustedLocal);
    }

    //run these once on client start so we don't have to wait for setInterval
    setServerTime();
    setDisplayTime();

    //check server time every 15min
    setInterval(function updateServerTime() {
      setServerTime();
    }, 900000);

    //update clock on screen every second
    setInterval(function updateDisplayTime() {
      setDisplayTime();
    }, 1000);

  });

  //pass the clock to the HTML template
  Template.register.clock = function () {
    return new Date(Session.get("serverTime"));
  };

Server code:

Meteor.methods({

    //get server time in milliseconds
    getServerTime: function () {
        var _time = (new Date).getTime();
        console.log(_time);
        return _time;
    }

  });

As a followup on this question, is this a performance drain? We are calculating offset every second, and changing the Session based on this re-calculation.

Does this mean anything else dependent on Session would also get recalculated every second?

Might it be better to set into something else?

Or simply make a method which does the local date adjustments as needed, vs setting up the interval and session variable.

let me know what you've found.


Just subscribe to a normal collection and put your date in it: (no roundtrip calculations)

import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';

export const ServerDate = new Mongo.Collection(null);

if (Meteor.isServer) {
  // This code only runs on the server
  Meteor.publish('serverDate', function serverDatePublication() {
    return ServerDate.find({});
  });
  console.log('<ServerDate>:', new Date());
  Meteor.setInterval(updateDate, 1000 * 1);
}

function updateDate() {
  // Clean out result cache
  ServerDate.remove({});
  ServerDate.insert(new Date());
}

Need Your Help

Mercurial error: abort no username supplied

mercurial version-control

Problem on WindowsXP (likely will happen on all Win installs), first time using Mercurial. I found the answer in an inobvious place so I'm asking/answering the question myself so others don't have to

Is there a way to make Rails ActiveRecord attributes private?

ruby-on-rails activerecord private

By default, ActiveRecord takes all fields from the corresponding database table and creates public attributes for all of them.