Source: timing.js

/**
 * This module facilitates timestamping events, using a standardized clock.
 * When supported by the browser, WebScience uses the shared monotonic clock
 * specified by the W3C High Resolution Time recommendation. Otherwise,
 * WebScience uses the system clock.
 * 
 * ## Web Browser Clocks
 * There are two clocks supported in modern web browsers.
 *   * __System Clock__ (`Date.now()` or `new Date`). The system clock is
 *     the ordinary time provided by the operating system. Using the
 *     system clock to timestamp events poses a risk: the user or operating
 *     system can adjust the clock at any time, for any reason, without any
 *     notice, to any value. The user might manually adjust the clock, for
 *     example, or the operating system might synchronize the clock to account
 *     for clock skew (e.g., NTP time sync). These adjustments can be large and
 *     non-monotonic, breaking assumptions that WebScience makes about timestamp
 *     proximity and ordering. A clock change during study execution could
 *     introduce subtle bugs or other unexpected behavior.
 *   * __Shared Monotonic Clock__
 *    (`performance.timeOrigin + performance.now()`). The W3C High Resolution
 *    Time recommendation specifies a shared monotonic clock. This clock
 *    should have the following properties:
 *      * strictly monotonic;
 *      * not subject to large or non-monotonic adjustments from any source;
 *      * consistent across cores, processes, threads, and globals down to the
 *        hardware level; and
 *      * synchronized to the system clock just once, on browser startup.
 *      
 * Our goal is to migrate WebScience and Rally studies to the shared monotonic
 * clock, because it does not have clock change risks like the system clock.
 * Unfortunately, browser implementations of High Resolution Time currently
 * depart from the W3C recommendation in significant ways that prevent reliance
 * on the shared monotonic clock. We will update this module as browsers correct
 * their implementations.
 * 
 * ## Additional Notes
 *   * The High Resolution Time spec describes a shared monotonic clock (which
 *     must be used to generate `performance.timeOrigin` for each global) and
 *     per-global monotonic clocks (which tick for  `performance.now()` and other
 *     uses of `DOMHighResTimeStamp`). Monotonic clocks on modern hardware are
 *     synchronized across cores, processes, and threads, so we treat
 *     `performance.timeOrigin + performance.now()` as the current time on the
 *     shared monotonic clock, even though the W3C spec doesn't _quite_ say that.
 *   * Firefox and Chrome currently depart from the High Resolution Time
 *     spec in significant ways: `performance.timeOrigin` is sometimes set from
 *     the system clock rather than the shared monotonic clock, and
 *     `performance.now()` (and other uses of `DOMHighResTimeStamp`) do not
 *     tick during system sleep on certain platforms.
 *  
 * @see {@link https://www.w3.org/TR/hr-time-2/}
 * @see {@link https://github.com/mdn/content/issues/4713}
 * @see {@link https://github.com/w3c/hr-time/issues/65}
 * @module timing
 */

/**
 * Get whether the browser supports the High Resolution Time shared
 * monotonic clock. Currently always returns `false`. We will update
 * this function as browser support improves.
 * @returns {boolean} Whether the browser supports the shared monotonic
 * clock.
 * @private
 */
function sharedMonotonicClockSupport() {
    return false;
}

/**
 * Get the current time, in milliseconds since the epoch, using a
 * standardized clock.
 * @returns {number} The current time, in milliseconds since the epoch.
 */
export function now() {
    if(sharedMonotonicClockSupport()) {
        return window.performance.timeOrigin + window.performance.now();
    }
    return Date.now();
}

/**
 * Convert a timestamp on the system clock to a timestamp on the
 * standardized clock. Use this function only where strictly necessary,
 * and where it can be used immediately after the timestamp on the
 * system clock. There is a risk that the system clock will have
 * changed between the timestamp and now.
 * @param {number} timeStamp - A timestamp, in milliseconds since the
 * epoch, on the system clock.
 * @returns {number} A timestamp, in milliseconds since the epoch, on
 * the standardized clock.
 * @example
 * const systemTimeStamp = Date.now();
 * const standardizedTimeStamp = webScience.timing.fromSystemClock(systemTimeStamp);
 */
export function fromSystemClock(timeStamp) {
    if(sharedMonotonicClockSupport()) {
        return timeStamp - Date.now() + window.performance.timeOrigin + window.performance.now();
    }
    return timeStamp;
}

/**
 * Convert a timestamp on the shared monotonic clock to a timestamp
 * on the standardized clock. Use this function only where strictly
 * necessary, and where it can be used immediately after the timestamp
 * on the monotonic clock. There is a risk that the system clock will
 * have changed between the timestamp and now or that the monotonic
 * clock was affected by an implementation bug.
 * @param {number} timeStamp - A timestamp, in milliseconds since the
 * epoch, on the shared monotonic clock.
 * @param {boolean} relativeToTimeOrigin - Whether the timestamp
 * is relative to a time origin (e.g., a DOM event or Performance API
 * timestamp), or the time origin has already been added to the
 * timestamp (e.g., `performance.timeOrigin` or
 * `performance.timeOrigin + performance.now()`).
 * @returns {number} A timestamp, in milliseconds since the epoch, on
 * the standardized clock.
 * @example
 * const monotonicTimeStamp = performance.timeOrigin;
 * const standardizedTimeStamp = webScience.timing.fromMonotonicClock(monotonicTimeStamp, false);
 * @example
 * const monotonicTimeStamp = performance.timeOrigin + performance.now();
 * const standardizedTimeStamp = webScience.timing.fromMonotonicClock(monotonicTimeStamp, false);
 * @example
 * const relativeMonotonicTimeStamp = performance.now();
 * const standardizedTimeStamp = webScience.timing.fromMonotonicClock(relativeMonotonicTimeStamp, true);
 */
export function fromMonotonicClock(timeStamp, relativeToTimeOrigin) {
    if(sharedMonotonicClockSupport()) {
        if(relativeToTimeOrigin) {
            return window.performance.timeOrigin + timeStamp;
        }
        return timeStamp;
    }
    if(relativeToTimeOrigin) {
        return timeStamp - window.performance.now() + Date.now();
    }
    return timeStamp - window.performance.now() - window.performance.timeOrigin + Date.now();
}