UNPKG

influx

Version:
334 lines (333 loc) 10.8 kB
"use strict"; /* eslint-disable no-fallthrough */ Object.defineProperty(exports, "__esModule", { value: true }); exports.castTimestamp = exports.isoOrTimeToDate = exports.dateToTime = exports.formatDate = exports.toNanoDate = exports.Precision = void 0; const ds_1 = require("./ds"); /** * Just a quick overview of what's going on in this file. It's a bit of a mess. * Influx uses three time formats: * - ISO times with nanoseconds when querying where an epoch is not provided * - Unix timestamps when querying with an epoch (specifying the precision * in the given time unit) * - Its own time format for time literals. * * To complicate matters, Influx operates on nanosecond precisions * by default, but we can't represent nanosecond timestamps in * JavaScript numbers as they're 64 bit uints. * * As a result we have several utilities to convert between these different * formats. When precision is required, we represent nanosecond timestamps * as strings and wrap default dates in the INanoDate interface which * lets the consumer read and write these more precise timestamps. * * Representing the timestamps as strings is definitely not a pure way to go * about it, but importing an arbitrary-precision integer library adds * bloat and is a massive hit to throughput. The operations we do do * are pretty trivial, so we stick with manipulating strings * and make sure to wash our hands when we're done. * * Vocabulary: * Unix timestamp = 'timestamp', abbreviated as 'time' * ISO timestamp = 'ISO time', abbreviated as 'ISO' * Influx timestamp = 'Influx time', abbreviated as 'Influx' */ function leftPad(str, length, pad = "0") { if (typeof str === "number") { str = String(str); } while (str.length < length) { str = pad + str; } return str; } function rightPad(str, length, pad = "0") { if (typeof str === "number") { str = String(str); } while (str.length < length) { str += pad; } return str; } /** * Precision is a map of available Influx time precisions. * @type {Object.<String, String>} * @example * console.log(Precision.Hours); // => 'h' * console.log(Precision.Minutes); // => 'm' * console.log(Precision.Seconds); // => 's' * console.log(Precision.Milliseconds); // => 'ms' * console.log(Precision.Microseconds); // => 'u' * console.log(Precision.Nanoseconds); // => 'n' */ exports.Precision = Object.freeze({ // Tslint:disable-line Hours: "h", Microseconds: "u", Milliseconds: "ms", Minutes: "m", Nanoseconds: "n", Seconds: "s", }); class MillisecondDateManipulator { format(date) { return ('"' + leftPad(date.getUTCFullYear(), 2) + "-" + leftPad(date.getUTCMonth() + 1, 2) + "-" + leftPad(date.getUTCDate(), 2) + " " + leftPad(date.getUTCHours(), 2) + ":" + leftPad(date.getUTCMinutes(), 2) + ":" + leftPad(date.getUTCSeconds(), 2) + "." + leftPad(date.getUTCMilliseconds(), 3) + '"'); } toTime(date, precision) { let ms = date.getTime(); switch (precision) { case "n": ms *= 1000; case "u": ms *= 1000; case "ms": return String(ms); case "h": ms /= 60; case "m": ms /= 60; case "s": ms /= 1000; return String(Math.floor(ms)); default: throw new Error(`Unknown precision '${precision}'!`); } } isoToDate(timestamp) { return new Date(timestamp); } timetoDate(timestamp, precision) { switch (precision) { case "n": timestamp /= 1000; case "u": timestamp /= 1000; case "ms": return new Date(timestamp); case "h": timestamp *= 60; case "m": timestamp *= 60; case "s": timestamp *= 1000; return new Date(timestamp); default: throw new Error(`Unknown precision '${precision}'!`); } } } const nsPer = { ms: Math.pow(10, 6), s: Math.pow(10, 9), }; function nanoIsoToTime(iso) { let [secondsStr, decimalStr] = iso.split("."); if (decimalStr === undefined) { decimalStr = "000000000"; } else { decimalStr = rightPad(decimalStr.slice(0, -1), 9); secondsStr += "Z"; } const seconds = Math.floor(new Date(secondsStr).getTime() / 1000); return `${seconds}${decimalStr}`; } const nanoDateMethods = { getNanoTimeFromISO() { if (!this._cachedNanoISO) { this._cachedNanoTime = nanoIsoToTime(this._nanoISO); } return this._cachedNanoTime; }, toNanoISOStringFromISO() { if (!this._cachedNanoISO) { this._cachedNanoTime = nanoIsoToTime(this._nanoISO); } const base = this.toISOString().slice(0, -4); // Slice of `123Z` milliseconds return `${base}${this._cachedNanoTime.slice(-9)}Z`; }, getNanoTimeFromStamp() { return this._nanoTime; }, toNanoISOStringFromStamp() { const base = this.toISOString().slice(0, -4); // Slice of `123Z` milliseconds return `${base}${this._nanoTime.slice(-9)}Z`; }, }; /** * Covers a nanoseconds unix timestamp to a INanoDate for node-influx. The * timestamp is provided as a string to prevent precision loss. * * Please see [A Moment for Times](https://node-influx.github.io/manual/ * usage.html#a-moment-for-times) for a more complete and eloquent explanation * of time handling in this module. * * @param timestamp * @example * const date = toNanoDate('1475985480231035600') * * // You can use the returned Date as a normal date: * expect(date.getTime()).to.equal(1475985480231); * * // We decorate it with two additional methods to read * // nanosecond-precision results: * expect(date.getNanoTime()).to.equal('1475985480231035600'); * expect(date.toNanoISOString()).to.equal('2016-10-09T03:58:00.231035600Z'); */ function toNanoDate(timestamp) { const secs = Math.floor(Number(timestamp) / nsPer.s); const remainingNs = timestamp.slice(String(secs).length); const remainingMs = Number(remainingNs) / nsPer.ms; const date = new Date(0); date.setUTCSeconds(secs, remainingMs); date._nanoTime = timestamp; date.getNanoTime = nanoDateMethods.getNanoTimeFromStamp; date.toNanoISOString = nanoDateMethods.toNanoISOStringFromStamp; return date; } exports.toNanoDate = toNanoDate; function asNanoDate(date) { const d = date; if (d.getNanoTime) { return d; } return undefined; } class NanosecondsDateManipulator { format(date) { return ('"' + leftPad(date.getUTCFullYear(), 2) + "-" + leftPad(date.getUTCMonth() + 1, 2) + "-" + leftPad(date.getUTCDate(), 2) + " " + leftPad(date.getUTCHours(), 2) + ":" + leftPad(date.getUTCMinutes(), 2) + ":" + leftPad(date.getUTCSeconds(), 2) + "." + date.getNanoTime().slice(-9) + '"'); } toTime(date, precision) { let ms = date.getTime(); switch (precision) { case "u": return date.getNanoTime().slice(0, -3); case "n": return date.getNanoTime(); case "h": ms /= 60; case "m": ms /= 60; case "s": ms /= 1000; case "ms": return String(Math.floor(ms)); default: throw new Error(`Unknown precision '${precision}'!`); } } isoToDate(timestamp) { const date = new Date(timestamp); date._nanoISO = timestamp; date.getNanoTime = nanoDateMethods.getNanoTimeFromISO; date.toNanoISOString = nanoDateMethods.toNanoISOStringFromISO; return date; } timetoDate(timestamp, precision) { switch (precision) { case "h": timestamp *= 60; case "m": timestamp *= 60; case "s": timestamp *= 1000; case "ms": timestamp *= 1000; case "u": timestamp *= 1000; case "n": { const date = new Date(timestamp / nsPer.ms); date._nanoTime = String(timestamp); date.getNanoTime = nanoDateMethods.getNanoTimeFromStamp; date.toNanoISOString = nanoDateMethods.toNanoISOStringFromStamp; return date; } default: throw new Error(`Unknown precision '${precision}'!`); } } } const milliManipulator = new MillisecondDateManipulator(); const nanoManipulator = new NanosecondsDateManipulator(); /** * FormatDate converts the Date instance to Influx's date query format. * @private */ function formatDate(date) { const nano = asNanoDate(date); if (nano) { return nanoManipulator.format(nano); } return milliManipulator.format(date); } exports.formatDate = formatDate; /** * Converts a Date instance to a timestamp with the specified time precision. * @private */ function dateToTime(date, precision) { const nano = asNanoDate(date); if (nano) { return nanoManipulator.toTime(nano, precision); } return milliManipulator.toTime(date, precision); } exports.dateToTime = dateToTime; /** * Converts an ISO-formatted data or unix timestamp to a Date instance. If * the precision is finer than 'ms' the returned value will be a INanoDate. * @private */ function isoOrTimeToDate(stamp, precision = "n") { if (typeof stamp === "string") { return nanoManipulator.isoToDate(stamp); } return nanoManipulator.timetoDate(stamp, precision); } exports.isoOrTimeToDate = isoOrTimeToDate; /** * Converts a timestamp to a string with the correct precision. Assumes * that raw number and string instances are already in the correct precision. * @private */ function castTimestamp(timestamp, precision) { if (typeof timestamp === "string") { if (!ds_1.isNumeric(timestamp)) { throw new Error(`Expected numeric value for, timestamp, but got '${timestamp}'!`); } return timestamp; } if (typeof timestamp === "number") { return String(timestamp); } return dateToTime(timestamp, precision); } exports.castTimestamp = castTimestamp;