UNPKG

10.8 kBJavaScriptView Raw
1"use strict";
2/* eslint-disable no-fallthrough */
3Object.defineProperty(exports, "__esModule", { value: true });
4exports.castTimestamp = exports.isoOrTimeToDate = exports.dateToTime = exports.formatDate = exports.toNanoDate = exports.Precision = void 0;
5const ds_1 = require("./ds");
6/**
7 * Just a quick overview of what's going on in this file. It's a bit of a mess.
8 * Influx uses three time formats:
9 * - ISO times with nanoseconds when querying where an epoch is not provided
10 * - Unix timestamps when querying with an epoch (specifying the precision
11 * in the given time unit)
12 * - Its own time format for time literals.
13 *
14 * To complicate matters, Influx operates on nanosecond precisions
15 * by default, but we can't represent nanosecond timestamps in
16 * JavaScript numbers as they're 64 bit uints.
17 *
18 * As a result we have several utilities to convert between these different
19 * formats. When precision is required, we represent nanosecond timestamps
20 * as strings and wrap default dates in the INanoDate interface which
21 * lets the consumer read and write these more precise timestamps.
22 *
23 * Representing the timestamps as strings is definitely not a pure way to go
24 * about it, but importing an arbitrary-precision integer library adds
25 * bloat and is a massive hit to throughput. The operations we do do
26 * are pretty trivial, so we stick with manipulating strings
27 * and make sure to wash our hands when we're done.
28 *
29 * Vocabulary:
30 * Unix timestamp = 'timestamp', abbreviated as 'time'
31 * ISO timestamp = 'ISO time', abbreviated as 'ISO'
32 * Influx timestamp = 'Influx time', abbreviated as 'Influx'
33 */
34function leftPad(str, length, pad = "0") {
35 if (typeof str === "number") {
36 str = String(str);
37 }
38 while (str.length < length) {
39 str = pad + str;
40 }
41 return str;
42}
43function rightPad(str, length, pad = "0") {
44 if (typeof str === "number") {
45 str = String(str);
46 }
47 while (str.length < length) {
48 str += pad;
49 }
50 return str;
51}
52/**
53 * Precision is a map of available Influx time precisions.
54 * @type {Object.<String, String>}
55 * @example
56 * console.log(Precision.Hours); // => 'h'
57 * console.log(Precision.Minutes); // => 'm'
58 * console.log(Precision.Seconds); // => 's'
59 * console.log(Precision.Milliseconds); // => 'ms'
60 * console.log(Precision.Microseconds); // => 'u'
61 * console.log(Precision.Nanoseconds); // => 'n'
62 */
63exports.Precision = Object.freeze({
64 // Tslint:disable-line
65 Hours: "h",
66 Microseconds: "u",
67 Milliseconds: "ms",
68 Minutes: "m",
69 Nanoseconds: "n",
70 Seconds: "s",
71});
72class MillisecondDateManipulator {
73 format(date) {
74 return ('"' +
75 leftPad(date.getUTCFullYear(), 2) +
76 "-" +
77 leftPad(date.getUTCMonth() + 1, 2) +
78 "-" +
79 leftPad(date.getUTCDate(), 2) +
80 " " +
81 leftPad(date.getUTCHours(), 2) +
82 ":" +
83 leftPad(date.getUTCMinutes(), 2) +
84 ":" +
85 leftPad(date.getUTCSeconds(), 2) +
86 "." +
87 leftPad(date.getUTCMilliseconds(), 3) +
88 '"');
89 }
90 toTime(date, precision) {
91 let ms = date.getTime();
92 switch (precision) {
93 case "n":
94 ms *= 1000;
95 case "u":
96 ms *= 1000;
97 case "ms":
98 return String(ms);
99 case "h":
100 ms /= 60;
101 case "m":
102 ms /= 60;
103 case "s":
104 ms /= 1000;
105 return String(Math.floor(ms));
106 default:
107 throw new Error(`Unknown precision '${precision}'!`);
108 }
109 }
110 isoToDate(timestamp) {
111 return new Date(timestamp);
112 }
113 timetoDate(timestamp, precision) {
114 switch (precision) {
115 case "n":
116 timestamp /= 1000;
117 case "u":
118 timestamp /= 1000;
119 case "ms":
120 return new Date(timestamp);
121 case "h":
122 timestamp *= 60;
123 case "m":
124 timestamp *= 60;
125 case "s":
126 timestamp *= 1000;
127 return new Date(timestamp);
128 default:
129 throw new Error(`Unknown precision '${precision}'!`);
130 }
131 }
132}
133const nsPer = {
134 ms: Math.pow(10, 6),
135 s: Math.pow(10, 9),
136};
137function nanoIsoToTime(iso) {
138 let [secondsStr, decimalStr] = iso.split(".");
139 if (decimalStr === undefined) {
140 decimalStr = "000000000";
141 }
142 else {
143 decimalStr = rightPad(decimalStr.slice(0, -1), 9);
144 secondsStr += "Z";
145 }
146 const seconds = Math.floor(new Date(secondsStr).getTime() / 1000);
147 return `${seconds}${decimalStr}`;
148}
149const nanoDateMethods = {
150 getNanoTimeFromISO() {
151 if (!this._cachedNanoISO) {
152 this._cachedNanoTime = nanoIsoToTime(this._nanoISO);
153 }
154 return this._cachedNanoTime;
155 },
156 toNanoISOStringFromISO() {
157 if (!this._cachedNanoISO) {
158 this._cachedNanoTime = nanoIsoToTime(this._nanoISO);
159 }
160 const base = this.toISOString().slice(0, -4); // Slice of `123Z` milliseconds
161 return `${base}${this._cachedNanoTime.slice(-9)}Z`;
162 },
163 getNanoTimeFromStamp() {
164 return this._nanoTime;
165 },
166 toNanoISOStringFromStamp() {
167 const base = this.toISOString().slice(0, -4); // Slice of `123Z` milliseconds
168 return `${base}${this._nanoTime.slice(-9)}Z`;
169 },
170};
171/**
172 * Covers a nanoseconds unix timestamp to a INanoDate for node-influx. The
173 * timestamp is provided as a string to prevent precision loss.
174 *
175 * Please see [A Moment for Times](https://node-influx.github.io/manual/
176 * usage.html#a-moment-for-times) for a more complete and eloquent explanation
177 * of time handling in this module.
178 *
179 * @param timestamp
180 * @example
181 * const date = toNanoDate('1475985480231035600')
182 *
183 * // You can use the returned Date as a normal date:
184 * expect(date.getTime()).to.equal(1475985480231);
185 *
186 * // We decorate it with two additional methods to read
187 * // nanosecond-precision results:
188 * expect(date.getNanoTime()).to.equal('1475985480231035600');
189 * expect(date.toNanoISOString()).to.equal('2016-10-09T03:58:00.231035600Z');
190 */
191function toNanoDate(timestamp) {
192 const secs = Math.floor(Number(timestamp) / nsPer.s);
193 const remainingNs = timestamp.slice(String(secs).length);
194 const remainingMs = Number(remainingNs) / nsPer.ms;
195 const date = new Date(0);
196 date.setUTCSeconds(secs, remainingMs);
197 date._nanoTime = timestamp;
198 date.getNanoTime = nanoDateMethods.getNanoTimeFromStamp;
199 date.toNanoISOString = nanoDateMethods.toNanoISOStringFromStamp;
200 return date;
201}
202exports.toNanoDate = toNanoDate;
203function asNanoDate(date) {
204 const d = date;
205 if (d.getNanoTime) {
206 return d;
207 }
208 return undefined;
209}
210class NanosecondsDateManipulator {
211 format(date) {
212 return ('"' +
213 leftPad(date.getUTCFullYear(), 2) +
214 "-" +
215 leftPad(date.getUTCMonth() + 1, 2) +
216 "-" +
217 leftPad(date.getUTCDate(), 2) +
218 " " +
219 leftPad(date.getUTCHours(), 2) +
220 ":" +
221 leftPad(date.getUTCMinutes(), 2) +
222 ":" +
223 leftPad(date.getUTCSeconds(), 2) +
224 "." +
225 date.getNanoTime().slice(-9) +
226 '"');
227 }
228 toTime(date, precision) {
229 let ms = date.getTime();
230 switch (precision) {
231 case "u":
232 return date.getNanoTime().slice(0, -3);
233 case "n":
234 return date.getNanoTime();
235 case "h":
236 ms /= 60;
237 case "m":
238 ms /= 60;
239 case "s":
240 ms /= 1000;
241 case "ms":
242 return String(Math.floor(ms));
243 default:
244 throw new Error(`Unknown precision '${precision}'!`);
245 }
246 }
247 isoToDate(timestamp) {
248 const date = new Date(timestamp);
249 date._nanoISO = timestamp;
250 date.getNanoTime = nanoDateMethods.getNanoTimeFromISO;
251 date.toNanoISOString = nanoDateMethods.toNanoISOStringFromISO;
252 return date;
253 }
254 timetoDate(timestamp, precision) {
255 switch (precision) {
256 case "h":
257 timestamp *= 60;
258 case "m":
259 timestamp *= 60;
260 case "s":
261 timestamp *= 1000;
262 case "ms":
263 timestamp *= 1000;
264 case "u":
265 timestamp *= 1000;
266 case "n": {
267 const date = new Date(timestamp / nsPer.ms);
268 date._nanoTime = String(timestamp);
269 date.getNanoTime = nanoDateMethods.getNanoTimeFromStamp;
270 date.toNanoISOString = nanoDateMethods.toNanoISOStringFromStamp;
271 return date;
272 }
273 default:
274 throw new Error(`Unknown precision '${precision}'!`);
275 }
276 }
277}
278const milliManipulator = new MillisecondDateManipulator();
279const nanoManipulator = new NanosecondsDateManipulator();
280/**
281 * FormatDate converts the Date instance to Influx's date query format.
282 * @private
283 */
284function formatDate(date) {
285 const nano = asNanoDate(date);
286 if (nano) {
287 return nanoManipulator.format(nano);
288 }
289 return milliManipulator.format(date);
290}
291exports.formatDate = formatDate;
292/**
293 * Converts a Date instance to a timestamp with the specified time precision.
294 * @private
295 */
296function dateToTime(date, precision) {
297 const nano = asNanoDate(date);
298 if (nano) {
299 return nanoManipulator.toTime(nano, precision);
300 }
301 return milliManipulator.toTime(date, precision);
302}
303exports.dateToTime = dateToTime;
304/**
305 * Converts an ISO-formatted data or unix timestamp to a Date instance. If
306 * the precision is finer than 'ms' the returned value will be a INanoDate.
307 * @private
308 */
309function isoOrTimeToDate(stamp, precision = "n") {
310 if (typeof stamp === "string") {
311 return nanoManipulator.isoToDate(stamp);
312 }
313 return nanoManipulator.timetoDate(stamp, precision);
314}
315exports.isoOrTimeToDate = isoOrTimeToDate;
316/**
317 * Converts a timestamp to a string with the correct precision. Assumes
318 * that raw number and string instances are already in the correct precision.
319 * @private
320 */
321function castTimestamp(timestamp, precision) {
322 if (typeof timestamp === "string") {
323 if (!ds_1.isNumeric(timestamp)) {
324 throw new Error(`Expected numeric value for, timestamp, but got '${timestamp}'!`);
325 }
326 return timestamp;
327 }
328 if (typeof timestamp === "number") {
329 return String(timestamp);
330 }
331 return dateToTime(timestamp, precision);
332}
333exports.castTimestamp = castTimestamp;