function withGlobal(_global) {
			const userAgent = _global.navigator && _global.navigator.userAgent;
			const isRunningInIE = userAgent && userAgent.indexOf("MSIE ") > -1;
			const maxTimeout = Math.pow(2, 31) - 1; //see https://heycam.github.io/webidl/#abstract-opdef-converttoint
			const idCounterStart = 1e12; // arbitrarily large number to avoid collisions with native timer IDs
			const NOOP = function () {
				return undefined;
			};
			const NOOP_ARRAY = function () {
				return [];
			};
			const timeoutResult = _global.setTimeout(NOOP, 0);
			const addTimerReturnsObject = typeof timeoutResult === "object";
			const hrtimePresent =
				_global.process && typeof _global.process.hrtime === "function";
			const hrtimeBigintPresent =
				hrtimePresent && typeof _global.process.hrtime.bigint === "function";
			const nextTickPresent =
				_global.process && typeof _global.process.nextTick === "function";
			const utilPromisify = _global.process && require("util").promisify;
			const performancePresent =
				_global.performance && typeof _global.performance.now === "function";
			const hasPerformancePrototype =
				_global.Performance &&
				(typeof _global.Performance).match(/^(function|object)$/);
			const hasPerformanceConstructorPrototype =
				_global.performance &&
				_global.performance.constructor &&
				_global.performance.constructor.prototype;
			const queueMicrotaskPresent = _global.hasOwnProperty("queueMicrotask");
			const requestAnimationFramePresent =
				_global.requestAnimationFrame &&
				typeof _global.requestAnimationFrame === "function";
			const cancelAnimationFramePresent =
				_global.cancelAnimationFrame &&
				typeof _global.cancelAnimationFrame === "function";
			const requestIdleCallbackPresent =
				_global.requestIdleCallback &&
				typeof _global.requestIdleCallback === "function";
			const cancelIdleCallbackPresent =
				_global.cancelIdleCallback &&
				typeof _global.cancelIdleCallback === "function";
			const setImmediatePresent =
				_global.setImmediate && typeof _global.setImmediate === "function";

			// Make properties writable in IE, as per
			// https://www.adequatelygood.com/Replacing-setTimeout-Globally.html
			/* eslint-disable no-self-assign */
			if (isRunningInIE) {
				_global.setTimeout = _global.setTimeout;
				_global.clearTimeout = _global.clearTimeout;
				_global.setInterval = _global.setInterval;
				_global.clearInterval = _global.clearInterval;
				_global.Date = _global.Date;
			}

			// setImmediate is not a standard function
			// avoid adding the prop to the window object if not present
			if (setImmediatePresent) {
				_global.setImmediate = _global.setImmediate;
				_global.clearImmediate = _global.clearImmediate;
			}
			/* eslint-enable no-self-assign */

			_global.clearTimeout(timeoutResult);

			const NativeDate = _global.Date;
			let uniqueTimerId = idCounterStart;

			/**
			 * @param {number} num
			 * @returns {boolean}
			 */
			function isNumberFinite(num) {
				if (Number.isFinite) {
					return Number.isFinite(num);
				}

				return isFinite(num);
			}

			let isNearInfiniteLimit = false;

			/**
			 * @param {Clock} clock
			 * @param {number} i
			 */
			function checkIsNearInfiniteLimit(clock, i) {
				if (clock.loopLimit && i === clock.loopLimit - 1) {
					isNearInfiniteLimit = true;
				}
			}

			/**
			 *
			 */
			function resetIsNearInfiniteLimit() {
				isNearInfiniteLimit = false;
			}

			/**
			 * Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into
			 * number of milliseconds. This is used to support human-readable strings passed
			 * to clock.tick()
			 *
			 * @param {string} str
			 * @returns {number}
			 */
			function parseTime(str) {
				if (!str) {
					return 0;
				}

				const strings = str.split(":");
				const l = strings.length;
				let i = l;
				let ms = 0;
				let parsed;

				if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
					throw new Error(
						"tick only understands numbers, 'm:s' and 'h:m:s'. Each part must be two digits"
					);
				}

				while (i--) {
					parsed = parseInt(strings[i], 10);

					if (parsed >= 60) {
						throw new Error(`Invalid time ${str}`);
					}

					ms += parsed * Math.pow(60, l - i - 1);
				}

				return ms * 1000;
			}

			/**
			 * Get the decimal part of the millisecond value as nanoseconds
			 *
			 * @param {number} msFloat the number of milliseconds
			 * @returns {number} an integer number of nanoseconds in the range [0,1e6)
			 *
			 * Example: nanoRemainer(123.456789) -> 456789
			 */
			function nanoRemainder(msFloat) {
				const modulo = 1e6;
				const remainder = (msFloat * 1e6) % modulo;
				const positiveRemainder =
					remainder < 0 ? remainder + modulo : remainder;

				return Math.floor(positiveRemainder);
			}

			/**
			 * Used to grok the `now` parameter to createClock.
			 *
			 * @param {Date|number} epoch the system time
			 * @returns {number}
			 */
			function getEpoch(epoch) {
				if (!epoch) {
					return 0;
				}
				if (typeof epoch.getTime === "function") {
					return epoch.getTime();
				}
				if (typeof epoch === "number") {
					return epoch;
				}
				throw new TypeError("now should be milliseconds since UNIX epoch");
			}

			/**
			 * @param {number} from
			 * @param {number} to
			 * @param {Timer} timer
			 * @returns {boolean}
			 */
			function inRange(from, to, timer) {
				return timer && timer.callAt >= from && timer.callAt <= to;
			}

			/**
			 * @param {Clock} clock
			 * @param {Timer} job
			 */
			function getInfiniteLoopError(clock, job) {
				const infiniteLoopError = new Error(
					`Aborting after running ${clock.loopLimit} timers, assuming an infinite loop!`
				);

				if (!job.error) {
					return infiniteLoopError;
				}

				// pattern never matched in Node
				const computedTargetPattern = /target\.*[<|(|[].*?[>|\]|)]\s*/;
				let clockMethodPattern = new RegExp(
					String(Object.keys(clock).join("|"))
				);

				if (addTimerReturnsObject) {
					// node.js environment
					clockMethodPattern = new RegExp(
						`\\s+at (Object\\.)?(?:${Object.keys(clock).join("|")})\\s+`
					);
				}

				let matchedLineIndex = -1;
				job.error.stack.split("\n").some(function (line, i) {
					// If we've matched a computed target line (e.g. setTimeout) then we
					// don't need to look any further. Return true to stop iterating.
					const matchedComputedTarget = line.match(computedTargetPattern);
					/* istanbul ignore if */
					if (matchedComputedTarget) {
						matchedLineIndex = i;
						return true;
					}

					// If we've matched a clock method line, then there may still be
					// others further down the trace. Return false to keep iterating.
					const matchedClockMethod = line.match(clockMethodPattern);
					if (matchedClockMethod) {
						matchedLineIndex = i;
						return false;
					}

					// If we haven't matched anything on this line, but we matched
					// previously and set the matched line index, then we can stop.
					// If we haven't matched previously, then we should keep iterating.
					return matchedLineIndex >= 0;
				});

				const stack = `${infiniteLoopError}\n${job.type || "Microtask"} - ${
					job.func.name || "anonymous"
				}\n${job.error.stack
					.split("\n")
					.slice(matchedLineIndex + 1)
					.join("\n")}`;

				try {
					Object.defineProperty(infiniteLoopError, "stack", {
						value: stack,
					});
				} catch (e) {
					// noop
				}

				return infiniteLoopError;
			}

			/**
			 * @param {Date} target
			 * @param {Date} source
			 * @returns {Date} the target after modifications
			 */
			function mirrorDateProperties(target, source) {
				let prop;
				for (prop in source) {
					if (source.hasOwnProperty(prop)) {
						target[prop] = source[prop];
					}
				}

				// set special now implementation
				if (source.now) {
					target.now = function now() {
						return target.clock.now;
					};
				} else {
					delete target.now;
				}

				// set special toSource implementation
				if (source.toSource) {
					target.toSource = function toSource() {
						return source.toSource();
					};
				} else {
					delete target.toSource;
				}

				// set special toString implementation
				target.toString = function toString() {
					return source.toString();
				};

				target.prototype = source.prototype;
				target.parse = source.parse;
				target.UTC = source.UTC;
				target.prototype.toUTCString = source.prototype.toUTCString;
				target.isFake = true;

				return target;
			}

			//eslint-disable-next-line jsdoc/require-jsdoc
			function createDate() {
				/**
				 * @param {number} year
				 * @param {number} month
				 * @param {number} date
				 * @param {number} hour
				 * @param {number} minute
				 * @param {number} second
				 * @param {number} ms
				 *
				 * @returns {Date}
				 */
				function ClockDate(year, month, date, hour, minute, second, ms) {
					// the Date constructor called as a function, ref Ecma-262 Edition 5.1, section 15.9.2.
					// This remains so in the 10th edition of 2019 as well.
					if (!(this instanceof ClockDate)) {
						return new NativeDate(ClockDate.clock.now).toString();
					}

					// if Date is called as a constructor with 'new' keyword
					// Defensive and verbose to avoid potential harm in passing
					// explicit undefined when user does not pass argument
					switch (arguments.length) {
						case 0:
							return new NativeDate(ClockDate.clock.now);
						case 1:
							return new NativeDate(year);
						case 2:
							return new NativeDate(year, month);
						case 3:
							return new NativeDate(year, month, date);
						case 4:
							return new NativeDate(year, month, date, hour);
						case 5:
							return new NativeDate(year, month, date, hour, minute);
						case 6:
							return new NativeDate(
								year,
								month,
								date,
								hour,
								minute,
								second
							);
						default:
							return new NativeDate(
								year,
								month,
								date,
								hour,
								minute,
								second,
								ms
							);
					}
				}

				return mirrorDateProperties(ClockDate, NativeDate);
			}

			//eslint-disable-next-line jsdoc/require-jsdoc
			function enqueueJob(clock, job) {
				// enqueues a microtick-deferred task - ecma262/#sec-enqueuejob
				if (!clock.jobs) {
					clock.jobs = [];
				}
				clock.jobs.push(job);
			}

			//eslint-disable-next-line jsdoc/require-jsdoc
			function runJobs(clock) {
				// runs all microtick-deferred tasks - ecma262/#sec-runjobs
				if (!clock.jobs) {
					return;
				}
				for (let i = 0; i < clock.jobs.length; i++) {
					const job = clock.jobs[i];
					job.func.apply(null, job.args);

					checkIsNearInfiniteLimit(clock, i);
					if (clock.loopLimit && i > clock.loopLimit) {
						throw getInfiniteLoopError(clock, job);
					}
				}
				resetIsNearInfiniteLimit();
				clock.jobs = [];
			}

			/**
			 * @param {Clock} clock
			 * @param {Timer} timer
			 * @returns {number} id of the created timer
			 */
			function addTimer(clock, timer) {
				if (timer.func === undefined) {
					throw new Error("Callback must be provided to timer calls");
				}

				if (addTimerReturnsObject) {
					// Node.js environment
					if (typeof timer.func !== "function") {
						throw new TypeError(
							`[ERR_INVALID_CALLBACK]: Callback must be a function. Received ${
								timer.func
							} of type ${typeof timer.func}`
						);
					}
				}

				if (isNearInfiniteLimit) {
					timer.error = new Error();
				}

				timer.type = timer.immediate ? "Immediate" : "Timeout";

				if (timer.hasOwnProperty("delay")) {
					if (typeof timer.delay !== "number") {
						timer.delay = parseInt(timer.delay, 10);
					}

					if (!isNumberFinite(timer.delay)) {
						timer.delay = 0;
					}
					timer.delay = timer.delay > maxTimeout ? 1 : timer.delay;
					timer.delay = Math.max(0, timer.delay);
				}

				if (timer.hasOwnProperty("interval")) {
					timer.type = "Interval";
					timer.interval = timer.interval > maxTimeout ? 1 : timer.interval;
				}

				if (timer.hasOwnProperty("animation")) {
					timer.type = "AnimationFrame";
					timer.animation = true;
				}

				if (timer.hasOwnProperty("idleCallback")) {
					timer.type = "IdleCallback";
					timer.idleCallback = true;
				}

				if (!clock.timers) {
					clock.timers = {};
				}

				timer.id = uniqueTimerId++;
				timer.createdAt = clock.now;
				timer.callAt =
					clock.now + (parseInt(timer.delay) || (clock.duringTick ? 1 : 0));

				clock.timers[timer.id] = timer;

				if (addTimerReturnsObject) {
					const res = {
						refed: true,
						ref: function () {
							this.refed = true;
							return res;
						},
						unref: function () {
							this.refed = false;
							return res;
						},
						hasRef: function () {
							return this.refed;
						},
						refresh: function () {
							timer.callAt =
								clock.now +
								(parseInt(timer.delay) || (clock.duringTick ? 1 : 0));

							// it _might_ have been removed, but if not the assignment is perfectly fine
							clock.timers[timer.id] = timer;

							return res;
						},
						[Symbol.toPrimitive]: function () {
							return timer.id;
						},
					};
					return res;
				}

				return timer.id;
			}

			/* eslint consistent-return: "off" */
			/**
			 * Timer comparitor
			 *
			 * @param {Timer} a
			 * @param {Timer} b
			 * @returns {number}
			 */
			function compareTimers(a, b) {
				// Sort first by absolute timing
				if (a.callAt < b.callAt) {
					return -1;
				}
				if (a.callAt > b.callAt) {
					return 1;
				}

				// Sort next by immediate, immediate timers take precedence
				if (a.immediate && !b.immediate) {
					return -1;
				}
				if (!a.immediate && b.immediate) {
					return 1;
				}

				// Sort next by creation time, earlier-created timers take precedence
				if (a.createdAt < b.createdAt) {
					return -1;
				}
				if (a.createdAt > b.createdAt) {
					return 1;
				}

				// Sort next by id, lower-id timers take precedence
				if (a.id < b.id) {
					return -1;
				}
				if (a.id > b.id) {
					return 1;
				}

				// As timer ids are unique, no fallback `0` is necessary
			}

			/**
			 * @param {Clock} clock
			 * @param {number} from
			 * @param {number} to
			 *
			 * @returns {Timer}
			 */
			function firstTimerInRange(clock, from, to) {
				const timers = clock.timers;
				let timer = null;
				let id, isInRange;

				for (id in timers) {
					if (timers.hasOwnProperty(id)) {
						isInRange = inRange(from, to, timers[id]);

						if (
							isInRange &&
							(!timer || compareTimers(timer, timers[id]) === 1)
						) {
							timer = timers[id];
						}
					}
				}

				return timer;
			}

			/**
			 * @param {Clock} clock
			 * @returns {Timer}
			 */
			function firstTimer(clock) {
				const timers = clock.timers;
				let timer = null;
				let id;

				for (id in timers) {
					if (timers.hasOwnProperty(id)) {
						if (!timer || compareTimers(timer, timers[id]) === 1) {
							timer = timers[id];
						}
					}
				}

				return timer;
			}

			/**
			 * @param {Clock} clock
			 * @returns {Timer}
			 */
			function lastTimer(clock) {
				const timers = clock.timers;
				let timer = null;
				let id;

				for (id in timers) {
					if (timers.hasOwnProperty(id)) {
						if (!timer || compareTimers(timer, timers[id]) === -1) {
							timer = timers[id];
						}
					}
				}

				return timer;
			}

			/**
			 * @param {Clock} clock
			 * @param {Timer} timer
			 */
			function callTimer(clock, timer) {
				if (typeof timer.interval === "number") {
					clock.timers[timer.id].callAt += timer.interval;
				} else {
					delete clock.timers[timer.id];
				}

				if (typeof timer.func === "function") {
					timer.func.apply(null, timer.args);
				} else {
					/* eslint no-eval: "off" */
					const eval2 = eval;
					(function () {
						eval2(timer.func);
					})();
				}
			}

			/**
			 * Gets clear handler name for a given timer type
			 *
			 * @param {string} ttype
			 */
			function getClearHandler(ttype) {
				if (ttype === "IdleCallback" || ttype === "AnimationFrame") {
					return `cancel${ttype}`;
				}
				return `clear${ttype}`;
			}

			/**
			 * Gets schedule handler name for a given timer type
			 *
			 * @param {string} ttype
			 */
			function getScheduleHandler(ttype) {
				if (ttype === "IdleCallback" || ttype === "AnimationFrame") {
					return `request${ttype}`;
				}
				return `set${ttype}`;
			}

			/**
			 * Creates an anonymous function to warn only once
			 */
			function createWarnOnce() {
				let calls = 0;
				return function (msg) {
					// eslint-disable-next-line
					!calls++ && console.warn(msg);
				};
			}
			const warnOnce = createWarnOnce();

			/**
			 * @param {Clock} clock
			 * @param {number} timerId
			 * @param {string} ttype
			 */
			function clearTimer(clock, timerId, ttype) {
				if (!timerId) {
					// null appears to be allowed in most browsers, and appears to be
					// relied upon by some libraries, like Bootstrap carousel
					return;
				}

				if (!clock.timers) {
					clock.timers = {};
				}

				// in Node, the ID is stored as the primitive value for `Timeout` objects
				// for `Immediate` objects, no ID exists, so it gets coerced to NaN
				const id = Number(timerId);

				if (Number.isNaN(id) || id < idCounterStart) {
					const handlerName = getClearHandler(ttype);

					if (clock.shouldClearNativeTimers === true) {
						const nativeHandler = clock[`_${handlerName}`];
						return typeof nativeHandler === "function"
							? nativeHandler(timerId)
							: undefined;
					}
					warnOnce(
						`FakeTimers: ${handlerName} was invoked to clear a native timer instead of one created by this library.` +
						"\nTo automatically clean-up native timers, use `shouldClearNativeTimers`."
					);
				}

				if (clock.timers.hasOwnProperty(id)) {
					// check that the ID matches a timer of the correct type
					const timer = clock.timers[id];
					if (
						timer.type === ttype ||
						(timer.type === "Timeout" && ttype === "Interval") ||
						(timer.type === "Interval" && ttype === "Timeout")
					) {
						delete clock.timers[id];
					} else {
						const clear = getClearHandler(ttype);
						const schedule = getScheduleHandler(timer.type);
						throw new Error(
							`Cannot clear timer: timer created with ${schedule}() but cleared with ${clear}()`
						);
					}
				}
			}

			/**
			 * @param {Clock} clock
			 * @param {Config} config
			 * @returns {Timer[]}
			 */
			function uninstall(clock, config) {
				let method, i, l;
				const installedHrTime = "_hrtime";
				const installedNextTick = "_nextTick";

				for (i = 0, l = clock.methods.length; i < l; i++) {
					method = clock.methods[i];
					if (method === "hrtime" && _global.process) {
						_global.process.hrtime = clock[installedHrTime];
					} else if (method === "nextTick" && _global.process) {
						_global.process.nextTick = clock[installedNextTick];
					} else if (method === "performance") {
						const originalPerfDescriptor = Object.getOwnPropertyDescriptor(
							clock,
							`_${method}`
						);
						if (
							originalPerfDescriptor &&
							originalPerfDescriptor.get &&
							!originalPerfDescriptor.set
						) {
							Object.defineProperty(
								_global,
								method,
								originalPerfDescriptor
							);
						} else if (originalPerfDescriptor.configurable) {
							_global[method] = clock[`_${method}`];
						}
					} else {
						if (_global[method] && _global[method].hadOwnProperty) {
							_global[method] = clock[`_${method}`];
						} else {
							try {
								delete _global[method];
							} catch (ignore) {
								/* eslint no-empty: "off" */
							}
						}
					}
				}

				if (config.shouldAdvanceTime === true) {
					_global.clearInterval(clock.attachedInterval);
				}

				// Prevent multiple executions which will completely remove these props
				clock.methods = [];

				// return pending timers, to enable checking what timers remained on uninstall
				if (!clock.timers) {
					return [];
				}
				return Object.keys(clock.timers).map(function mapper(key) {
					return clock.timers[key];
				});
			}

			/**
			 * @param {object} target the target containing the method to replace
			 * @param {string} method the keyname of the method on the target
			 * @param {Clock} clock
			 */
			function hijackMethod(target, method, clock) {
				clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(
					target,
					method
				);
				clock[`_${method}`] = target[method];

				if (method === "Date") {
					const date = mirrorDateProperties(clock[method], target[method]);
					target[method] = date;
				} else if (method === "performance") {
					const originalPerfDescriptor = Object.getOwnPropertyDescriptor(
						target,
						method
					);
					// JSDOM has a read only performance field so we have to save/copy it differently
					if (
						originalPerfDescriptor &&
						originalPerfDescriptor.get &&
						!originalPerfDescriptor.set
					) {
						Object.defineProperty(
							clock,
							`_${method}`,
							originalPerfDescriptor
						);

						const perfDescriptor = Object.getOwnPropertyDescriptor(
							clock,
							method
						);
						Object.defineProperty(target, method, perfDescriptor);
					} else {
						target[method] = clock[method];
					}
				} else {
					target[method] = function () {
						return clock[method].apply(clock, arguments);
					};

					Object.defineProperties(
						target[method],
						Object.getOwnPropertyDescriptors(clock[method])
					);
				}

				target[method].clock = clock;
			}

			/**
			 * @param {Clock} clock
			 * @param {number} advanceTimeDelta
			 */
			function doIntervalTick(clock, advanceTimeDelta) {
				clock.tick(advanceTimeDelta);
			}

			/**
			 * @typedef {object} Timers
			 * @property {setTimeout} setTimeout
			 * @property {clearTimeout} clearTimeout
			 * @property {setInterval} setInterval
			 * @property {clearInterval} clearInterval
			 * @property {Date} Date
			 * @property {SetImmediate=} setImmediate
			 * @property {function(NodeImmediate): void=} clearImmediate
			 * @property {function(number[]):number[]=} hrtime
			 * @property {NextTick=} nextTick
			 * @property {Performance=} performance
			 * @property {RequestAnimationFrame=} requestAnimationFrame
			 * @property {boolean=} queueMicrotask
			 * @property {function(number): void=} cancelAnimationFrame
			 * @property {RequestIdleCallback=} requestIdleCallback
			 * @property {function(number): void=} cancelIdleCallback
			 */

			/** @type {Timers} */
			const timers = {
				setTimeout: _global.setTimeout,
				clearTimeout: _global.clearTimeout,
				setInterval: _global.setInterval,
				clearInterval: _global.clearInterval,
				Date: _global.Date,
			};

			if (setImmediatePresent) {
				timers.setImmediate = _global.setImmediate;
				timers.clearImmediate = _global.clearImmediate;
			}

			if (hrtimePresent) {
				timers.hrtime = _global.process.hrtime;
			}

			if (nextTickPresent) {
				timers.nextTick = _global.process.nextTick;
			}

			if (performancePresent) {
				timers.performance = _global.performance;
			}

			if (requestAnimationFramePresent) {
				timers.requestAnimationFrame = _global.requestAnimationFrame;
			}

			if (queueMicrotaskPresent) {
				timers.queueMicrotask = true;
			}

			if (cancelAnimationFramePresent) {
				timers.cancelAnimationFrame = _global.cancelAnimationFrame;
			}

			if (requestIdleCallbackPresent) {
				timers.requestIdleCallback = _global.requestIdleCallback;
			}

			if (cancelIdleCallbackPresent) {
				timers.cancelIdleCallback = _global.cancelIdleCallback;
			}

			const originalSetTimeout = _global.setImmediate || _global.setTimeout;

			/**
			 * @param {Date|number} [start] the system time - non-integer values are floored
			 * @param {number} [loopLimit] maximum number of timers that will be run when calling runAll()
			 * @returns {Clock}
			 */
			function createClock(start, loopLimit) {
				// eslint-disable-next-line no-param-reassign
				start = Math.floor(getEpoch(start));
				// eslint-disable-next-line no-param-reassign
				loopLimit = loopLimit || 1000;
				let nanos = 0;
				const adjustedSystemTime = [0, 0]; // [millis, nanoremainder]

				if (NativeDate === undefined) {
					throw new Error(
						"The global scope doesn't have a `Date` object" +
						" (see https://github.com/sinonjs/sinon/issues/1852#issuecomment-419622780)"
					);
				}

				const clock = {
					now: start,
					Date: createDate(),
					loopLimit: loopLimit,
				};

				clock.Date.clock = clock;

				//eslint-disable-next-line jsdoc/require-jsdoc
				function getTimeToNextFrame() {
					return 16 - ((clock.now - start) % 16);
				}

				//eslint-disable-next-line jsdoc/require-jsdoc
				function hrtime(prev) {
					const millisSinceStart = clock.now - adjustedSystemTime[0] - start;
					const secsSinceStart = Math.floor(millisSinceStart / 1000);
					const remainderInNanos =
						(millisSinceStart - secsSinceStart * 1e3) * 1e6 +
						nanos -
						adjustedSystemTime[1];

					if (Array.isArray(prev)) {
						if (prev[1] > 1e9) {
							throw new TypeError(
								"Number of nanoseconds can't exceed a billion"
							);
						}

						const oldSecs = prev[0];
						let nanoDiff = remainderInNanos - prev[1];
						let secDiff = secsSinceStart - oldSecs;

						if (nanoDiff < 0) {
							nanoDiff += 1e9;
							secDiff -= 1;
						}

						return [secDiff, nanoDiff];
					}
					return [secsSinceStart, remainderInNanos];
				}

				if (hrtimeBigintPresent) {
					hrtime.bigint = function () {
						const parts = hrtime();
						return BigInt(parts[0]) * BigInt(1e9) + BigInt(parts[1]); // eslint-disable-line
					};
				}

				clock.requestIdleCallback = function requestIdleCallback(
					func,
					timeout
				) {
					let timeToNextIdlePeriod = 0;

					if (clock.countTimers() > 0) {
						timeToNextIdlePeriod = 50; // const for now
					}

					const result = addTimer(clock, {
						func: func,
						args: Array.prototype.slice.call(arguments, 2),
						delay:
							typeof timeout === "undefined"
								? timeToNextIdlePeriod
								: Math.min(timeout, timeToNextIdlePeriod),
						idleCallback: true,
					});

					return Number(result);
				};

				clock.cancelIdleCallback = function cancelIdleCallback(timerId) {
					return clearTimer(clock, timerId, "IdleCallback");
				};

				clock.setTimeout = function setTimeout(func, timeout) {
					return addTimer(clock, {
						func: func,
						args: Array.prototype.slice.call(arguments, 2),
						delay: timeout,
					});
				};
				if (typeof _global.Promise !== "undefined" && utilPromisify) {
					clock.setTimeout[
						utilPromisify.custom
						] = function promisifiedSetTimeout(timeout, arg) {
						return new _global.Promise(function setTimeoutExecutor(
							resolve
						) {
							addTimer(clock, {
								func: resolve,
								args: [arg],
								delay: timeout,
							});
						});
					};
				}

				clock.clearTimeout = function clearTimeout(timerId) {
					return clearTimer(clock, timerId, "Timeout");
				};

				clock.nextTick = function nextTick(func) {
					return enqueueJob(clock, {
						func: func,
						args: Array.prototype.slice.call(arguments, 1),
						error: isNearInfiniteLimit ? new Error() : null,
					});
				};

				clock.queueMicrotask = function queueMicrotask(func) {
					return clock.nextTick(func); // explicitly drop additional arguments
				};

				clock.setInterval = function setInterval(func, timeout) {
					// eslint-disable-next-line no-param-reassign
					timeout = parseInt(timeout, 10);
					return addTimer(clock, {
						func: func,
						args: Array.prototype.slice.call(arguments, 2),
						delay: timeout,
						interval: timeout,
					});
				};

				clock.clearInterval = function clearInterval(timerId) {
					return clearTimer(clock, timerId, "Interval");
				};

				if (setImmediatePresent) {
					clock.setImmediate = function setImmediate(func) {
						return addTimer(clock, {
							func: func,
							args: Array.prototype.slice.call(arguments, 1),
							immediate: true,
						});
					};

					if (typeof _global.Promise !== "undefined" && utilPromisify) {
						clock.setImmediate[
							utilPromisify.custom
							] = function promisifiedSetImmediate(arg) {
							return new _global.Promise(function setImmediateExecutor(
								resolve
							) {
								addTimer(clock, {
									func: resolve,
									args: [arg],
									immediate: true,
								});
							});
						};
					}

					clock.clearImmediate = function clearImmediate(timerId) {
						return clearTimer(clock, timerId, "Immediate");
					};
				}

				clock.countTimers = function countTimers() {
					return (
						Object.keys(clock.timers || {}).length +
						(clock.jobs || []).length
					);
				};

				clock.requestAnimationFrame = function requestAnimationFrame(func) {
					const result = addTimer(clock, {
						func: func,
						delay: getTimeToNextFrame(),
						args: [clock.now + getTimeToNextFrame()],
						animation: true,
					});

					return Number(result);
				};

				clock.cancelAnimationFrame = function cancelAnimationFrame(timerId) {
					return clearTimer(clock, timerId, "AnimationFrame");
				};

				clock.runMicrotasks = function runMicrotasks() {
					runJobs(clock);
				};

				/**
				 * @param {number|string} tickValue milliseconds or a string parseable by parseTime
				 * @param {boolean} isAsync
				 * @param {Function} resolve
				 * @param {Function} reject
				 * @returns {number|undefined} will return the new `now` value or nothing for async
				 */
				function doTick(tickValue, isAsync, resolve, reject) {
					const msFloat =
						typeof tickValue === "number"
							? tickValue
							: parseTime(tickValue);
					const ms = Math.floor(msFloat);
					const remainder = nanoRemainder(msFloat);
					let nanosTotal = nanos + remainder;
					let tickTo = clock.now + ms;

					if (msFloat < 0) {
						throw new TypeError("Negative ticks are not supported");
					}

					// adjust for positive overflow
					if (nanosTotal >= 1e6) {
						tickTo += 1;
						nanosTotal -= 1e6;
					}

					nanos = nanosTotal;
					let tickFrom = clock.now;
					let previous = clock.now;
					// ESLint fails to detect this correctly
					/* eslint-disable prefer-const */
					let timer,
						firstException,
						oldNow,
						nextPromiseTick,
						compensationCheck,
						postTimerCall;
					/* eslint-enable prefer-const */

					clock.duringTick = true;

					// perform microtasks
					oldNow = clock.now;
					runJobs(clock);
					if (oldNow !== clock.now) {
						// compensate for any setSystemTime() call during microtask callback
						tickFrom += clock.now - oldNow;
						tickTo += clock.now - oldNow;
					}

					//eslint-disable-next-line jsdoc/require-jsdoc
					function doTickInner() {
						// perform each timer in the requested range
						timer = firstTimerInRange(clock, tickFrom, tickTo);
						// eslint-disable-next-line no-unmodified-loop-condition
						while (timer && tickFrom <= tickTo) {
							if (clock.timers[timer.id]) {
								tickFrom = timer.callAt;
								clock.now = timer.callAt;
								oldNow = clock.now;
								try {
									runJobs(clock);
									callTimer(clock, timer);
								} catch (e) {
									firstException = firstException || e;
								}

								if (isAsync) {
									// finish up after native setImmediate callback to allow
									// all native es6 promises to process their callbacks after
									// each timer fires.
									originalSetTimeout(nextPromiseTick);
									return;
								}

								compensationCheck();
							}

							postTimerCall();
						}

						// perform process.nextTick()s again
						oldNow = clock.now;
						runJobs(clock);
						if (oldNow !== clock.now) {
							// compensate for any setSystemTime() call during process.nextTick() callback
							tickFrom += clock.now - oldNow;
							tickTo += clock.now - oldNow;
						}
						clock.duringTick = false;

						// corner case: during runJobs new timers were scheduled which could be in the range [clock.now, tickTo]
						timer = firstTimerInRange(clock, tickFrom, tickTo);
						if (timer) {
							try {
								clock.tick(tickTo - clock.now); // do it all again - for the remainder of the requested range
							} catch (e) {
								firstException = firstException || e;
							}
						} else {
							// no timers remaining in the requested range: move the clock all the way to the end
							clock.now = tickTo;

							// update nanos
							nanos = nanosTotal;
						}
						if (firstException) {
							throw firstException;
						}

						if (isAsync) {
							resolve(clock.now);
						} else {
							return clock.now;
						}
					}

					nextPromiseTick =
						isAsync &&
						function () {
							try {
								compensationCheck();
								postTimerCall();
								doTickInner();
							} catch (e) {
								reject(e);
							}
						};

					compensationCheck = function () {
						// compensate for any setSystemTime() call during timer callback
						if (oldNow !== clock.now) {
							tickFrom += clock.now - oldNow;
							tickTo += clock.now - oldNow;
							previous += clock.now - oldNow;
						}
					};

					postTimerCall = function () {
						timer = firstTimerInRange(clock, previous, tickTo);
						previous = tickFrom;
					};

					return doTickInner();
				}

				/**
				 * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"
				 * @returns {number} will return the new `now` value
				 */
				clock.tick = function tick(tickValue) {
					return doTick(tickValue, false);
				};

				if (typeof _global.Promise !== "undefined") {
					/**
					 * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15"
					 * @returns {Promise}
					 */
					clock.tickAsync = function tickAsync(tickValue) {
						return new _global.Promise(function (resolve, reject) {
							originalSetTimeout(function () {
								try {
									doTick(tickValue, true, resolve, reject);
								} catch (e) {
									reject(e);
								}
							});
						});
					};
				}

				clock.next = function next() {
					runJobs(clock);
					const timer = firstTimer(clock);
					if (!timer) {
						return clock.now;
					}

					clock.duringTick = true;
					try {
						clock.now = timer.callAt;
						callTimer(clock, timer);
						runJobs(clock);
						return clock.now;
					} finally {
						clock.duringTick = false;
					}
				};

				if (typeof _global.Promise !== "undefined") {
					clock.nextAsync = function nextAsync() {
						return new _global.Promise(function (resolve, reject) {
							originalSetTimeout(function () {
								try {
									const timer = firstTimer(clock);
									if (!timer) {
										resolve(clock.now);
										return;
									}

									let err;
									clock.duringTick = true;
									clock.now = timer.callAt;
									try {
										callTimer(clock, timer);
									} catch (e) {
										err = e;
									}
									clock.duringTick = false;

									originalSetTimeout(function () {
										if (err) {
											reject(err);
										} else {
											resolve(clock.now);
										}
									});
								} catch (e) {
									reject(e);
								}
							});
						});
					};
				}

				clock.runAll = function runAll() {
					let numTimers, i;
					runJobs(clock);
					for (i = 0; i < clock.loopLimit; i++) {
						if (!clock.timers) {
							resetIsNearInfiniteLimit();
							return clock.now;
						}

						numTimers = Object.keys(clock.timers).length;
						if (numTimers === 0) {
							resetIsNearInfiniteLimit();
							return clock.now;
						}

						clock.next();
						checkIsNearInfiniteLimit(clock, i);
					}

					const excessJob = firstTimer(clock);
					throw getInfiniteLoopError(clock, excessJob);
				};

				clock.runToFrame = function runToFrame() {
					return clock.tick(getTimeToNextFrame());
				};

				if (typeof _global.Promise !== "undefined") {
					clock.runAllAsync = function runAllAsync() {
						return new _global.Promise(function (resolve, reject) {
							let i = 0;
							/**
							 *
							 */
							function doRun() {
								originalSetTimeout(function () {
									try {
										let numTimers;
										if (i < clock.loopLimit) {
											if (!clock.timers) {
												resetIsNearInfiniteLimit();
												resolve(clock.now);
												return;
											}

											numTimers = Object.keys(clock.timers)
												.length;
											if (numTimers === 0) {
												resetIsNearInfiniteLimit();
												resolve(clock.now);
												return;
											}

											clock.next();

											i++;

											doRun();
											checkIsNearInfiniteLimit(clock, i);
											return;
										}

										const excessJob = firstTimer(clock);
										reject(getInfiniteLoopError(clock, excessJob));
									} catch (e) {
										reject(e);
									}
								});
							}
							doRun();
						});
					};
				}

				clock.runToLast = function runToLast() {
					const timer = lastTimer(clock);
					if (!timer) {
						runJobs(clock);
						return clock.now;
					}

					return clock.tick(timer.callAt - clock.now);
				};

				if (typeof _global.Promise !== "undefined") {
					clock.runToLastAsync = function runToLastAsync() {
						return new _global.Promise(function (resolve, reject) {
							originalSetTimeout(function () {
								try {
									const timer = lastTimer(clock);
									if (!timer) {
										resolve(clock.now);
									}

									resolve(clock.tickAsync(timer.callAt));
								} catch (e) {
									reject(e);
								}
							});
						});
					};
				}

				clock.reset = function reset() {
					nanos = 0;
					clock.timers = {};
					clock.jobs = [];
					clock.now = start;
				};

				clock.setSystemTime = function setSystemTime(systemTime) {
					// determine time difference
					const newNow = getEpoch(systemTime);
					const difference = newNow - clock.now;
					let id, timer;

					adjustedSystemTime[0] = adjustedSystemTime[0] + difference;
					adjustedSystemTime[1] = adjustedSystemTime[1] + nanos;
					// update 'system clock'
					clock.now = newNow;
					nanos = 0;

					// update timers and intervals to keep them stable
					for (id in clock.timers) {
						if (clock.timers.hasOwnProperty(id)) {
							timer = clock.timers[id];
							timer.createdAt += difference;
							timer.callAt += difference;
						}
					}
				};

				if (performancePresent) {
					clock.performance = Object.create(null);
					clock.performance.now = function FakeTimersNow() {
						const hrt = hrtime();
						const millis = hrt[0] * 1000 + hrt[1] / 1e6;
						return millis;
					};
				}

				if (hrtimePresent) {
					clock.hrtime = hrtime;
				}

				return clock;
			}

			/* eslint-disable complexity */

			/**
			 * @param {Config=} [config] Optional config
			 * @returns {Clock}
			 */
			function install(config) {
				if (
					arguments.length > 1 ||
					config instanceof Date ||
					Array.isArray(config) ||
					typeof config === "number"
				) {
					throw new TypeError(
						`FakeTimers.install called with ${String(
							config
						)} install requires an object parameter`
					);
				}

				if (_global.Date.isFake === true) {
					// Timers are already faked; this is a problem.
					// Make the user reset timers before continuing.
					throw new TypeError(
						"Can't install fake timers twice on the same global object."
					);
				}

				// eslint-disable-next-line no-param-reassign
				config = typeof config !== "undefined" ? config : {};
				config.shouldAdvanceTime = config.shouldAdvanceTime || false;
				config.advanceTimeDelta = config.advanceTimeDelta || 20;
				config.shouldClearNativeTimers =
					config.shouldClearNativeTimers || false;

				if (config.target) {
					throw new TypeError(
						"config.target is no longer supported. Use `withGlobal(target)` instead."
					);
				}

				let i, l;
				const clock = createClock(config.now, config.loopLimit);
				clock.shouldClearNativeTimers = config.shouldClearNativeTimers;

				clock.uninstall = function () {
					return uninstall(clock, config);
				};

				clock.methods = config.toFake || [];

				if (clock.methods.length === 0) {
					// do not fake nextTick by default - GitHub#126
					clock.methods = Object.keys(timers).filter(function (key) {
						return key !== "nextTick" && key !== "queueMicrotask";
					});
				}

				if (config.shouldAdvanceTime === true) {
					const intervalTick = doIntervalTick.bind(
						null,
						clock,
						config.advanceTimeDelta
					);
					const intervalId = _global.setInterval(
						intervalTick,
						config.advanceTimeDelta
					);
					clock.attachedInterval = intervalId;
				}

				if (clock.methods.includes("performance")) {
					const proto = (() => {
						if (hasPerformancePrototype) {
							return _global.Performance.prototype;
						}
						if (hasPerformanceConstructorPrototype) {
							return _global.performance.constructor.prototype;
						}
					})();
					if (proto) {
						Object.getOwnPropertyNames(proto).forEach(function (name) {
							if (name !== "now") {
								clock.performance[name] =
									name.indexOf("getEntries") === 0
										? NOOP_ARRAY
										: NOOP;
							}
						});
					} else if ((config.toFake || []).includes("performance")) {
						// user explicitly tried to fake performance when not present
						throw new ReferenceError(
							"non-existent performance object cannot be faked"
						);
					}
				}

				for (i = 0, l = clock.methods.length; i < l; i++) {
					const nameOfMethodToReplace = clock.methods[i];
					if (nameOfMethodToReplace === "hrtime") {
						if (
							_global.process &&
							typeof _global.process.hrtime === "function"
						) {
							hijackMethod(_global.process, nameOfMethodToReplace, clock);
						}
					} else if (nameOfMethodToReplace === "nextTick") {
						if (
							_global.process &&
							typeof _global.process.nextTick === "function"
						) {
							hijackMethod(_global.process, nameOfMethodToReplace, clock);
						}
					} else {
						hijackMethod(_global, nameOfMethodToReplace, clock);
					}
				}

				return clock;
			}

			/* eslint-enable complexity */

			return {
				timers: timers,
				createClock: createClock,
				install: install,
				withGlobal: withGlobal,
			};
		}
Mocks available features in the specified global namespace.

@param {*} _global Namespace to mock (e.g. `window`)
@returns {FakeTimers}