UNPKG

@toreda/time

Version:

Simple, small footprint library for common time operations and converting between units of time.

320 lines (319 loc) 12.8 kB
"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;