summaryrefslogtreecommitdiff
path: root/node_modules/luxon/src/interval.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/luxon/src/interval.js')
-rw-r--r--node_modules/luxon/src/interval.js669
1 files changed, 669 insertions, 0 deletions
diff --git a/node_modules/luxon/src/interval.js b/node_modules/luxon/src/interval.js
new file mode 100644
index 0000000..48f9ba4
--- /dev/null
+++ b/node_modules/luxon/src/interval.js
@@ -0,0 +1,669 @@
+import DateTime, { friendlyDateTime } from "./datetime.js";
+import Duration from "./duration.js";
+import Settings from "./settings.js";
+import { InvalidArgumentError, InvalidIntervalError } from "./errors.js";
+import Invalid from "./impl/invalid.js";
+import Formatter from "./impl/formatter.js";
+import * as Formats from "./impl/formats.js";
+
+const INVALID = "Invalid Interval";
+
+// checks if the start is equal to or before the end
+function validateStartEnd(start, end) {
+ if (!start || !start.isValid) {
+ return Interval.invalid("missing or invalid start");
+ } else if (!end || !end.isValid) {
+ return Interval.invalid("missing or invalid end");
+ } else if (end < start) {
+ return Interval.invalid(
+ "end before start",
+ `The end of an interval must be after its start, but you had start=${start.toISO()} and end=${end.toISO()}`
+ );
+ } else {
+ return null;
+ }
+}
+
+/**
+ * An Interval object represents a half-open interval of time, where each endpoint is a {@link DateTime}. Conceptually, it's a container for those two endpoints, accompanied by methods for creating, parsing, interrogating, comparing, transforming, and formatting them.
+ *
+ * Here is a brief overview of the most commonly used methods and getters in Interval:
+ *
+ * * **Creation** To create an Interval, use {@link Interval.fromDateTimes}, {@link Interval.after}, {@link Interval.before}, or {@link Interval.fromISO}.
+ * * **Accessors** Use {@link Interval#start} and {@link Interval#end} to get the start and end.
+ * * **Interrogation** To analyze the Interval, use {@link Interval#count}, {@link Interval#length}, {@link Interval#hasSame}, {@link Interval#contains}, {@link Interval#isAfter}, or {@link Interval#isBefore}.
+ * * **Transformation** To create other Intervals out of this one, use {@link Interval#set}, {@link Interval#splitAt}, {@link Interval#splitBy}, {@link Interval#divideEqually}, {@link Interval.merge}, {@link Interval.xor}, {@link Interval#union}, {@link Interval#intersection}, or {@link Interval#difference}.
+ * * **Comparison** To compare this Interval to another one, use {@link Interval#equals}, {@link Interval#overlaps}, {@link Interval#abutsStart}, {@link Interval#abutsEnd}, {@link Interval#engulfs}
+ * * **Output** To convert the Interval into other representations, see {@link Interval#toString}, {@link Interval#toLocaleString}, {@link Interval#toISO}, {@link Interval#toISODate}, {@link Interval#toISOTime}, {@link Interval#toFormat}, and {@link Interval#toDuration}.
+ */
+export default class Interval {
+ /**
+ * @private
+ */
+ constructor(config) {
+ /**
+ * @access private
+ */
+ this.s = config.start;
+ /**
+ * @access private
+ */
+ this.e = config.end;
+ /**
+ * @access private
+ */
+ this.invalid = config.invalid || null;
+ /**
+ * @access private
+ */
+ this.isLuxonInterval = true;
+ }
+
+ /**
+ * 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(reason, explanation = null) {
+ if (!reason) {
+ throw new InvalidArgumentError("need to specify a reason the Interval is invalid");
+ }
+
+ const invalid = reason instanceof Invalid ? reason : new Invalid(reason, explanation);
+
+ if (Settings.throwOnInvalid) {
+ throw new InvalidIntervalError(invalid);
+ } else {
+ return new Interval({ invalid });
+ }
+ }
+
+ /**
+ * 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(start, end) {
+ const builtStart = friendlyDateTime(start),
+ builtEnd = friendlyDateTime(end);
+
+ const validateError = validateStartEnd(builtStart, builtEnd);
+
+ if (validateError == null) {
+ return new Interval({
+ start: builtStart,
+ end: builtEnd,
+ });
+ } else {
+ return validateError;
+ }
+ }
+
+ /**
+ * 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(start, duration) {
+ const dur = Duration.fromDurationLike(duration),
+ dt = friendlyDateTime(start);
+ return Interval.fromDateTimes(dt, dt.plus(dur));
+ }
+
+ /**
+ * 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(end, duration) {
+ const dur = Duration.fromDurationLike(duration),
+ dt = friendlyDateTime(end);
+ return Interval.fromDateTimes(dt.minus(dur), dt);
+ }
+
+ /**
+ * 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(text, opts) {
+ const [s, e] = (text || "").split("/", 2);
+ if (s && e) {
+ let start, startIsValid;
+ try {
+ start = DateTime.fromISO(s, opts);
+ startIsValid = start.isValid;
+ } catch (e) {
+ startIsValid = false;
+ }
+
+ let end, endIsValid;
+ try {
+ end = DateTime.fromISO(e, opts);
+ endIsValid = end.isValid;
+ } catch (e) {
+ endIsValid = false;
+ }
+
+ if (startIsValid && endIsValid) {
+ return Interval.fromDateTimes(start, end);
+ }
+
+ if (startIsValid) {
+ const dur = Duration.fromISO(e, opts);
+ if (dur.isValid) {
+ return Interval.after(start, dur);
+ }
+ } else if (endIsValid) {
+ const dur = Duration.fromISO(s, opts);
+ if (dur.isValid) {
+ return Interval.before(end, dur);
+ }
+ }
+ }
+ return Interval.invalid("unparsable", `the input "${text}" 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(o) {
+ return (o && o.isLuxonInterval) || false;
+ }
+
+ /**
+ * Returns the start of the Interval
+ * @type {DateTime}
+ */
+ get start() {
+ return this.isValid ? this.s : null;
+ }
+
+ /**
+ * Returns the end of the Interval. This is the first instant which is not part of the interval
+ * (Interval is half-open).
+ * @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) : 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(unit = "milliseconds") {
+ return this.isValid ? this.toDuration(...[unit]).get(unit) : 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(unit = "milliseconds", opts) {
+ if (!this.isValid) return NaN;
+ const start = this.start.startOf(unit, opts);
+ let end;
+ if (opts?.useLocaleWeeks) {
+ end = this.end.reconfigure({ locale: start.locale });
+ } else {
+ end = this.end;
+ }
+ end = end.startOf(unit, opts);
+ return Math.floor(end.diff(start, unit).get(unit)) + (end.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(unit) {
+ return this.isValid ? this.isEmpty() || this.e.minus(1).hasSame(this.s, unit) : false;
+ }
+
+ /**
+ * 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(dateTime) {
+ if (!this.isValid) return false;
+ return this.s > dateTime;
+ }
+
+ /**
+ * Return whether this Interval's end is before the specified DateTime.
+ * @param {DateTime} dateTime
+ * @return {boolean}
+ */
+ isBefore(dateTime) {
+ if (!this.isValid) return false;
+ return this.e <= dateTime;
+ }
+
+ /**
+ * Return whether this Interval contains the specified DateTime.
+ * @param {DateTime} dateTime
+ * @return {boolean}
+ */
+ contains(dateTime) {
+ if (!this.isValid) return false;
+ return this.s <= dateTime && this.e > dateTime;
+ }
+
+ /**
+ * "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, end } = {}) {
+ if (!this.isValid) return this;
+ return Interval.fromDateTimes(start || this.s, end || this.e);
+ }
+
+ /**
+ * Split this Interval at each of the specified DateTimes
+ * @param {...DateTime} dateTimes - the unit of time to count.
+ * @return {Array}
+ */
+ splitAt(...dateTimes) {
+ if (!this.isValid) return [];
+ const sorted = dateTimes
+ .map(friendlyDateTime)
+ .filter((d) => this.contains(d))
+ .sort((a, b) => a.toMillis() - b.toMillis()),
+ results = [];
+ let { s } = this,
+ i = 0;
+
+ while (s < this.e) {
+ const added = sorted[i] || this.e,
+ next = +added > +this.e ? this.e : added;
+ results.push(Interval.fromDateTimes(s, next));
+ s = next;
+ i += 1;
+ }
+
+ return results;
+ }
+
+ /**
+ * 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(duration) {
+ const dur = Duration.fromDurationLike(duration);
+
+ if (!this.isValid || !dur.isValid || dur.as("milliseconds") === 0) {
+ return [];
+ }
+
+ let { s } = this,
+ idx = 1,
+ next;
+
+ const results = [];
+ while (s < this.e) {
+ const added = this.start.plus(dur.mapUnits((x) => x * idx));
+ next = +added > +this.e ? this.e : added;
+ results.push(Interval.fromDateTimes(s, next));
+ s = next;
+ idx += 1;
+ }
+
+ return results;
+ }
+
+ /**
+ * 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(numberOfParts) {
+ if (!this.isValid) return [];
+ return this.splitBy(this.length() / numberOfParts).slice(0, numberOfParts);
+ }
+
+ /**
+ * Return whether this Interval overlaps with the specified Interval
+ * @param {Interval} other
+ * @return {boolean}
+ */
+ overlaps(other) {
+ return this.e > other.s && this.s < other.e;
+ }
+
+ /**
+ * Return whether this Interval's end is adjacent to the specified Interval's start.
+ * @param {Interval} other
+ * @return {boolean}
+ */
+ abutsStart(other) {
+ if (!this.isValid) return false;
+ return +this.e === +other.s;
+ }
+
+ /**
+ * Return whether this Interval's start is adjacent to the specified Interval's end.
+ * @param {Interval} other
+ * @return {boolean}
+ */
+ abutsEnd(other) {
+ if (!this.isValid) return false;
+ return +other.e === +this.s;
+ }
+
+ /**
+ * 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(other) {
+ if (!this.isValid) return false;
+ return this.s <= other.s && this.e >= other.e;
+ }
+
+ /**
+ * Return whether this Interval has the same start and end as the specified Interval.
+ * @param {Interval} other
+ * @return {boolean}
+ */
+ equals(other) {
+ if (!this.isValid || !other.isValid) {
+ return false;
+ }
+
+ return this.s.equals(other.s) && this.e.equals(other.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(other) {
+ if (!this.isValid) return this;
+ const s = this.s > other.s ? this.s : other.s,
+ e = this.e < other.e ? this.e : other.e;
+
+ if (s >= e) {
+ return null;
+ } else {
+ return Interval.fromDateTimes(s, 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(other) {
+ if (!this.isValid) return this;
+ const s = this.s < other.s ? this.s : other.s,
+ e = this.e > other.e ? this.e : other.e;
+ return Interval.fromDateTimes(s, 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(intervals) {
+ const [found, final] = intervals
+ .sort((a, b) => a.s - b.s)
+ .reduce(
+ ([sofar, current], item) => {
+ if (!current) {
+ return [sofar, item];
+ } else if (current.overlaps(item) || current.abutsStart(item)) {
+ return [sofar, current.union(item)];
+ } else {
+ return [sofar.concat([current]), item];
+ }
+ },
+ [[], null]
+ );
+ if (final) {
+ found.push(final);
+ }
+ return found;
+ }
+
+ /**
+ * 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(intervals) {
+ let start = null,
+ currentCount = 0;
+ const results = [],
+ ends = intervals.map((i) => [
+ { time: i.s, type: "s" },
+ { time: i.e, type: "e" },
+ ]),
+ flattened = Array.prototype.concat(...ends),
+ arr = flattened.sort((a, b) => a.time - b.time);
+
+ for (const i of arr) {
+ currentCount += i.type === "s" ? 1 : -1;
+
+ if (currentCount === 1) {
+ start = i.time;
+ } else {
+ if (start && +start !== +i.time) {
+ results.push(Interval.fromDateTimes(start, i.time));
+ }
+
+ start = null;
+ }
+ }
+
+ return Interval.merge(results);
+ }
+
+ /**
+ * 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(...intervals) {
+ return Interval.xor([this].concat(intervals))
+ .map((i) => this.intersection(i))
+ .filter((i) => i && !i.isEmpty());
+ }
+
+ /**
+ * Returns a string representation of this Interval appropriate for debugging.
+ * @return {string}
+ */
+ toString() {
+ if (!this.isValid) return INVALID;
+ return `[${this.s.toISO()} – ${this.e.toISO()})`;
+ }
+
+ /**
+ * Returns a string representation of this Interval appropriate for the REPL.
+ * @return {string}
+ */
+ [Symbol.for("nodejs.util.inspect.custom")]() {
+ if (this.isValid) {
+ return `Interval { start: ${this.s.toISO()}, end: ${this.e.toISO()} }`;
+ } else {
+ return `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(formatOpts = Formats.DATE_SHORT, opts = {}) {
+ return this.isValid
+ ? Formatter.create(this.s.loc.clone(opts), formatOpts).formatInterval(this)
+ : INVALID;
+ }
+
+ /**
+ * 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(opts) {
+ if (!this.isValid) return INVALID;
+ return `${this.s.toISO(opts)}/${this.e.toISO(opts)}`;
+ }
+
+ /**
+ * 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() {
+ if (!this.isValid) return INVALID;
+ return `${this.s.toISODate()}/${this.e.toISODate()}`;
+ }
+
+ /**
+ * 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(opts) {
+ if (!this.isValid) return INVALID;
+ return `${this.s.toISOTime(opts)}/${this.e.toISOTime(opts)}`;
+ }
+
+ /**
+ * 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(dateFormat, { separator = " – " } = {}) {
+ if (!this.isValid) return INVALID;
+ return `${this.s.toFormat(dateFormat)}${separator}${this.e.toFormat(dateFormat)}`;
+ }
+
+ /**
+ * 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(unit, opts) {
+ if (!this.isValid) {
+ return Duration.invalid(this.invalidReason);
+ }
+ return this.e.diff(this.s, unit, opts);
+ }
+
+ /**
+ * 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(mapFn) {
+ return Interval.fromDateTimes(mapFn(this.s), mapFn(this.e));
+ }
+}