UNPKG

influx-point-formatter

Version:
329 lines (328 loc) 10.8 kB
"use strict"; /* eslint-disable no-fallthrough */ Object.defineProperty(exports, "__esModule", { value: true }); 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 date = new Date(Math.floor(Number(timestamp) / nsPer.ms)); 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;