@anoki/fse-ui
Version:
FSE UI components library
478 lines (477 loc) • 18.3 kB
JavaScript
import c, { friendlyDateTime as f } from "./index.es225.js";
import h from "./index.es3.js";
import S from "./index.es4.js";
import { InvalidArgumentError as v, InvalidIntervalError as p } from "./index.es237.js";
import V from "./index.es238.js";
import O from "./index.es228.js";
import { DATE_SHORT as D } from "./index.es236.js";
const d = "Invalid Interval";
function I(m, t) {
return !m || !m.isValid ? a.invalid("missing or invalid start") : !t || !t.isValid ? a.invalid("missing or invalid end") : t < m ? a.invalid(
"end before start",
`The end of an interval must be after its start, but you had start=${m.toISO()} and end=${t.toISO()}`
) : null;
}
class a {
/**
* @private
*/
constructor(t) {
this.s = t.start, this.e = t.end, this.invalid = t.invalid || null, this.isLuxonInterval = !0;
}
/**
* Create an invalid Interval.
* @param {string} reason - simple string of why this Interval is invalid. Should not contain parameters or anything else data-dependent
* @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information
* @return {Interval}
*/
static invalid(t, i = null) {
if (!t)
throw new v("need to specify a reason the Interval is invalid");
const e = t instanceof V ? t : new V(t, i);
if (S.throwOnInvalid)
throw new p(e);
return new a({ invalid: e });
}
/**
* Create an Interval from a start DateTime and an end DateTime. Inclusive of the start but not the end.
* @param {DateTime|Date|Object} start
* @param {DateTime|Date|Object} end
* @return {Interval}
*/
static fromDateTimes(t, i) {
const e = f(t), s = f(i), r = I(e, s);
return r ?? new a({
start: e,
end: s
});
}
/**
* Create an Interval from a start DateTime and a Duration to extend to.
* @param {DateTime|Date|Object} start
* @param {Duration|Object|number} duration - the length of the Interval.
* @return {Interval}
*/
static after(t, i) {
const e = h.fromDurationLike(i), s = f(t);
return a.fromDateTimes(s, s.plus(e));
}
/**
* Create an Interval from an end DateTime and a Duration to extend backwards to.
* @param {DateTime|Date|Object} end
* @param {Duration|Object|number} duration - the length of the Interval.
* @return {Interval}
*/
static before(t, i) {
const e = h.fromDurationLike(i), s = f(t);
return a.fromDateTimes(s.minus(e), s);
}
/**
* Create an Interval from an ISO 8601 string.
* Accepts `<start>/<end>`, `<start>/<duration>`, and `<duration>/<end>` formats.
* @param {string} text - the ISO string to parse
* @param {Object} [opts] - options to pass {@link DateTime#fromISO} and optionally {@link Duration#fromISO}
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* @return {Interval}
*/
static fromISO(t, i) {
const [e, s] = (t || "").split("/", 2);
if (e && s) {
let r, n;
try {
r = c.fromISO(e, i), n = r.isValid;
} catch {
n = !1;
}
let u, l;
try {
u = c.fromISO(s, i), l = u.isValid;
} catch {
l = !1;
}
if (n && l)
return a.fromDateTimes(r, u);
if (n) {
const o = h.fromISO(s, i);
if (o.isValid)
return a.after(r, o);
} else if (l) {
const o = h.fromISO(e, i);
if (o.isValid)
return a.before(u, o);
}
}
return a.invalid("unparsable", `the input "${t}" can't be parsed as ISO 8601`);
}
/**
* Check if an object is an Interval. Works across context boundaries
* @param {object} o
* @return {boolean}
*/
static isInterval(t) {
return t && t.isLuxonInterval || !1;
}
/**
* Returns the start of the Interval
* @type {DateTime}
*/
get start() {
return this.isValid ? this.s : null;
}
/**
* Returns the end of the Interval
* @type {DateTime}
*/
get end() {
return this.isValid ? this.e : null;
}
/**
* Returns the last DateTime included in the interval (since end is not part of the interval)
* @type {DateTime}
*/
get lastDateTime() {
return this.isValid && this.e ? this.e.minus(1) : null;
}
/**
* Returns whether this Interval's end is at least its start, meaning that the Interval isn't 'backwards'.
* @type {boolean}
*/
get isValid() {
return this.invalidReason === null;
}
/**
* Returns an error code if this Interval is invalid, or null if the Interval is valid
* @type {string}
*/
get invalidReason() {
return this.invalid ? this.invalid.reason : null;
}
/**
* Returns an explanation of why this Interval became invalid, or null if the Interval is valid
* @type {string}
*/
get invalidExplanation() {
return this.invalid ? this.invalid.explanation : null;
}
/**
* Returns the length of the Interval in the specified unit.
* @param {string} unit - the unit (such as 'hours' or 'days') to return the length in.
* @return {number}
*/
length(t = "milliseconds") {
return this.isValid ? this.toDuration(t).get(t) : NaN;
}
/**
* Returns the count of minutes, hours, days, months, or years included in the Interval, even in part.
* Unlike {@link Interval#length} this counts sections of the calendar, not periods of time, e.g. specifying 'day'
* asks 'what dates are included in this interval?', not 'how many days long is this interval?'
* @param {string} [unit='milliseconds'] - the unit of time to count.
* @param {Object} opts - options
* @param {boolean} [opts.useLocaleWeeks=false] - If true, use weeks based on the locale, i.e. use the locale-dependent start of the week; this operation will always use the locale of the start DateTime
* @return {number}
*/
count(t = "milliseconds", i) {
if (!this.isValid) return NaN;
const e = this.start.startOf(t, i);
let s;
return i != null && i.useLocaleWeeks ? s = this.end.reconfigure({ locale: e.locale }) : s = this.end, s = s.startOf(t, i), Math.floor(s.diff(e, t).get(t)) + (s.valueOf() !== this.end.valueOf());
}
/**
* Returns whether this Interval's start and end are both in the same unit of time
* @param {string} unit - the unit of time to check sameness on
* @return {boolean}
*/
hasSame(t) {
return this.isValid ? this.isEmpty() || this.e.minus(1).hasSame(this.s, t) : !1;
}
/**
* Return whether this Interval has the same start and end DateTimes.
* @return {boolean}
*/
isEmpty() {
return this.s.valueOf() === this.e.valueOf();
}
/**
* Return whether this Interval's start is after the specified DateTime.
* @param {DateTime} dateTime
* @return {boolean}
*/
isAfter(t) {
return this.isValid ? this.s > t : !1;
}
/**
* Return whether this Interval's end is before the specified DateTime.
* @param {DateTime} dateTime
* @return {boolean}
*/
isBefore(t) {
return this.isValid ? this.e <= t : !1;
}
/**
* Return whether this Interval contains the specified DateTime.
* @param {DateTime} dateTime
* @return {boolean}
*/
contains(t) {
return this.isValid ? this.s <= t && this.e > t : !1;
}
/**
* "Sets" the start and/or end dates. Returns a newly-constructed Interval.
* @param {Object} values - the values to set
* @param {DateTime} values.start - the starting DateTime
* @param {DateTime} values.end - the ending DateTime
* @return {Interval}
*/
set({ start: t, end: i } = {}) {
return this.isValid ? a.fromDateTimes(t || this.s, i || this.e) : this;
}
/**
* Split this Interval at each of the specified DateTimes
* @param {...DateTime} dateTimes - the unit of time to count.
* @return {Array}
*/
splitAt(...t) {
if (!this.isValid) return [];
const i = t.map(f).filter((n) => this.contains(n)).sort((n, u) => n.toMillis() - u.toMillis()), e = [];
let { s } = this, r = 0;
for (; s < this.e; ) {
const n = i[r] || this.e, u = +n > +this.e ? this.e : n;
e.push(a.fromDateTimes(s, u)), s = u, r += 1;
}
return e;
}
/**
* Split this Interval into smaller Intervals, each of the specified length.
* Left over time is grouped into a smaller interval
* @param {Duration|Object|number} duration - The length of each resulting interval.
* @return {Array}
*/
splitBy(t) {
const i = h.fromDurationLike(t);
if (!this.isValid || !i.isValid || i.as("milliseconds") === 0)
return [];
let { s: e } = this, s = 1, r;
const n = [];
for (; e < this.e; ) {
const u = this.start.plus(i.mapUnits((l) => l * s));
r = +u > +this.e ? this.e : u, n.push(a.fromDateTimes(e, r)), e = r, s += 1;
}
return n;
}
/**
* Split this Interval into the specified number of smaller intervals.
* @param {number} numberOfParts - The number of Intervals to divide the Interval into.
* @return {Array}
*/
divideEqually(t) {
return this.isValid ? this.splitBy(this.length() / t).slice(0, t) : [];
}
/**
* Return whether this Interval overlaps with the specified Interval
* @param {Interval} other
* @return {boolean}
*/
overlaps(t) {
return this.e > t.s && this.s < t.e;
}
/**
* Return whether this Interval's end is adjacent to the specified Interval's start.
* @param {Interval} other
* @return {boolean}
*/
abutsStart(t) {
return this.isValid ? +this.e == +t.s : !1;
}
/**
* Return whether this Interval's start is adjacent to the specified Interval's end.
* @param {Interval} other
* @return {boolean}
*/
abutsEnd(t) {
return this.isValid ? +t.e == +this.s : !1;
}
/**
* Returns true if this Interval fully contains the specified Interval, specifically if the intersect (of this Interval and the other Interval) is equal to the other Interval; false otherwise.
* @param {Interval} other
* @return {boolean}
*/
engulfs(t) {
return this.isValid ? this.s <= t.s && this.e >= t.e : !1;
}
/**
* Return whether this Interval has the same start and end as the specified Interval.
* @param {Interval} other
* @return {boolean}
*/
equals(t) {
return !this.isValid || !t.isValid ? !1 : this.s.equals(t.s) && this.e.equals(t.e);
}
/**
* Return an Interval representing the intersection of this Interval and the specified Interval.
* Specifically, the resulting Interval has the maximum start time and the minimum end time of the two Intervals.
* Returns null if the intersection is empty, meaning, the intervals don't intersect.
* @param {Interval} other
* @return {Interval}
*/
intersection(t) {
if (!this.isValid) return this;
const i = this.s > t.s ? this.s : t.s, e = this.e < t.e ? this.e : t.e;
return i >= e ? null : a.fromDateTimes(i, e);
}
/**
* Return an Interval representing the union of this Interval and the specified Interval.
* Specifically, the resulting Interval has the minimum start time and the maximum end time of the two Intervals.
* @param {Interval} other
* @return {Interval}
*/
union(t) {
if (!this.isValid) return this;
const i = this.s < t.s ? this.s : t.s, e = this.e > t.e ? this.e : t.e;
return a.fromDateTimes(i, e);
}
/**
* Merge an array of Intervals into an equivalent minimal set of Intervals.
* Combines overlapping and adjacent Intervals.
* The resulting array will contain the Intervals in ascending order, that is, starting with the earliest Interval
* and ending with the latest.
*
* @param {Array} intervals
* @return {Array}
*/
static merge(t) {
const [i, e] = t.sort((s, r) => s.s - r.s).reduce(
([s, r], n) => r ? r.overlaps(n) || r.abutsStart(n) ? [s, r.union(n)] : [s.concat([r]), n] : [s, n],
[[], null]
);
return e && i.push(e), i;
}
/**
* Return an array of Intervals representing the spans of time that only appear in one of the specified Intervals.
* @param {Array} intervals
* @return {Array}
*/
static xor(t) {
let i = null, e = 0;
const s = [], r = t.map((l) => [
{ time: l.s, type: "s" },
{ time: l.e, type: "e" }
]), n = Array.prototype.concat(...r), u = n.sort((l, o) => l.time - o.time);
for (const l of u)
e += l.type === "s" ? 1 : -1, e === 1 ? i = l.time : (i && +i != +l.time && s.push(a.fromDateTimes(i, l.time)), i = null);
return a.merge(s);
}
/**
* Return an Interval representing the span of time in this Interval that doesn't overlap with any of the specified Intervals.
* @param {...Interval} intervals
* @return {Array}
*/
difference(...t) {
return a.xor([this].concat(t)).map((i) => this.intersection(i)).filter((i) => i && !i.isEmpty());
}
/**
* Returns a string representation of this Interval appropriate for debugging.
* @return {string}
*/
toString() {
return this.isValid ? `[${this.s.toISO()} – ${this.e.toISO()})` : d;
}
/**
* Returns a string representation of this Interval appropriate for the REPL.
* @return {string}
*/
[Symbol.for("nodejs.util.inspect.custom")]() {
return this.isValid ? `Interval { start: ${this.s.toISO()}, end: ${this.e.toISO()} }` : `Interval { Invalid, reason: ${this.invalidReason} }`;
}
/**
* Returns a localized string representing this Interval. Accepts the same options as the
* Intl.DateTimeFormat constructor and any presets defined by Luxon, such as
* {@link DateTime.DATE_FULL} or {@link DateTime.TIME_SIMPLE}. The exact behavior of this method
* is browser-specific, but in general it will return an appropriate representation of the
* Interval in the assigned locale. Defaults to the system's locale if no locale has been
* specified.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
* @param {Object} [formatOpts=DateTime.DATE_SHORT] - Either a DateTime preset or
* Intl.DateTimeFormat constructor options.
* @param {Object} opts - Options to override the configuration of the start DateTime.
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(); //=> 11/7/2022 – 11/8/2022
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL); //=> November 7 – 8, 2022
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL, { locale: 'fr-FR' }); //=> 7–8 novembre 2022
* @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString(DateTime.TIME_SIMPLE); //=> 6:00 – 8:00 PM
* @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString({ weekday: 'short', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); //=> Mon, Nov 07, 6:00 – 8:00 p
* @return {string}
*/
toLocaleString(t = D, i = {}) {
return this.isValid ? O.create(this.s.loc.clone(i), t).formatInterval(this) : d;
}
/**
* Returns an ISO 8601-compliant string representation of this Interval.
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* @param {Object} opts - The same options as {@link DateTime#toISO}
* @return {string}
*/
toISO(t) {
return this.isValid ? `${this.s.toISO(t)}/${this.e.toISO(t)}` : d;
}
/**
* Returns an ISO 8601-compliant string representation of date of this Interval.
* The time components are ignored.
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* @return {string}
*/
toISODate() {
return this.isValid ? `${this.s.toISODate()}/${this.e.toISODate()}` : d;
}
/**
* Returns an ISO 8601-compliant string representation of time of this Interval.
* The date components are ignored.
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* @param {Object} opts - The same options as {@link DateTime#toISO}
* @return {string}
*/
toISOTime(t) {
return this.isValid ? `${this.s.toISOTime(t)}/${this.e.toISOTime(t)}` : d;
}
/**
* Returns a string representation of this Interval formatted according to the specified format
* string. **You may not want this.** See {@link Interval#toLocaleString} for a more flexible
* formatting tool.
* @param {string} dateFormat - The format string. This string formats the start and end time.
* See {@link DateTime#toFormat} for details.
* @param {Object} opts - Options.
* @param {string} [opts.separator = ' – '] - A separator to place between the start and end
* representations.
* @return {string}
*/
toFormat(t, { separator: i = " – " } = {}) {
return this.isValid ? `${this.s.toFormat(t)}${i}${this.e.toFormat(t)}` : d;
}
/**
* Return a Duration representing the time spanned by this interval.
* @param {string|string[]} [unit=['milliseconds']] - the unit or units (such as 'hours' or 'days') to include in the duration.
* @param {Object} opts - options that affect the creation of the Duration
* @param {string} [opts.conversionAccuracy='casual'] - the conversion system to use
* @example Interval.fromDateTimes(dt1, dt2).toDuration().toObject() //=> { milliseconds: 88489257 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration('days').toObject() //=> { days: 1.0241812152777778 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes']).toObject() //=> { hours: 24, minutes: 34.82095 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration(['hours', 'minutes', 'seconds']).toObject() //=> { hours: 24, minutes: 34, seconds: 49.257 }
* @example Interval.fromDateTimes(dt1, dt2).toDuration('seconds').toObject() //=> { seconds: 88489.257 }
* @return {Duration}
*/
toDuration(t, i) {
return this.isValid ? this.e.diff(this.s, t, i) : h.invalid(this.invalidReason);
}
/**
* Run mapFn on the interval start and end, returning a new Interval from the resulting DateTimes
* @param {function} mapFn
* @return {Interval}
* @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.toUTC())
* @example Interval.fromDateTimes(dt1, dt2).mapEndpoints(endpoint => endpoint.plus({ hours: 2 }))
*/
mapEndpoints(t) {
return a.fromDateTimes(t(this.s), t(this.e));
}
}
export {
a as default
};
//# sourceMappingURL=index.es226.js.map