Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 61 additions & 103 deletions lib/types/duration.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit Refactor duration.js:
♻️ This should be two commits:

  1. Replace internal uses of Long to BigInt.
  2. Replace util.format() to "${}" syntax.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use strict";
const Long = require("long");
const util = require("util");
const utils = require("../utils");
const {
bigintToLong,
Expand All @@ -18,12 +17,12 @@ const reusableBuffers = {
};

const maxInt32 = 0x7fffffff;
const longOneThousand = Long.fromInt(1000);
const nanosPerMicro = longOneThousand;
const nanosPerMilli = longOneThousand.multiply(nanosPerMicro);
const nanosPerSecond = longOneThousand.multiply(nanosPerMilli);
const nanosPerMinute = Long.fromInt(60).multiply(nanosPerSecond);
const nanosPerHour = Long.fromInt(60).multiply(nanosPerMinute);
const maxInt64 = 0x7fffffffffffffffn;
const nanosPerMicro = 1000n;
const nanosPerMilli = 1000n * nanosPerMicro;
const nanosPerSecond = 1000n * nanosPerMilli;
const nanosPerMinute = 60n * nanosPerSecond;
const nanosPerHour = 60n * nanosPerMinute;
const daysPerWeek = 7;
const monthsPerYear = 12;
const standardRegex = /(\d+)(y|mo|w|d|h|s|ms|us|µs|ns|m)/gi;
Expand Down Expand Up @@ -97,7 +96,7 @@ class Duration {
return (
this.months === other.months &&
this.days === other.days &&
this.nanoseconds.compare(other.nanoseconds) === 0
this.#nanoseconds === other.#nanoseconds
);
}

Expand Down Expand Up @@ -156,17 +155,16 @@ class Duration {
* @returns {Buffer}
*/
toBuffer() {
let nanoseconds = bigintToLong(this.#nanoseconds);
const lengthMonths = utils.VIntCoding.writeVInt(
Long.fromNumber(this.#months),
BigInt(this.#months),
reusableBuffers.months,
);
const lengthDays = utils.VIntCoding.writeVInt(
Long.fromNumber(this.#days),
BigInt(this.#days),
reusableBuffers.days,
);
const lengthNanoseconds = utils.VIntCoding.writeVInt(
nanoseconds,
this.#nanoseconds,
reusableBuffers.nanoseconds,
);
const buffer = utils.allocBufferUnsafe(
Expand All @@ -185,41 +183,39 @@ class Duration {
* @return {string}
*/
toString() {
let nanoseconds = bigintToLong(this.#nanoseconds);
let value = "";
function append(dividend, divisor, unit) {
if (dividend === 0 || dividend < divisor) {
return dividend;
}
// string concatenation is supposed to be faster than join()
value += (dividend / divisor).toFixed(0) + unit;
value += Math.floor(dividend / divisor) + unit;
return dividend % divisor;
}
function append64(dividend, divisor, unit) {
if (dividend.equals(Long.ZERO) || dividend.lessThan(divisor)) {
if (dividend === 0n || dividend < divisor) {
return dividend;
}
// string concatenation is supposed to be faster than join()
value += dividend.divide(divisor).toString() + unit;
return dividend.modulo(divisor);
value += (dividend / divisor).toString() + unit;
return dividend % divisor;
}
if (this.#months < 0 || this.#days < 0 || nanoseconds.isNegative()) {
if (this.#months < 0 || this.#days < 0 || this.#nanoseconds < 0) {
value = "-";
}
let remainder = append(Math.abs(this.#months), monthsPerYear, "y");
append(remainder, 1, "mo");
append(Math.abs(this.#days), 1, "d");

if (!nanoseconds.equals(Long.ZERO)) {
const nanos = nanoseconds.isNegative()
? nanoseconds.negate()
: nanoseconds;
if (this.#nanoseconds !== 0n) {
const nanos =
this.#nanoseconds < 0n ? -this.#nanoseconds : this.#nanoseconds;
remainder = append64(nanos, nanosPerHour, "h");
remainder = append64(remainder, nanosPerMinute, "m");
remainder = append64(remainder, nanosPerSecond, "s");
remainder = append64(remainder, nanosPerMilli, "ms");
remainder = append64(remainder, nanosPerMicro, "us");
append64(remainder, Long.ONE, "ns");
append64(remainder, 1n, "ns");
}
return value;
}
Expand All @@ -231,8 +227,8 @@ class Duration {
*/
static fromBuffer(buffer) {
const offset = { value: 0 };
const months = utils.VIntCoding.readVInt(buffer, offset).toNumber();
const days = utils.VIntCoding.readVInt(buffer, offset).toNumber();
const months = Number(utils.VIntCoding.readVInt(buffer, offset));
const days = Number(utils.VIntCoding.readVInt(buffer, offset));
const nanoseconds = utils.VIntCoding.readVInt(buffer, offset);
return new Duration(months, days, nanoseconds);
}
Expand Down Expand Up @@ -304,9 +300,7 @@ function parseStandardFormat(isNegative, source) {
function parseIso8601Format(isNegative, source) {
const matches = iso8601Regex.exec(source);
if (!matches || matches[0] !== source) {
throw new TypeError(
util.format("Unable to convert '%s' to a duration", source),
);
throw new TypeError(`Unable to convert '${source}' to a duration`);
}
const builder = new Builder(isNegative);
if (matches[1]) {
Expand Down Expand Up @@ -341,9 +335,7 @@ function parseIso8601Format(isNegative, source) {
function parseIso8601WeekFormat(isNegative, source) {
const matches = iso8601WeekRegex.exec(source);
if (!matches || matches[0] !== source) {
throw new TypeError(
util.format("Unable to convert '%s' to a duration", source),
);
throw new TypeError(`Unable to convert '${source}' to a duration`);
}
return new Builder(isNegative).addWeeks(matches[1]).build();
}
Expand All @@ -357,9 +349,7 @@ function parseIso8601WeekFormat(isNegative, source) {
function parseIso8601AlternativeFormat(isNegative, source) {
const matches = iso8601AlternateRegex.exec(source);
if (!matches || matches[0] !== source) {
throw new TypeError(
util.format("Unable to convert '%s' to a duration", source),
);
throw new TypeError(`Unable to convert '${source}' to a duration`);
}
return new Builder(isNegative)
.addYears(matches[1])
Expand All @@ -382,7 +372,7 @@ class Builder {
this._unitIndex = 0;
this._months = 0;
this._days = 0;
this._nanoseconds = Long.ZERO;
this._nanoseconds = 0n;
this._addMethods = {
y: this.addYears,
mo: this.addMonths,
Expand Down Expand Up @@ -414,20 +404,13 @@ class Builder {
#validateOrder(unitIndex) {
if (unitIndex === this._unitIndex) {
throw new TypeError(
util.format(
"Invalid duration. The %s are specified multiple times",
this.#getUnitName(unitIndex),
),
`Invalid duration. The ${this.#getUnitName(unitIndex)} are specified multiple times`,
);
}

if (unitIndex <= this._unitIndex) {
throw new TypeError(
util.format(
"Invalid duration. The %s should be after %s",
this.#getUnitName(this._unitIndex),
this.#getUnitName(unitIndex),
),
`Invalid duration. The ${this.#getUnitName(this._unitIndex)} should be after ${this.#getUnitName(unitIndex)}`,
);
}
this._unitIndex = unitIndex;
Expand All @@ -437,9 +420,10 @@ class Builder {
* @param {Number} monthsPerUnit
*/
#validateMonths(units, monthsPerUnit) {
const maxMonths = maxInt32 + (this._isNegative ? 1 : 0);
this.#validate32(
units,
(maxInt32 - this._months) / monthsPerUnit,
(maxMonths - this._months) / monthsPerUnit,
"months",
);
}
Expand All @@ -448,16 +432,18 @@ class Builder {
* @param {Number} daysPerUnit
*/
#validateDays(units, daysPerUnit) {
this.#validate32(units, (maxInt32 - this._days) / daysPerUnit, "days");
const maxDays = maxInt32 + (this._isNegative ? 1 : 0);
this.#validate32(units, (maxDays - this._days) / daysPerUnit, "days");
}
/**
* @param {Long} units
* @param {Long} nanosPerUnit
* @param {bigint} units
* @param {bigint} nanosPerUnit
*/
#validateNanos(units, nanosPerUnit) {
const maxNanos = maxInt64 + (this._isNegative ? 1n : 0n);
this.#validate64(
units,
Long.MAX_VALUE.subtract(this._nanoseconds).divide(nanosPerUnit),
(maxNanos - this._nanoseconds) / nanosPerUnit,
"nanoseconds",
);
}
Expand All @@ -469,27 +455,19 @@ class Builder {
#validate32(units, limit, unitName) {
if (units > limit) {
throw new TypeError(
util.format(
"Invalid duration. The total number of %s must be less or equal to %s",
unitName,
maxInt32,
),
`Invalid duration. The total number of ${unitName} must fit in a 32 bit signed integer.`,
);
}
}
/**
* @param {Long} units
* @param {Long} limit
* @param {bigint} units
* @param {bigint} limit
* @param {String} unitName
*/
#validate64(units, limit, unitName) {
if (units.greaterThan(limit)) {
if (units > limit) {
throw new TypeError(
util.format(
"Invalid duration. The total number of %s must be less or equal to %s",
unitName,
Long.MAX_VALUE.toString(),
),
`Invalid duration. The total number of ${unitName} must fit in a 64 bit signed integer.`,
);
}
}
Expand All @@ -503,9 +481,7 @@ class Builder {
add(textValue, symbol) {
const addMethod = this._addMethods[symbol.toLowerCase()];
if (!addMethod) {
throw new TypeError(
util.format("Unknown duration symbol '%s'", symbol),
);
throw new TypeError(`Unknown duration symbol '${symbol}'`);
}
return addMethod.call(this, textValue);
}
Expand Down Expand Up @@ -554,93 +530,75 @@ class Builder {
return this;
}
/**
* @param {String|Long} hours
* @param {String|Number|BigInt} hours
* @return {Builder}
*/
addHours(hours) {
const value =
typeof hours === "string" ? Long.fromString(hours) : hours;
const value = BigInt(hours);
this.#validateOrder(5);
this.#validateNanos(value, nanosPerHour);
this._nanoseconds = this._nanoseconds.add(value.multiply(nanosPerHour));
this._nanoseconds += value * nanosPerHour;
return this;
}
/**
* @param {String|Long} minutes
* @param {String|Number|BigInt} minutes
* @return {Builder}
*/
addMinutes(minutes) {
const value =
typeof minutes === "string" ? Long.fromString(minutes) : minutes;
const value = BigInt(minutes);
this.#validateOrder(6);
this.#validateNanos(value, nanosPerMinute);
this._nanoseconds = this._nanoseconds.add(
value.multiply(nanosPerMinute),
);
this._nanoseconds += value * nanosPerMinute;
return this;
}
/**
* @param {String|Long} seconds
* @param {String|Number|BigInt} seconds
* @return {Builder}
*/
addSeconds(seconds) {
const value =
typeof seconds === "string" ? Long.fromString(seconds) : seconds;
const value = BigInt(seconds);
this.#validateOrder(7);
this.#validateNanos(value, nanosPerSecond);
this._nanoseconds = this._nanoseconds.add(
value.multiply(nanosPerSecond),
);
this._nanoseconds += value * nanosPerSecond;
return this;
}
/**
* @param {String|Long} millis
* @param {String|Number|BigInt} millis
* @return {Builder}
*/
addMillis(millis) {
const value =
typeof millis === "string" ? Long.fromString(millis) : millis;
const value = BigInt(millis);
this.#validateOrder(8);
this.#validateNanos(value, nanosPerMilli);
this._nanoseconds = this._nanoseconds.add(
value.multiply(nanosPerMilli),
);
this._nanoseconds += value * nanosPerMilli;
return this;
}
/**
* @param {String|Long} micros
* @param {String|Number|BigInt} micros
* @return {Builder}
*/
addMicros(micros) {
const value =
typeof micros === "string" ? Long.fromString(micros) : micros;
const value = BigInt(micros);
this.#validateOrder(9);
this.#validateNanos(value, nanosPerMicro);
this._nanoseconds = this._nanoseconds.add(
value.multiply(nanosPerMicro),
);
this._nanoseconds += value * nanosPerMicro;
return this;
}
/**
* @param {String|Long} nanos
* @param {String|Number|BigInt} nanos
* @return {Builder}
*/
addNanos(nanos) {
const value =
typeof nanos === "string" ? Long.fromString(nanos) : nanos;
const value = BigInt(nanos);
this.#validateOrder(10);
this.#validateNanos(value, Long.ONE);
this._nanoseconds = this._nanoseconds.add(value);
this.#validateNanos(value, 1n);
this._nanoseconds += value;
return this;
}
/** @return {Duration} */
build() {
return this._isNegative
? new Duration(
-this._months,
-this._days,
this._nanoseconds.negate(),
)
? new Duration(-this._months, -this._days, -this._nanoseconds)
: new Duration(this._months, this._days, this._nanoseconds);
}
}
Expand Down
Loading
Loading