javascripttimestampunix-timestampepochnavigation-timing-api

Get Navigation Timing backward/forward compatible - Convert from epoch to HR time


Let's introduce by a note from www.w3.org including two important links to compare.

The PerformanceTiming interface was defined in [NAVIGATION-TIMING] and is now considered obsolete. The use of names from the PerformanceTiming interface is supported to remain backwards compatible, but there are no plans to extend this functionality to names in the PerformanceNavigationTiming interface defined in [NAVIGATION-TIMING-2] (or other interfaces) in the future.

I have made a function to get a Navigation Time that should be both backward and forward compatible, because we are in the middle era of transforming to level 2. So this function to get a time from an event name works in Chrome but not Firefox:

function nav(eventName) {
  var lev1 = performance.timing; //deprecated unix epoch time in ms since 1970
  var lev2 = performance.getEntriesByType("navigation")[0]; //ms since page started to load. (since performance.timing.navigationStart)
  var nav = lev2 || lev1; //if lev2 is undefined then use lev1
  return nav[eventName]
}

Explanation: When there is no "navigation" entry this falls back to the deprecated way to do navigation timing based on Unix epoch time time in milliseconds since 1970 (lev1), while the new way (lev2) is HR time in milliseconds since the current document navigation started to load, that is useful together with User Timing that always have had the HR time format.

How can we get the function return HR time in all cases?

When I see a number with more than 10 digits without a period I know it is a time got from the deprecated Navigation Timing level 1. All other test cases give decimal point numbers meaning it is HR times with higher precision. The biggest issue is that they have different time origin.

I have gone through confusion, trial errors and frustrated serching (MDN has not updated to level 2) to confirm and state that:

How to convert unix epoch time to HR time?


SOLVED .:

The code is corrected by help from Amadan.
See comments in tha accepted answer.

function nav(eventName, fallback) {
  var lev1 = performance.timing; //deprecated unix epoch time in ms since 1970
  var lev2 = performance.getEntriesByType("navigation")[0]; //ms since page started to load
  var nav = lev2 || lev1; //if lev2 is undefined then use lev1
  if (!nav[eventName] && fallback) eventName = fallback

  // approximate t microseconds it takes to execute performance.now()
  var i = 10000, t = performance.now()
  while(--i) performance.now()
  t = (performance.now() - t)/10000 // < 10 microseconds

  var oldTime = new Date().getTime(), 
      newTime = performance.now(),
      timeOrigin = performance.timeOrigin? 
                   performance.timeOrigin: 
                   oldTime - newTime - t; // approximate

  return nav[eventName] - (lev2? 0: timeOrigin);
  // return nav[eventName] - (lev2? 0: lev1.navigationStart); //alternative?
}

The performance.timeOrigin is reduced in the case where old timing lev1 is used.
If browser does not have it then approximate timeOrigin by reducing performance.now() the time since timeOrigin, from (new Date().getTime()) the time since Unix Epoch to result in the time to timeOrigin since Unix Epoch. Apparently it is the definition though the link was a bit vague about it. I confirmed by testing and I trust the answer. Hopefully w3c have a better definition of timeOrigin than: the high resolution timestamp of the start time of the performance measurement.

The functions returned value represents the time elapsed since the time origin.

It may be insignificant in most cases, but the measured time t it took to execute performance.now() is removed to approximate simultaneous execution.

I measured t to almost 10 microseconds on my Raspberry Pi that was fairly stable with various loop sizes. But my Lenovo was not as precise rounding off decimals and getting shorter times on t when tested bigger loop sizes.


An alternative solution is commented away in the last line of code.
The deprecated performance.timing.navigationStart:

representing the moment, in miliseconds since the UNIX epoch, right after the prompt for unload terminates on the previous document in the same browsing context. If there is no previous document, this value will be the same as PerformanceTiming.fetchStart

So, to check current document (ignoring any previous) then use the deprecated performance.timing.fetchStart:

representing the moment, in miliseconds since the UNIX epoch, the browser is ready to fetch the document using an HTTP request. This moment is before the check to any application cache.

It is of course correct to use a deprecated property if it is the only one the browser understand. It is used when "navigation" is not defined in the getEntriesByType otherwise having good browser support.


A quick check confirmed each other by this line just before return:

console.log(performance.timeOrigin + '\n' + lev1.navigationStart + '\n' + lev1.fetchStart)

With a result that looks like this in my Chrome

1560807558225.1611
1560807558225
1560807558241


Solution

  • It is only possible if the browser supports HR time 2:

    let unixTime = hrTime + performance.timeOrigin;
    
    let hrTime = unixTime - performance.timeOrigin;
    

    However, performance is generally used for time diffs, which do not care what the origin of absolute timestamps is.

    For the browsers that do not support HR time 2, or those that "support" it half-heartedly, you can fake it this way:

    const hrSyncPoint = performance.now();
    const unixSyncPoint = new Date().getTime();
    const timeOrigin = unixSyncPoint - hrSyncPoint;
    

    It's not super-exact, but should be good enough for most purposes (on my system, performance.timeOrigin - timeOrigin is sub-millisecond).