@toreda/time
Version:
Simple, small footprint library for common time operations and converting between units of time.
320 lines (319 loc) • 12.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimeData = void 0;
const utils_1 = require("./utils");
const like_1 = require("../log/like");
const type_1 = require("./check/type");
const convert_1 = require("./convert");
const make_1 = require("./make");
const supported_1 = require("./unit/supported");
const FALLBACK_UNIT = 's';
function isSafeFiniteNumber(value) {
return typeof value === 'number' && isFinite(value) && utils_1.TimeUtils.withinSafeRange(value);
}
/**
* Internal state data created and wrapped by Time instances. All mutators
* are silent no-ops on bad input: the failure is logged and `caller` is
* returned unchanged.
*/
class TimeData {
constructor(units, value, log) {
this.log = (0, like_1.logLike)(log) ? log : console;
let safeUnits = units;
if (!(0, supported_1.timeUnitSupported)(units)) {
this.log.error(`TimeData.constructor:`, `units arg is not a supported TimeUnit; falling back to '${FALLBACK_UNIT}'.`);
safeUnits = FALLBACK_UNIT;
}
let safeValue = value;
if (!isSafeFiniteNumber(value)) {
this.log.error(`TimeData.constructor:`, `value arg is not a finite number in safe range; falling back to 0.`);
safeValue = 0;
}
this._units = safeUnits;
this.initialUnits = safeUnits;
this.value = safeValue;
this.initialValue = safeValue;
}
/**
* Get the instance's current native time unit.
*/
units() {
return this._units;
}
/**
* Convert the current value into the target time unit and update the
* instance's native time unit to match. Used by in-place conversion
* methods on the wrapping Time instance.
*
* @returns `caller` for chaining. The conversion is a no-op when the
* target is unsupported, the conversion fails, or `target`
* already matches the current unit.
*/
setUnits(caller, target) {
if (!(0, supported_1.timeUnitSupported)(target)) {
this.log.error(`TimeData.setUnits:`, `target arg is not a supported TimeUnit.`);
return caller;
}
if (target === this._units) {
if (!isSafeFiniteNumber(this.value)) {
this.log.error(`TimeData.setUnits:`, `current value is not a finite number in safe range; refusing no-op.`);
return caller;
}
return caller;
}
const converted = (0, convert_1.timeConvert)(this._units, target, this.value);
if (converted === null) {
this.log.error(`TimeData.setUnits:`, `bad timeConvert result for target unit.`);
return caller;
}
this.value = converted;
this._units = target;
return caller;
}
/**
* Get the current time value in instance's native time unit.
*/
get() {
return this.value;
}
/**
* Replace the current value with `input`.
* @returns `caller` for chaining. State is left unchanged when `input`
* is missing, not a finite number in safe range, or a Time
* instance whose conversion fails.
*/
set(caller, input) {
if (input === null || input === undefined) {
this.log.error(`TimeData.set:`, `input arg is missing.`);
return caller;
}
if (typeof input === 'number') {
if (!isSafeFiniteNumber(input)) {
this.log.error(`TimeData.set:`, `input number is not finite or is out of safe range.`);
return caller;
}
this.value = input;
return caller;
}
if (!(0, type_1.timeCheckType)(input)) {
this.log.error(`TimeData.set:`, `input arg is not a valid Time instance.`);
return caller;
}
const updated = (0, convert_1.timeConvert)(input.units(), this._units, input());
if (updated === null) {
this.log.error(`TimeData.set:`, `bad timeConvert result for input.`);
return caller;
}
this.value = updated;
return caller;
}
/**
* Add number input to current value.
* @param caller Time instance calling this method.
* @param input number value to be added.
* @returns `caller` for chaining. State is left unchanged when
* `input` is not a finite number in safe range or the
* resulting sum overflows the safe range.
*/
addNumber(caller, input) {
if (!isSafeFiniteNumber(input)) {
this.log.error(`TimeData.addNumber:`, `input arg is not a finite number in safe range.`);
return caller;
}
const total = this.value + input;
if (!isSafeFiniteNumber(total)) {
this.log.error(`TimeData.addNumber:`, `computed total is not finite or is out of safe range.`);
return caller;
}
this.value = total;
return caller;
}
/**
* Subtract number input from the current value.
* @param caller Time instance calling this method.
* @param input number value to be subtracted.
* @returns `caller` for chaining. State is left unchanged when
* `input` is not a finite number in safe range or the
* resulting difference overflows the safe range.
*/
subNumber(caller, input) {
if (!isSafeFiniteNumber(input)) {
this.log.error(`TimeData.subNumber:`, `input arg is not a finite number in safe range.`);
return caller;
}
const total = this.value - input;
if (!isSafeFiniteNumber(total)) {
this.log.error(`TimeData.subNumber:`, `computed total is not finite or is out of safe range.`);
return caller;
}
this.value = total;
return caller;
}
/**
* Get numeric unit value from a Time or number input.
*
* @param convertTo TimeUnit the result should be expressed in. Used only
* when `input` is a Time instance; raw numbers pass through
* unchanged.
* @param input A Time instance (its value is converted from its native
* unit into `convertTo`), a finite number in safe range
* (returned as-is), or `null`/`undefined`.
* @returns The unit value as a number, or `null` if `input` is
* missing, a non-finite or out-of-range number, not a
* valid Time instance, or fails conversion.
*/
getUnitValue(convertTo, input) {
if (input === null || input === undefined) {
return null;
}
if (typeof input === 'number') {
return isSafeFiniteNumber(input) ? input : null;
}
if (!(0, type_1.timeCheckType)(input)) {
return null;
}
const converted = (0, convert_1.timeConvert)(input.units(), convertTo, input());
return converted;
}
/**
* Convert value from specified units into instance's native time units, then
* subtract it from the current value.
* @param caller Time instance calling this method.
* @param units TimeUnit of the provided value.
* @param value number value to be converted and subtracted.
* @param decimals Optional precision (decimals) for the conversion.
*/
subUnit(caller, units, value, decimals) {
if (!(0, supported_1.timeUnitSupported)(units)) {
this.log.error(`TimeData.subUnit:`, `units arg is not a supported TimeUnit.`);
return caller;
}
if (!isSafeFiniteNumber(value)) {
this.log.error(`TimeData.subUnit:`, `value arg is not a finite number in safe range.`);
return caller;
}
const converted = (0, convert_1.timeConvert)(units, this._units, value, decimals);
if (converted === null) {
this.log.error(`TimeData.subUnit:`, `bad timeConvert result for value.`);
return caller;
}
return this.subNumber(caller, converted);
}
/**
* Convert value from provided unit type into instance's native time units and
* add it to the current value.
* @param caller Time instance calling this function to be returned by function.
* @param units TimeUnit of the provided value.
* @param value Value to be converted and added to current time.
* @param decimals Number of decimals to include in final added value.
*/
addUnit(caller, units, value, decimals) {
if (!(0, supported_1.timeUnitSupported)(units)) {
this.log.error(`TimeData.addUnit:`, `units arg is not a supported TimeUnit.`);
return caller;
}
if (!isSafeFiniteNumber(value)) {
this.log.error(`TimeData.addUnit:`, `value arg is not a finite number in safe range.`);
return caller;
}
const converted = (0, convert_1.timeConvert)(units, this._units, value, decimals);
if (converted === null) {
this.log.error(`TimeData.addUnit:`, `bad timeConvert result for value.`);
return caller;
}
return this.addNumber(caller, converted);
}
/**
* Invert the current value's sign in place.
* @param caller Time instance calling this method.
* @param positivesOnly When true, only positive values are inverted.
* Negative values are left unchanged.
*/
invert(caller, positivesOnly) {
// 0 has no meaningful inversion and `0 * -1 === -0`, which would
// otherwise leak into stored state.
if (this.value === 0) {
return caller;
}
if (positivesOnly === true && this.value < 0) {
return caller;
}
this.value = this.value * -1;
return caller;
}
/**
* Get a Time object representing the elapsed time between the instance's
* current value and a target Time. Returns a negative value when the target
* is in the future. The returned Time uses the instance's native unit.
* @param target
*/
timeSinceTime(target) {
if (!(0, type_1.timeCheckType)(target)) {
this.log.error(`TimeData.timeSinceTime:`, `target arg is not a valid Time instance.`);
return null;
}
const since = (0, convert_1.timeConvert)(target.units(), this._units, target());
if (since === null) {
this.log.error(`TimeData.timeSinceTime:`, `bad timeConvert result for target.`);
return null;
}
return this.timeSinceNumber(since);
}
timeSinceNumber(target) {
if (!isSafeFiniteNumber(target)) {
this.log.error(`TimeData.timeSinceNumber:`, `target arg is not a finite number in safe range.`);
return null;
}
const result = this.value - target;
if (!isSafeFiniteNumber(result)) {
this.log.error(`TimeData.timeSinceNumber:`, `computed result is not finite or is out of safe range.`);
return null;
}
return (0, make_1.timeMake)(this._units, result);
}
/**
* Get time object containing time left until target time. May return
* negative value when target time is in the past. The returned time
* object's time left value uses the same time units as the calling instance.
* @param time
*/
timeUntilTime(time) {
if (!(0, type_1.timeCheckType)(time)) {
this.log.error(`TimeData.timeUntilTime:`, `time arg is not a valid Time instance.`);
return null;
}
const target = (0, convert_1.timeConvert)(time.units(), this._units, time());
if (target === null) {
this.log.error(`TimeData.timeUntilTime:`, `bad timeConvert result for time arg.`);
return null;
}
return this.timeUntilNumber(target);
}
/**
* Get time remaining until target unix timestamp.
* @param target
*/
timeUntilNumber(target) {
if (!isSafeFiniteNumber(target)) {
this.log.error(`TimeData.timeUntilNumber:`, `target arg is not a finite number in safe range.`);
return null;
}
const result = target - this.value;
if (!isSafeFiniteNumber(result)) {
this.log.error(`TimeData.timeUntilNumber:`, `computed result is not finite or is out of safe range.`);
return null;
}
return (0, make_1.timeMake)(this._units, result);
}
/**
* Reset internal state variables to their initial values.
* @param caller
*/
reset(caller) {
this.value = this.initialValue;
this._units = this.initialUnits;
this.log.debug(`TimeData.reset:`, `complete.`);
return caller;
}
}
exports.TimeData = TimeData;