Syncing code with the sun

setInterval() and requestAnimationFrame() both let you call some piece of Javascript on a regular interval. In both, the elapsed time is relative to when your script first starts executing. Let's look at how you can keep your code in sync with a wall clock instead.

Javascript

(function (exports, $) {
  var tickPeriodMs = Number(5 + "e3"); // sec/step, must be >= 1!
  var frameStart, frameEnd; // absolute (wall clock) frame start / end

  exports.setTickPeriod = function (periodInSeconds) {
    tickPeriodMs = Number(periodInSeconds + "e3");
  };
  function onFrame() {
    frameStart = Date.now();
    if (!frameEnd) frameEnd = Date.now();

    // does multiple exist in range [frameEnd, frameStart]?
    var nearest;
    var rem = frameEnd % tickPeriodMs;
    if (rem == 0) nearest = frameEnd;
    else nearest = frameEnd + tickPeriodMs - rem;

    var isTick = frameEnd <= nearest && nearest <= frameStart;

    if (isTick) {
      console.log(
        "Added " + Number(tickPeriodMs + "e-3") + "+ second(s) to local time"
      );
      $(window).trigger("tick"); // or similar
    }

    frameEnd = Date.now();
    requestAnimationFrame(onFrame);
  }
  requestAnimationFrame(onFrame);
})((window.Wallclock = {}), jQuery);

Include this code on your page outside of any onReady (or similar) callbacks. The first and last lines set up our wrapper object, window.Wallclock. A single function, Wallclock.setTickPeriod(), sets how often an event named tick is emitted from the window object.

For example, if you want to log a message at the start of each minute (each time your watch's second hand reaches 12):

Javascript

// after including the code above
Wallclock.setTickPeriod(60);
$(window).on("tick", function () {
  console.log("Hello! Current time := " + new Date());
});

Here's how it works. I drive a main loop via requestAnimationFrame(). Each time through, I record the Unix timestamps of the end of the last frame, and the start of the current frame.

The Unix timestamp of any wall clock event - like "at 12:30" or "15 minutes past the hour" - occurs at a multiple of the milliseconds in that interval. For instance, suppose the current (Unix) time is 1467844308776 (that's 22:31:48 GMT on July 6, 2016). You'll reach 22:35:00 at 1467844500000. That last number is a multiple of 300,000 (the number of milliseconds in 5 minutes).

Based on the value you provide to .setTickPeriod(), the code determines if a multiple of that period has occurred between frames. If it has, we emit a tick, knowing that the wall clock time has crossed over the boundary we're interested in.

A final note about accuracy: This code will potentially miss some tick's if your browser window is minimized, or the current tab changed. This is due to the battery-friendly nature of requestAnimationFrame, which browsers run at a lower rate (or not at all!) under those conditions. Consider a tick to mean, "at least N seconds has been added to the local time".

© 2021 David Mann. All content provided on this site, code or otherwise, is licensed and/or copyright as specified below: