UNPKG

@anoki/fse-ui

Version:

FSE UI components library

478 lines (477 loc) 18.3 kB
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