@jony_starc/rostime
Version:
ROS (Robot Operating System) Time and Duration primitives and helper methods
402 lines (401 loc) • 15.5 kB
JavaScript
;
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright 2018-2021 Cruise LLC
//
// This source code is licensed under the Apache License, Version 2.0,
// found at http://www.apache.org/licenses/LICENSE-2.0
// You may not use this file except in compliance with the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.isTime = isTime;
exports.toString = toString;
exports.fromString = fromString;
exports.toRFC3339String = toRFC3339String;
exports.fromRFC3339String = fromRFC3339String;
exports.toDate = toDate;
exports.fromDate = fromDate;
exports.percentOf = percentOf;
exports.interpolate = interpolate;
exports.fixTime = fixTime;
exports.add = add;
exports.subtract = subtract;
exports.toNanoSec = toNanoSec;
exports.toMicroSec = toMicroSec;
exports.toSec = toSec;
exports.fromSec = fromSec;
exports.fromNanoSec = fromNanoSec;
exports.toMillis = toMillis;
exports.fromMillis = fromMillis;
exports.fromMicros = fromMicros;
exports.clampTime = clampTime;
exports.isTimeInRangeInclusive = isTimeInRangeInclusive;
exports.compare = compare;
exports.isLessThan = isLessThan;
exports.isGreaterThan = isGreaterThan;
exports.areEqual = areEqual;
/**
* Test if a given object matches the signature of { sec: number; nsec: number }
* @param obj Object to test
* @returns True if the object is equivalent to a Time object, otherwise false
*/
function isTime(obj) {
return (typeof obj === "object" &&
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
!!obj &&
"sec" in obj &&
"nsec" in obj &&
Object.getOwnPropertyNames(obj).length === 2);
}
/**
* Converts a Time to a string containing a floating point number of seconds
* @param stamp Time to convert
* @param allowNegative Allow negative times to be converted
* @returns String timestamp containing a floating point number of seconds
*/
function toString(stamp, allowNegative) {
if (allowNegative === void 0) { allowNegative = false; }
if (!allowNegative && (stamp.sec < 0 || stamp.nsec < 0)) {
throw new Error("Invalid negative time { sec: ".concat(stamp.sec, ", nsec: ").concat(stamp.nsec, " }"));
}
var sec = Math.floor(stamp.sec);
var nsec = Math.floor(stamp.nsec);
return "".concat(sec, ".").concat(nsec.toFixed().padStart(9, "0"));
}
/**
* Parse fractional seconds (digits following a decimal separator ".") and interpret them as an
* integer number of nanoseconds. Because of the rounding behavior, this function may return 1e9 (a
* value that would be too large for the `nsec` field).
*/
function parseNanoseconds(digits) {
// There can be 9 digits of nanoseconds. If the fractional part is "1", we need to add eight
// zeros. Also, make sure we round to an integer if we need to _remove_ digits.
var digitsShort = 9 - digits.length;
return Math.round(parseInt(digits, 10) * Math.pow(10, digitsShort));
}
/**
* Converts a string containing floating point number of seconds to a Time. We use a string because
* nanosecond precision cannot be stored in a 64-bit float for large values (e.g. UNIX timestamps).
* @param stamp UNIX timestamp containing a whole or floating point number of seconds
* @returns Time object on success, undefined on failure
*/
function fromString(stamp) {
if (/^\d+\.?$/.test(stamp)) {
// Whole number with optional "." at the end.
var sec_1 = parseInt(stamp, 10);
return { sec: isNaN(sec_1) ? 0 : sec_1, nsec: 0 };
}
if (!/^\d+\.\d+$/.test(stamp)) {
// Not digits.digits -- invalid.
return undefined;
}
var partials = stamp.split(".");
if (partials.length === 0) {
return undefined;
}
var first = partials[0], second = partials[1];
if (first == undefined || second == undefined) {
return undefined;
}
// It's possible we rounded to { sec: 1, nsec: 1e9 }, which is invalid, so fixTime.
var sec = parseInt(first, 10);
var nsec = parseNanoseconds(second);
return fixTime({ sec: isNaN(sec) ? 0 : sec, nsec: nsec });
}
/**
* Converts a Time to a string compatible with RFC3339/ISO8601. Similar to
* `toDate(stamp).toISOString()`, but with nanosecond precision.
* @param stamp Time to convert
*/
function toRFC3339String(stamp) {
if (stamp.sec < 0 || stamp.nsec < 0) {
throw new Error("Invalid negative time { sec: ".concat(stamp.sec, ", nsec: ").concat(stamp.nsec, " }"));
}
if (stamp.nsec >= 1e9) {
throw new Error("Invalid nanosecond value ".concat(stamp.nsec));
}
var date = new Date(stamp.sec * 1000);
var year = date.getUTCFullYear();
var month = (date.getUTCMonth() + 1).toFixed().padStart(2, "0");
var day = date.getUTCDate().toFixed().padStart(2, "0");
var hour = date.getUTCHours().toFixed().padStart(2, "0");
var minute = date.getUTCMinutes().toFixed().padStart(2, "0");
var second = date.getUTCSeconds().toFixed().padStart(2, "0");
var nanosecond = stamp.nsec.toFixed().padStart(9, "0");
return "".concat(year, "-").concat(month, "-").concat(day, "T").concat(hour, ":").concat(minute, ":").concat(second, ".").concat(nanosecond, "Z");
}
/**
* Parses a Time from a string compatible with a subset of ISO8601/RFC3339. Similar to
* `fromDate(new Date(string))`, but with nanosecond precision.
* @param stamp Time to convert
*/
function fromRFC3339String(stamp) {
var match = /^(\d{4,})-(\d\d)-(\d\d)[Tt](\d\d):(\d\d):(\d\d)(?:\.(\d+))?(?:[Zz]|([+-])(\d\d):(\d\d))$/.exec(stamp);
if (match == null) {
return undefined;
}
var year = match[1], month = match[2], day = match[3], hour = match[4], minute = match[5], second = match[6], frac = match[7], plusMinus = match[8], offHours = match[9], offMinutes = match[10];
var offSign = plusMinus === "-" ? -1 : 1;
var utcMillis = Date.UTC(+year, +month - 1, +day, +hour - offSign * +(offHours !== null && offHours !== void 0 ? offHours : 0), +minute - offSign * +(offMinutes !== null && offMinutes !== void 0 ? offMinutes : 0), +second);
if (utcMillis % 1000 !== 0) {
return undefined;
}
// It's possible we rounded to { sec: 1, nsec: 1e9 }, which is invalid, so fixTime.
return fixTime({
sec: utcMillis / 1000,
nsec: frac != undefined ? parseNanoseconds(frac) : 0,
});
}
/**
* Convert a Time to a JavaScript Date object. NOTE: sub-millisecond precision is lost.
* @param stamp Time to convert
* @returns Date representing the given Time as accurately as it can
*/
function toDate(stamp) {
var sec = stamp.sec, nsec = stamp.nsec;
return new Date(sec * 1000 + nsec / 1e6);
}
/**
* Conver a JavaScript Date object to a Time.
* @param date Date to convert
* @returns Time representing the given Date
*/
function fromDate(date) {
var millis = date.getTime();
var remainder = millis % 1000;
return { sec: Math.floor(millis / 1000), nsec: remainder * 1e6 };
}
/**
* Returns the fraction representing target's position in the range between start and end.
* e.g. start = { sec: 0 }, end = { sec: 10 }, target = { sec: 5 } = 0.5
* This is the reverse of the `interpolate()` method
* @param start Start timestamp of the interpolation range
* @param end End timestamp of the interpolation range
* @param target Timestamp that will be measured relative to the interpolation range
* @returns If target falls in between start and end (inclusive), it will be in the range [0.0-1.0].
* Otherwise, it is unbounded
*/
function percentOf(start, end, target) {
var totalDuration = subtract(end, start);
var targetDuration = subtract(target, start);
return toSec(targetDuration) / toSec(totalDuration);
}
/**
* Linearly interpolate the range between start and end by a given fraction.
* e.g. start = { sec: 0 }, end = { sec: 10 }, fraction = 0.5 = { sec: 5 }
* This is the reverse of the `percentOf` method
* @param start Start timestamp of the interpolation range
* @param end End timestamp of the interpolation range
* @param fraction Percent to interpolate along the range
* @returns If fraction is in the range [0.0-1.0], the target will fall in between start and end\
* (inclusive). Otherwise, it is unbounded
*/
function interpolate(start, end, fraction) {
var duration = subtract(end, start);
return add(start, fromSec(fraction * toSec(duration)));
}
/**
* Equivalent to fromNanoSec(toNanoSec(t)), but no chance of precision loss. nsec should be
* non-negative, and less than 1e9.
* @param t Potentially un-normalized time with the nsec (nanoseconds) value containing a value
* higher than one second (1e9)
* @param allowNegative Allow negative times to be normalized
* @returns A normalized Time
*/
function fixTime(t, allowNegative) {
if (allowNegative === void 0) { allowNegative = false; }
var durationNanos = t.nsec;
var secsFromNanos = Math.floor(durationNanos / 1e9);
var newSecs = t.sec + secsFromNanos;
var remainingDurationNanos = durationNanos % 1e9;
// use Math.abs here to prevent -0 when there is exactly 1 second of negative nanoseconds passed in
var newNanos = Math.abs(Math.sign(remainingDurationNanos) === -1
? 1e9 + remainingDurationNanos
: remainingDurationNanos);
var result = { sec: newSecs, nsec: newNanos };
if ((!allowNegative && result.sec < 0) || result.nsec < 0) {
throw new Error("Cannot normalize invalid time ".concat(toString(result, true)));
}
return result;
}
/**
* Add two Times together
* @param param0 First Time
* @param param1 Second Time
* @returns A normalized representation of the two Time objects added together
*/
function add(_a, _b) {
var sec1 = _a.sec, nsec1 = _a.nsec;
var sec2 = _b.sec, nsec2 = _b.nsec;
return fixTime({ sec: sec1 + sec2, nsec: nsec1 + nsec2 });
}
/**
* Subtract one Time from another
* @param param0 First Time
* @param param1 Time to subtract from the first Time
* @returns A normalized representation of the second Time subtracted from the first
*/
function subtract(_a, _b) {
var sec1 = _a.sec, nsec1 = _a.nsec;
var sec2 = _b.sec, nsec2 = _b.nsec;
return fixTime({ sec: sec1 - sec2, nsec: nsec1 - nsec2 }, true);
}
/**
* Convert Time to an integer number of nanoseconds
* @param param0 Time to convert
* @returns A bigint integer number of nanoseconds
*/
function toNanoSec(_a) {
var sec = _a.sec, nsec = _a.nsec;
return BigInt(sec) * 1000000000n + BigInt(nsec);
}
/**
* Convert Time to a floating point number of microseconds
* @param param0 Time to convert
* @returns A floating point number of microseconds
*/
function toMicroSec(_a) {
var sec = _a.sec, nsec = _a.nsec;
return (sec * 1e9 + nsec) / 1000;
}
/**
* Convert Time to a floating point number of seconds
* @param param0 Time to convert
* @returns A floating point number of seconds
*/
function toSec(_a) {
var sec = _a.sec, nsec = _a.nsec;
return sec + nsec * 1e-9;
}
/**
* Convert a floating point number of seconds to Time
* @param value Number of seconds
* @returns Time object
*/
function fromSec(value) {
// From https://github.com/ros/roscpp_core/blob/indigo-devel/rostime/include/ros/time.h#L153
var sec = Math.trunc(value);
var nsec = Math.round((value - sec) * 1e9);
sec += Math.trunc(nsec / 1e9);
nsec %= 1e9;
return { sec: sec, nsec: nsec };
}
/**
* Convert an integer number of nanoseconds to Time
* @param nsec Nanoseconds integer
* @returns Time object
*/
function fromNanoSec(nsec) {
// From https://github.com/ros/roscpp_core/blob/86720717c0e1200234cc0a3545a255b60fb541ec/rostime/include/ros/impl/time.h#L63
// and https://github.com/ros/roscpp_core/blob/7583b7d38c6e1c2e8623f6d98559c483f7a64c83/rostime/src/time.cpp#L536
//
// Note: BigInt(1e9) is slower than writing out the number
return { sec: Number(nsec / 1000000000n), nsec: Number(nsec % 1000000000n) };
}
/**
* Convert Time to an integer number of milliseconds
* @param time Time to convert
* @param roundUp Round up to nearest millisecond if true, otherwise round down. Defaults to true
* @returns Integer number of milliseconds
*/
function toMillis(time, roundUp) {
if (roundUp === void 0) { roundUp = true; }
var secondsMillis = time.sec * 1e3;
var nsecMillis = time.nsec / 1e6;
return roundUp ? secondsMillis + Math.ceil(nsecMillis) : secondsMillis + Math.floor(nsecMillis);
}
/**
* Convert milliseconds to Time
* @param value Milliseconds number
* @returns Time object
*/
function fromMillis(value) {
var sec = Math.trunc(value / 1000);
var nsec = Math.round((value - sec * 1000) * 1e6);
sec += Math.trunc(nsec / 1e9);
nsec %= 1e9;
return { sec: sec, nsec: nsec };
}
/**
* Convert microseconds to Time
* @param value Microseconds number
* @returns Time object
*/
function fromMicros(value) {
var sec = Math.trunc(value / 1e6);
var nsec = Math.round((value - sec * 1e6) * 1e3);
sec += Math.trunc(nsec / 1e9);
nsec %= 1e9;
return { sec: sec, nsec: nsec };
}
/**
* Clamp a given time value in the range from start to end (inclusive)
* @param time Time to clamp
* @param start Start of the target range
* @param end End of the target range
* @returns Clamped Time
*/
function clampTime(time, start, end) {
if (compare(start, time) > 0) {
return { sec: start.sec, nsec: start.nsec };
}
if (compare(end, time) < 0) {
return { sec: end.sec, nsec: end.nsec };
}
return { sec: time.sec, nsec: time.nsec };
}
/**
* Test if a given time is inside a test range
* @param time Time to test
* @param start Start of the test range
* @param end End of the test range
* @returns True if time falls in between start and end (inclusive)
*/
function isTimeInRangeInclusive(time, start, end) {
if (compare(start, time) > 0 || compare(end, time) < 0) {
return false;
}
return true;
}
/**
* Comparison function for Time object that can be used for sorting
* @param left First Time to compare
* @param right Second Time to compare
* @returns A positive value if left is larger than right, a negative value if right is larger than
* left, or zero if both times are equal
*/
function compare(left, right) {
var secDiff = left.sec - right.sec;
return secDiff !== 0 ? secDiff : left.nsec - right.nsec;
}
/**
* Returns true if the left time is less than the right time, otherwise false
* @param left Left side of comparison
* @param right Right side of comparison
* @returns Comparison result
*/
function isLessThan(left, right) {
return compare(left, right) < 0;
}
/**
* Returns true if the left time is greater than the right time, otherwise false
* @param left Left side of the comparison
* @param right Right side of the comparison
* @returns Comparison result
*/
function isGreaterThan(left, right) {
return compare(left, right) > 0;
}
/**
* Returns true if both times have the same number of seconds and nanoseconds
* @param left Left side of the comparison
* @param right Right side of the comparison
* @returns Equality result
*/
function areEqual(left, right) {
return left.sec === right.sec && left.nsec === right.nsec;
}