@softwareventures/time
Version:
Data types and functions for working with abstract times of day
462 lines • 18.9 kB
JavaScript
"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