"use strict";
/*!
* Copyright 2019 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreciseDate = void 0;
const FULL_ISO_REG = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d{4,9}Z/;
const NO_BIG_INT = 'BigInt only available in Node >= v10.7. Consider using getFullTimeString instead.';
var Sign;
(function (Sign) {
Sign[Sign["NEGATIVE"] = -1] = "NEGATIVE";
Sign[Sign["POSITIVE"] = 1] = "POSITIVE";
Sign[Sign["ZERO"] = 0] = "ZERO";
})(Sign || (Sign = {}));
/**
* The native Date object.
* @external Date
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date}
*/
/**
* @typedef {array} DateTuple
* @property {number} 0 Represents seconds of UTC time since Unix epoch
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
* 9999-12-31T23:59:59Z inclusive.
* @property {number} 1 Non-negative fractions of a second at nanosecond
* resolution. Negative second values with fractions must still have
* non-negative nanos values that count forward in time. Must be from 0 to
* 999,999,999 inclusive.
*/
/**
* @typedef {object} DateStruct
* @property {number} seconds Represents seconds of UTC time since Unix epoch
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
* 9999-12-31T23:59:59Z inclusive.
* @property {number} nanos Non-negative fractions of a second at nanosecond
* resolution. Negative second values with fractions must still have
* non-negative nanos values that count forward in time. Must be from 0 to
* 999,999,999 inclusive.
*/
/**
* Date object with nanosecond precision. Supports all standard Date arguments
* in addition to several custom types as noted below.
*
* @class
* @extends external:Date
*
* @param {number|string|bigint|Date|DateTuple|DateStruct} [time] The time
* value.
* @param {...number} [dateFields] Additional date fields (month, date, hours,
* minutes, seconds, milliseconds, microseconds, nanoseconds).
*
* @example
With a RFC 3339 formatted string.
* const date = new PreciseDate('2019-02-08T10:34:29.481145231Z');
*
* @example With a nanosecond timestamp string.
* const date = new PreciseDate('1549622069481320032');
*
* @example With a BigInt (requires Node >= v10.7)
* const date = new PreciseDate(1549622069481320032n);
*
* @example With a tuple containing seconds and nanoseconds.
* const date = new PreciseDate([1549622069, 481320032]);
*
* @example With an object containing `seconds` and `nanos`
* const date = new PreciseDate({seconds: 1549622069, nanos: 481320032});
*
* @example Specifiying date fields
* const date = new PreciseDate(2018, 5, 14, 41, 11, 34, 123, 874, 321);
*/
class PreciseDate extends Date {
constructor(time) {
super();
this._micros = 0;
this._nanos = 0;
if (time && typeof time !== 'number' && !(time instanceof Date)) {
this.setFullTime(PreciseDate.parseFull(time));
return;
}
// eslint-disable-next-line prefer-rest-params
const args = Array.from(arguments);
const dateFields = args.slice(0, 7);
const date = new Date(...dateFields);
const nanos = args.length === 9 ? args.pop() : 0;
const micros = args.length === 8 ? args.pop() : 0;
this.setTime(date.getTime());
this.setMicroseconds(micros);
this.setNanoseconds(nanos);
}
/**
* Returns the specified date represented in nanoseconds according to
* universal time.
*
* **NOTE:** Because this method returns a `BigInt` it requires Node >= v10.7.
* Use {@link PreciseDate#getFullTimeString} to get the time as a string.
*
* @see {@link https://github.com/tc39/proposal-bigint|BigInt}
*
* @throws {error} If `BigInt` is unavailable.
* @returns {bigint}
*
* @example
* const date = new PreciseDate('2019-02-08T10:34:29.481145231Z');
*
* console.log(date.getFullTime());
* // expected output: 1549622069481145231n
*/
getFullTime() {
if (typeof BigInt !== 'function') {
throw new Error(NO_BIG_INT);
}
return BigInt(this.getFullTimeString());
}
/**
* Returns a string of the specified date represented in nanoseconds according
* to universal time.
*
* @returns {string}
*
* @example
* const date = new PreciseDate('2019-02-08T10:34:29.481145231Z');
*
* console.log(date.getFullTimeString());
* // expected output: "1549622069481145231"
*/
getFullTimeString() {
const seconds = this._getSeconds();
let nanos = this._getNanos();
if (nanos && Math.sign(seconds) === Sign.NEGATIVE) {
nanos = 1e9 - nanos;
}
return `${seconds}${padLeft(nanos, 9)}`;
}
/**
* Returns the microseconds in the specified date according to universal time.
*
* @returns {number}
*
* @example
* const date = new PreciseDate('2019-02-08T10:34:29.481145Z');
*
* console.log(date.getMicroseconds());
* // expected output: 145
*/
getMicroseconds() {
return this._micros;
}
/**
* Returns the nanoseconds in the specified date according to universal time.
*
* @returns {number}
*
* @example
* const date = new PreciseDate('2019-02-08T10:34:29.481145231Z');
*
* console.log(date.getNanoseconds());
* // expected output: 231
*/
getNanoseconds() {
return this._nanos;
}
/**
* Sets the microseconds for a specified date according to universal time.
*
* @param {number} microseconds A number representing the microseconds.
* @returns {string} Returns a string representing the nanoseconds in the
* specified date according to universal time.
*
* @example
* const date = new PreciseDate();
*
* date.setMicroseconds(149);
*
* console.log(date.getMicroseconds());
* // expected output: 149
*/
setMicroseconds(micros) {
const abs = Math.abs(micros);
let millis = this.getUTCMilliseconds();
if (abs >= 1000) {
millis += Math.floor(abs / 1000) * Math.sign(micros);
micros %= 1000;
}
if (Math.sign(micros) === Sign.NEGATIVE) {
millis -= 1;
micros += 1000;
}
this._micros = micros;
this.setUTCMilliseconds(millis);
return this.getFullTimeString();
}
/**
* Sets the nanoseconds for a specified date according to universal time.
*
* @param {number} nanoseconds A number representing the nanoseconds.
* @returns {string} Returns a string representing the nanoseconds in the
* specified date according to universal time.
*
* @example
* const date = new PreciseDate();
*
* date.setNanoseconds(231);
*
* console.log(date.getNanoseconds());
* // expected output: 231
*/
setNanoseconds(nanos) {
const abs = Math.abs(nanos);
let micros = this._micros;
if (abs >= 1000) {
micros += Math.floor(abs / 1000) * Math.sign(nanos);
nanos %= 1000;
}
if (Math.sign(nanos) === Sign.NEGATIVE) {
micros -= 1;
nanos += 1000;
}
this._nanos = nanos;
return this.setMicroseconds(micros);
}
/**
* Sets the PreciseDate object to the time represented by a number of
* nanoseconds since January 1, 1970, 00:00:00 UTC.
*
* @param {bigint|number|string} time Value representing the number of
* nanoseconds since January 1, 1970, 00:00:00 UTC.
* @returns {string} Returns a string representing the nanoseconds in the
* specified date according to universal time (effectively, the value of
* the argument).
*
* @see {@link https://github.com/tc39/proposal-bigint|BigInt}
*
* @example With a nanosecond string.
* const date = new PreciseDate();
* date.setFullTime('1549622069481145231');
*
* @example With a BigInt
* date.setFullTime(1549622069481145231n);
*/
setFullTime(time) {
if (typeof time !== 'string') {
time = time.toString();
}
const sign = Math.sign(Number(time));
time = time.replace(/^-/, '');
const seconds = Number(time.substr(0, time.length - 9)) * sign;
const nanos = Number(time.substr(-9)) * sign;
this.setTime(seconds * 1000);
return this.setNanoseconds(nanos);
}
/**
* Sets the PreciseDate object to the time represented by a number of
* milliseconds since January 1, 1970, 00:00:00 UTC. Calling this method will
* reset both the microseconds and nanoseconds to 0.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setTime|Date#setTime}
*
* @param {number} time Value representing the number of milliseconds since
* January 1, 1970, 00:00:00 UTC.
* @returns {string} The number of milliseconds between January 1, 1970,
* 00:00:00 UTC and the updated date (effectively, the value of the
* argument).
*/
setTime(time) {
this._micros = 0;
this._nanos = 0;
return super.setTime(time);
}
/**
* Returns a string in RFC 3339 format. Unlike the native `Date#toISOString`,
* this will return 9 digits to represent sub-second precision.
*
* @see {@link https://tools.ietf.org/html/rfc3339|RFC 3339}
*
* @returns {string}
*
* @example
* const date = new PreciseDate(1549622069481145231n);
*
* console.log(date.toISOString());
* // expected output: "2019-02-08T10:34:29.481145231Z"
*/
toISOString() {
const micros = padLeft(this._micros, 3);
const nanos = padLeft(this._nanos, 3);
return super.toISOString().replace(/z$/i, `${micros}${nanos}Z`);
}
/**
* Returns an object representing the specified date according to universal
* time.
*
* @see {@link https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#timestamp|google.protobuf.Timestamp}
*
* @returns {DateStruct}
*
* @example
* const date = new PreciseDate('2019-02-08T10:34:29.481145231Z');
*
* console.log(date.toStruct());
* // expected output: {seconds: 1549622069, nanos: 481145231}
*/
toStruct() {
let seconds = this._getSeconds();
const nanos = this._getNanos();
const sign = Math.sign(seconds);
// These objects are essentially a mirror of protobuf timestamps.
// `nanos` must always count forward in time, even if the date is <= Unix
// epoch. To do this we just need to count backwards 1 second and return the
// nanoseconds as is.
if (sign === Sign.NEGATIVE && nanos) {
seconds -= 1;
}
return { seconds, nanos };
}
/**
* Returns a tuple representing the specified date according to universal
* time.
*
* @returns {DateTuple}
*
* @example
* const date = new PreciseDate('2019-02-08T10:34:29.481145231Z');
*
* console.log(date.toTuple());
* // expected output: [1549622069, 481145231]
*/
toTuple() {
const { seconds, nanos } = this.toStruct();
return [seconds, nanos];
}
/**
* Returns the total number of seconds in the specified date since Unix epoch.
* Numbers representing < epoch will be negative.
*
* @private
*
* @returns {number}
*/
_getSeconds() {
const time = this.getTime();
const sign = Math.sign(time);
return Math.floor(Math.abs(time) / 1000) * sign;
}
/**
* Returns the sub-second precision of the specified date. This will always be
* a positive number.
*
* @private
*
* @returns {number}
*/
_getNanos() {
const msInNanos = this.getUTCMilliseconds() * 1e6;
const microsInNanos = this._micros * 1000;
return this._nanos + msInNanos + microsInNanos;
}
/**
* Parses a precise time.
*
* @static
*
* @param {string|bigint|DateTuple|DateStruct} time The precise time value.
* @returns {string} Returns a string representing the nanoseconds in the
* specified date according to universal time.
*
* @example From a RFC 3339 formatted string.
* const time = PreciseDate.parseFull('2019-02-08T10:34:29.481145231Z');
* console.log(time); // expected output: "1549622069481145231"
*
* @example From a nanosecond timestamp string.
* const time = PreciseDate.parseFull('1549622069481145231');
* console.log(time); // expected output: "1549622069481145231"
*
* @example From a BigInt (requires Node >= v10.7)
* const time = PreciseDate.parseFull(1549622069481145231n);
* console.log(time); // expected output: "1549622069481145231"
*
* @example From a tuple.
* const time = PreciseDate.parseFull([1549622069, 481145231]);
* console.log(time); // expected output: "1549622069481145231"
*
* @example From an object.
* const struct = {seconds: 1549622069, nanos: 481145231};
* const time = PreciseDate.parseFull(struct);
* console.log(time); // expected output: "1549622069481145231"
*/
static parseFull(time) {
const date = new PreciseDate();
if (Array.isArray(time)) {
const [seconds, nanos] = time;
time = { seconds, nanos };
}
if (isFullTime(time)) {
date.setFullTime(time);
}
else if (isStruct(time)) {
const { seconds, nanos } = parseProto(time);
date.setTime(seconds * 1000);
date.setNanoseconds(nanos);
}
else if (isFullISOString(time)) {
date.setFullTime(parseFullISO(time));
}
else {
date.setTime(new Date(time).getTime());
}
return date.getFullTimeString();
}
/**
* Accepts the same number parameters as the PreciseDate constructor, but
* treats them as UTC. It returns a string that represents the number of
* nanoseconds since January 1, 1970, 00:00:00 UTC.
*
* **NOTE:** Because this method returns a `BigInt` it requires Node >= v10.7.
*
* @see {@link https://github.com/tc39/proposal-bigint|BigInt}
*
* @static
*
* @throws {error} If `BigInt` is unavailable.
*
* @param {...number} [dateFields] The date fields.
* @returns {bigint}
*
* @example
* const time = PreciseDate.fullUTC(2019, 1, 8, 10, 34, 29, 481, 145, 231);
* console.log(time); // expected output: 1549622069481145231n
*/
static fullUTC(...args) {
if (typeof BigInt !== 'function') {
throw new Error(NO_BIG_INT);
}
return BigInt(PreciseDate.fullUTCString(...args));
}
/**
* Accepts the same number parameters as the PreciseDate constructor, but
* treats them as UTC. It returns a string that represents the number of
* nanoseconds since January 1, 1970, 00:00:00 UTC.
*
* @static
*
* @param {...number} [dateFields] The date fields.
* @returns {string}
*
* @example
* const time = PreciseDate.fullUTCString(2019, 1, 8, 10, 34, 29, 481, 145,
* 231); console.log(time); // expected output: '1549622069481145231'
*/
static fullUTCString(...args) {
const milliseconds = Date.UTC(...args.slice(0, 7));
const date = new PreciseDate(milliseconds);
if (args.length === 9) {
date.setNanoseconds(args.pop());
}
if (args.length === 8) {
date.setMicroseconds(args.pop());
}
return date.getFullTimeString();
}
}
exports.PreciseDate = PreciseDate;
/**
* Parses a RFC 3339 formatted string representation of the date, and returns
* a string representing the nanoseconds since January 1, 1970, 00:00:00.
*
* @private
*
* @param {string} time The RFC 3339 formatted string.
* @returns {string}
*/
function parseFullISO(time) {
let digits = '0';
time = time.replace(/\.(\d+)/, ($0, $1) => {
digits = $1;
return '.000';
});
const nanos = Number(padRight(digits, 9));
const date = new PreciseDate(time);
return date.setNanoseconds(nanos);
}
/**
* Normalizes a {@link google.protobuf.Timestamp} object.
*
* @private
*
* @param {google.protobuf.Timestamp} timestamp The timestamp object.
* @returns {DateStruct}
*/
function parseProto({ seconds = 0, nanos = 0 }) {
if (typeof seconds.toNumber === 'function') {
seconds = seconds.toNumber();
}
seconds = Number(seconds);
nanos = Number(nanos);
return { seconds, nanos };
}
/**
* Checks to see if time value is specified in nanoseconds. We assume that all
* BigInt and string timestamps represent nanoseconds.
*
* @private
*
* @param {*} time The time to check.
* @returns {boolean}
*/
function isFullTime(time) {
return (typeof time === 'bigint' || (typeof time === 'string' && /^\d+$/.test(time)));
}
/**
* Checks to see if time value is a {@link DateStruct}.
*
* @private
*
* @param {*} time The time to check.
* @returns {boolean}
*/
function isStruct(time) {
return ((typeof time === 'object' &&
typeof time.seconds !== 'undefined') ||
typeof time.nanos === 'number');
}
/**
* Checks to see if the time value is a RFC 3339 formatted string.
*
* @private
*
* @param {*} time The time to check.
* @returns {boolean}
*/
function isFullISOString(time) {
return typeof time === 'string' && FULL_ISO_REG.test(time);
}
/**
* Pads a number/string with "0" to the left.
*
* @private
*
* @param {string|number} n The number/string to pad.
* @param {number} min The min size of the padded string.
* @returns {string}
*/
function padLeft(n, min) {
const padding = getPadding(n, min);
return `${padding}${n}`;
}
/**
* Pads a number/string with "0" to the right.
*
* @private
*
* @param {string|number} n The number/string to pad.
* @param {number} min The min size of the padded string.
* @returns {string}
*/
function padRight(n, min) {
const padding = getPadding(n, min);
return `${n}${padding}`;
}
/**
* Creates padding based on current size and min size needed.
*
* @private
*
* @param {string|number} n The number/string to pad.
* @param {number} [min=3] The min size of the padded string.
* @returns {string}
*/
function getPadding(n, min) {
const size = Math.max(min - n.toString().length, 0);
return '0'.repeat(size);
}
//# sourceMappingURL=index.js.map