UNPKG

@softwareventures/time

Version:

Data types and functions for working with abstract times of day

462 lines 18.9 kB
"use strict"; /** @file Data types and functions for working with abstract times of day. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.formatTimeHumanIso8601 = exports.formatHumanIso8601 = exports.formatTimeIso8601 = exports.formatIso8601 = exports.parseTimeIso8601 = exports.parseIso8601 = exports.timeNowDeviceLocal = exports.nowDeviceLocal = exports.timeNowUtc = exports.nowUtc = exports.timeAfterFn = exports.afterFn = exports.timeAfter = exports.after = exports.timeBeforeOrEqualFn = exports.beforeOrEqualFn = exports.timeBeforeOrEqual = exports.beforeOrEqual = exports.timeBeforeFn = exports.beforeFn = exports.timeBefore = exports.before = exports.timeCompareFn = exports.compareFn = exports.timeCompare = exports.compare = exports.timeNotEqualFn = exports.notEqualFn = exports.timeNotEqual = exports.notEqual = exports.timeEqualFn = exports.equalFn = exports.timeEqual = exports.equal = exports.timeFromReferenceSeconds = exports.fromReferenceSeconds = exports.timeToReferenceSeconds = exports.toReferenceSeconds = exports.normalizeTime = exports.normalize = exports.time = exports.validateTime = exports.validate = exports.isTimeValid = exports.isValid = exports.isValidTime = exports.isTime = void 0; const unknown_1 = require("unknown"); const is_integer_in_range_1 = require("is-integer-in-range"); const format = require("@softwareventures/format-time"); const ordered_1 = require("@softwareventures/ordered"); const nullable_1 = require("@softwareventures/nullable"); const js_date_1 = require("./js-date"); /** Returns `true` if the specified value has the shape of a `Time` object. * * The `hours`, `minutes` and `seconds` fields may be non-integers or outside * the expected range, meaning that the object may not represent a valid time. * * To test if the object represents a valid time, call {@link isValid} or * {@link isValidTime}. */ function isTime(value) { return (typeof value === "object" && value != null && (0, unknown_1.hasProperty)(value, "type") && value.type === "Time" && (0, unknown_1.hasProperty)(value, "hours") && typeof value.hours === "number" && (0, unknown_1.hasProperty)(value, "minutes") && typeof value.minutes === "number" && (0, unknown_1.hasProperty)(value, "seconds") && typeof value.seconds === "number"); } exports.isTime = isTime; /** Tests if the specified value is a {@link Time} object representing a valid * time. * * Returns `true` if the value has the shape of a `Time` object, the `hours` and * `minutes` fields are integers within the expected range, and `seconds` * is a number within the expected range. */ function isValidTime(value) { return isTime(value) && isValid(value); } exports.isValidTime = isValidTime; /** Tests if the specified {@link Time} object represents a valid time. * * Returns true if `hours` and `minutes` are integers within the expected * range, and `seconds` is a number within the expected range. * * Times returned by functions in this library are always valid. */ function isValid(time) { return ((!(0, unknown_1.hasProperty)(time, "type") || time.type === "Time") && (0, is_integer_in_range_1.default)(time.hours, 0, 23) && (0, is_integer_in_range_1.default)(time.minutes, 0, 59) && time.seconds >= 0 && time.seconds < 60); } exports.isValid = isValid; /** Tests if the specified {@link Time} object represents a valid time. * * Returns true if `hours` and `minutes` are integers within the expected * range, and `seconds` is a number within the expected range. * * Times returned by functions in this library are always valid. */ exports.isTimeValid = isValid; /** * Asserts that the specified {@link Time} object represents a valid time. * * Times returned by functions in this library are always valid. * * @throws {Error} if any of the `hour` or `minute` fields are non-integers, * or if any of the `hour`, `minute` or `second` fields are outside the * valid range. */ function validate(time) { if (!isValid(time)) { throw new Error("Invalid time"); } } exports.validate = validate; /** * Asserts that the specified {@link Time} object represents a valid time. * * Times returned by functions in this library are always valid. * * Alias of {@link validate}, useful for disambiguation from similar functions * that operate on other types. * * @throws {Error} if any of the `hour` or `minute` fields are non-integers, * or if any of the `hour`, `minute` or `second` fields are outside the * valid range. */ exports.validateTime = validate; /** Creates a {@link Time} with the specified options. * * If any numeric components are unspecified, they default to zero. * * If any numeric components are outside the expected range, then they * will roll over into the next larger component. If the time as a whole * is outside the 24-hour range, then the time will wrap around by as * many 24-hour periods as needed to put it in the valid range. * * @throws {Error} if any of the numeric components are non-finite. */ function time(time) { return fromReferenceSeconds(toReferenceSeconds(time)); } exports.time = time; /** Normalizes the specified {@link Time} object so that it represents a valid * time. * * If any numeric components are unspecified, they default to zero. * * If any numeric components are outside the expected range, then they * will roll over into the next larger component. If the time as a whole * is outside the 24-hour range, then the time will wrap around by as * many 24-hour periods as needed to put it in the valid range. * * Alias of {@link time}. Calling the function by this name instead might make * code clearer in cases where the purpose is to normalize an existing `Time` * object. * * @throws {Error} if any of the numeric components are non-finite. */ exports.normalize = time; /** Normalizes the specified {@link Time} object so that it represents a valid * time. * * If any numeric components are unspecified, they default to zero. * * If any numeric components are outside the expected range, then they * will roll over into the next larger component. If the time as a whole * is outside the 24-hour range, then the time will wrap around by as * many 24-hour periods as needed to put it in the valid range. * * Alias of {@link time}. Calling the function by this name instead might make * code clearer in cases where the purpose is to normalize an existing `Time` * object. * * @throws {Error} if any of the numeric components are non-finite. */ exports.normalizeTime = time; /** Converts the specified {@link Time} to a count of seconds since * midnight. */ function toReferenceSeconds(time) { var _a, _b, _c; const hours = (_a = time.hours) !== null && _a !== void 0 ? _a : 0; const minutes = (_b = time.minutes) !== null && _b !== void 0 ? _b : 0; const seconds = (_c = time.seconds) !== null && _c !== void 0 ? _c : 0; return (86400 + ((hours * 3600 + minutes * 60 + seconds) % 86400)) % 86400; } exports.toReferenceSeconds = toReferenceSeconds; /** Converts the specified {@link Time} to a count of seconds since * midnight. * * Alias of {@link toReferenceSeconds}, useful for disambiguation from similar * functions that operate on other types. */ exports.timeToReferenceSeconds = toReferenceSeconds; /** Creates a {@link Time} corresponding to the specified count of seconds * since midnight. * * @throws {Error} if seconds is not a finite value. */ function fromReferenceSeconds(seconds) { if (!isFinite(seconds)) { throw new Error("Non-finite seconds"); } const hours = Math.floor(seconds / 3600); const seconds2 = seconds - hours * 3600; const minutes = Math.floor(seconds2 / 60); const seconds3 = seconds2 - minutes * 60; return { type: "Time", hours, minutes, seconds: seconds3 }; } exports.fromReferenceSeconds = fromReferenceSeconds; /** Creates a {@link Time} corresponding to the specified count of seconds * since midnight. * * Alias of {@link fromReferenceSeconds}, useful for disambiguation from * similar functions that operate on other types. * * @throws {Error} if seconds is not a finite value. */ exports.timeFromReferenceSeconds = fromReferenceSeconds; /** Tests if two {@link Time}s are equal. */ function equal(a, b) { return toReferenceSeconds(a) === toReferenceSeconds(b); } exports.equal = equal; /** Tests if two {@link Time}s are equal. * * Alias of {@link equal}, useful for disambiguation from other equality * functions. */ exports.timeEqual = equal; /** Tests if two {@link Time}s are equal. * * Curried variant of {@link equal}. */ function equalFn(b) { return a => equal(a, b); } exports.equalFn = equalFn; /** Tests if two {@link Time}s are equal. * * Curried variant of {@link timeEqual}. */ exports.timeEqualFn = equalFn; /** Tests if two {@link Time}s are not equal. */ function notEqual(a, b) { return toReferenceSeconds(a) !== toReferenceSeconds(b); } exports.notEqual = notEqual; /** Tests if two {@link Time}s are not equal. * * Alias of {@link notEqual}, useful for disambiguation from other inequality * functions. */ exports.timeNotEqual = notEqual; /** Tests if two {@link Time}s are not equal. * * Curried variant of {@link notEqual}. */ function notEqualFn(b) { return a => notEqual(a, b); } exports.notEqualFn = notEqualFn; /** Tests if two {@link Time}s are not equal. * * Curried variant of {@link timeNotEqual}. */ exports.timeNotEqualFn = notEqualFn; /** Compares two {@link Time}s. * * Time `a` is considered to be `before` time `b` if time `a` is * earlier in the day. */ const compare = (a, b) => { const as = toReferenceSeconds(a); const bs = toReferenceSeconds(b); if (as < bs) { return ordered_1.Comparison.before; } else if (as > bs) { return ordered_1.Comparison.after; } else if (as === bs) { return ordered_1.Comparison.equal; } else { return ordered_1.Comparison.undefined; } }; exports.compare = compare; /** Compares two {@link Time}s. * * Time `a` is considered to be `before` time `b` if time `a` is * earlier in the day. * * Alias of {@link compare}, useful for disambiguation from other comparison * functions. */ exports.timeCompare = exports.compare; /** Compares two {@link Time}s. * * Time `a` is considered to be `before` time `b` if time `a` is * earlier in the day. * * Curried variant of {@link compare}. */ function compareFn(b) { return a => (0, exports.compare)(a, b); } exports.compareFn = compareFn; /** Compares two {@link Time}s. * * Time `a` is considered to be `before` time `b` if time `a` is * earlier in the day. * * Curried variant of {@link timeCompare}. */ exports.timeCompareFn = compareFn; /** Tests if {@link Time} `a` is earlier in the day than {@link Time} `b`. */ function before(a, b) { return toReferenceSeconds(a) < toReferenceSeconds(b); } exports.before = before; /** Tests if {@link Time} `a` is earlier in the day than {@link Time} `b`. * * Alias of {@link before}, useful for disambiguation from similar functions * that operate on other date/time types. */ exports.timeBefore = before; /** Tests if {@link Time} `a` is earlier in the day than {@link Time} `b`. * * Curried variant of {@link before}. */ function beforeFn(b) { return a => before(a, b); } exports.beforeFn = beforeFn; /** Tests if {@link Time} `a` is earlier in the day than {@link Time} `b`. * * Curried variant of {@link timeBefore}. */ exports.timeBeforeFn = beforeFn; /** Tests if {@link Time} `a` is equal to or earlier in the day than * {@link Time} `b`. */ function beforeOrEqual(a, b) { return toReferenceSeconds(a) <= toReferenceSeconds(b); } exports.beforeOrEqual = beforeOrEqual; /** Tests if {@link Time} `a` is equal to or earlier in the day than * {@link Time} `b`. * * Alias of {@link beforeOrEqual}, useful for disambiguation from similar * functions that operate on other date/time types. */ exports.timeBeforeOrEqual = beforeOrEqual; /** Tests if {@link Time} `a` is equal to or earlier in the day than * {@link Time} `b`. * * Curried variant of {@link beforeOrEqual}. */ function beforeOrEqualFn(b) { return a => beforeOrEqual(a, b); } exports.beforeOrEqualFn = beforeOrEqualFn; /** Tests if {@link Time} `a` is equal to or earlier in the day than * {@link Time} `b`. * * Curried variant of {@link timeBeforeOrEqual}. */ exports.timeBeforeOrEqualFn = beforeOrEqualFn; /** Tests if {@link Time} `a` is later in the day than {@link Time} `b`. */ function after(a, b) { return toReferenceSeconds(a) > toReferenceSeconds(b); } exports.after = after; /** Tests if {@link Time} `a` is later in the day than {@link Time} `b`. * * Alias of {@link after}, useful for disambiguation from similar functions * that operate on other date/time types. */ exports.timeAfter = after; /** Tests if {@link Time} `a` is later in the day than {@link Time} `b`. * * Curried variant of {@link after}. */ function afterFn(b) { return a => after(a, b); } exports.afterFn = afterFn; /** Tests if {@link Time} `a` is later in the day than {@link Time} `b`. * * Curried variant of {@link timeAfter}. */ exports.timeAfterFn = afterFn; /** Returns the current time of day according to UTC. */ function nowUtc() { const now = new js_date_1.JsDate(); return { type: "Time", hours: now.getUTCHours(), minutes: now.getUTCMinutes(), seconds: now.getUTCSeconds() + 0.001 * now.getUTCMilliseconds() }; } exports.nowUtc = nowUtc; /** Returns the current time of day according to UTC. * * Alias of {@link nowUtc}, useful for disambiguation from similar functions * that operate on other date/time types. */ exports.timeNowUtc = nowUtc; /** Returns the current time of day according to the device's local * timezone. */ function nowDeviceLocal() { const now = new js_date_1.JsDate(); return { type: "Time", hours: now.getHours(), minutes: now.getMinutes(), seconds: now.getSeconds() + 0.001 * now.getMilliseconds() }; } exports.nowDeviceLocal = nowDeviceLocal; /** Returns the current time of day according to the device's local timezone. * * Alias of {@link nowDeviceLocal}, useful for disambiguation from similar * functions that operate on other date/time types. */ exports.timeNowDeviceLocal = nowDeviceLocal; /** * Parses a {@link Time} from text in ISO 8601 format. * * The ISO 8601 text must not specify a time zone offset. * * If the specified text is not a valid ISO 8601 time then this function * returns `null`. * * Both extended `Thh:mm:ss.sss` and basic `Thhmmss.sss` ISO 8601 formats are * accepted, and the initial `T` is optional in both cases. The seconds field * may be omitted, and the minutes field may also be omitted if the seconds * field is omitted. Omitted fields default to zero. */ function parseIso8601(text) { var _a, _b; const match = /^T?(\d{2})(?::?(\d{2})(?::?(\d{2}(?:\.\d*)?))?)?$/iu.exec(text); if ((match === null || match === void 0 ? void 0 : match[1]) == null) { return null; } const hours = parseInt(match[1], 10); const minutes = (_a = (0, nullable_1.mapOptional)(match[2], text => parseInt(text, 10))) !== null && _a !== void 0 ? _a : 0; const seconds = (_b = (0, nullable_1.mapOptional)(match[3], text => parseFloat(text.replace(",", ".")))) !== null && _b !== void 0 ? _b : 0; return { type: "Time", hours, minutes, seconds }; } exports.parseIso8601 = parseIso8601; /** * Parses a {@link Time} from text in ISO 8601 format. * * The ISO 8601 text must not specify a time zone offset. * * If the specified text is not a valid ISO 8601 time then this function * returns `null`. * * Both extended `Thh:mm:ss.sss` and basic `Thhmmss.sss` ISO 8601 formats are * accepted, and the initial `T` is optional in both cases. The seconds field * may be omitted, and the minutes field may also be omitted if the seconds * field is omitted. Omitted fields default to zero. * * Alias of {@link parseIso8601}, useful for disambiguation from similar * functions that operate on other date/time types. */ exports.parseTimeIso8601 = parseIso8601; /** Returns a {@link TimeFormatter} that formats the specified {@link Time} as * ISO 8601, with the specified options. * * By default, the {@link Time} is formatted in the "extended" ISO 8601 format, * with the leading `"T"`, and without rounding, for example * `"T11:57:23.723615"`. * * If the `format` option is set to `"basic"`, then the colons are omitted, * for example `"T115723.723615"`. * * If the `round` option is set to `"seconds"`, then the time is rounded down * to the next lower second, for example `"T11:57:23"`. * * If the `round` option is set to `"ms"`, then the time is rounded down to * the next lower millisecond, for example `"T11:57:23.723"`. * * If the `leadingT` option is set to `false`, then the leading `"T"` is * omitted, for example `"11:57:23.363215"`. * * For other formats, see `@softwareventures/format-time`. */ exports.formatIso8601 = format.iso8601; /** Returns a {@link TimeFormatter} that formats the specified {@link Time} as * ISO 8601, with the specified options. * * By default, the {@link Time} is formatted in the "extended" ISO 8601 format, * with the leading `"T"`, and without rounding, for example * `"T11:57:23.723615"`. * * If the `format` option is set to `"basic"`, then the colons are omitted, * for example `"T115723.723615"`. * * If the `round` option is set to `"seconds"`, then the time is rounded down * to the next lower second, for example `"T11:57:23"`. * * If the `round` option is set to `"ms"`, then the time is rounded down to * the next lower millisecond, for example `"T11:57:23.723"`. * * If the `leadingT` option is set to `false`, then the leading `"T"` is * omitted, for example `"11:57:23.363215"`. * * Alias of {@link formatIso8601}, useful for disambiguation from similar * functions that operate on other date/time types. * * For other formats, see `@softwareventures/format-time`. */ exports.formatTimeIso8601 = format.iso8601; /** Formats the specified {@link Time} as ISO 8601 extended, rounded down to * the next lower second, and with no leading `"T"`. * * This format is intended to be reasonable for display to humans. */ exports.formatHumanIso8601 = format.humanIso8601; /** Formats the specified {@link Time} as ISO 8601 extended, rounded down to * the next lower second, and with no leading `"T"`. * * This format is intended to be reasonable for display to humans. * * Alias of {@link formatHumanIso8601}, useful for disambiguation from similar * functions that operate on other date/time types. */ exports.formatTimeHumanIso8601 = format.humanIso8601; //# sourceMappingURL=index.js.map