@bpmn-io/form-js-playground 0.12.1 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1521,772 +1521,6 @@
1521
1521
  timeZoneName: l$1
1522
1522
  };
1523
1523
 
1524
- /*
1525
- This is just a junk drawer, containing anything used across multiple classes.
1526
- Because Luxon is small(ish), this should stay small and we won't worry about splitting
1527
- it up into, say, parsingUtil.js and basicUtil.js and so on. But they are divided up by feature area.
1528
- */
1529
-
1530
- /**
1531
- * @private
1532
- */
1533
-
1534
- // TYPES
1535
-
1536
- function isUndefined$1(o) {
1537
- return typeof o === "undefined";
1538
- }
1539
- function isNumber$2(o) {
1540
- return typeof o === "number";
1541
- }
1542
- function isInteger(o) {
1543
- return typeof o === "number" && o % 1 === 0;
1544
- }
1545
- function isString$2(o) {
1546
- return typeof o === "string";
1547
- }
1548
- function isDate(o) {
1549
- return Object.prototype.toString.call(o) === "[object Date]";
1550
- }
1551
-
1552
- // CAPABILITIES
1553
-
1554
- function hasRelative() {
1555
- try {
1556
- return typeof Intl !== "undefined" && !!Intl.RelativeTimeFormat;
1557
- } catch (e) {
1558
- return false;
1559
- }
1560
- }
1561
-
1562
- // OBJECTS AND ARRAYS
1563
-
1564
- function maybeArray(thing) {
1565
- return Array.isArray(thing) ? thing : [thing];
1566
- }
1567
- function bestBy(arr, by, compare) {
1568
- if (arr.length === 0) {
1569
- return undefined;
1570
- }
1571
- return arr.reduce((best, next) => {
1572
- const pair = [by(next), next];
1573
- if (!best) {
1574
- return pair;
1575
- } else if (compare(best[0], pair[0]) === best[0]) {
1576
- return best;
1577
- } else {
1578
- return pair;
1579
- }
1580
- }, null)[1];
1581
- }
1582
- function pick(obj, keys) {
1583
- return keys.reduce((a, k) => {
1584
- a[k] = obj[k];
1585
- return a;
1586
- }, {});
1587
- }
1588
- function hasOwnProperty(obj, prop) {
1589
- return Object.prototype.hasOwnProperty.call(obj, prop);
1590
- }
1591
-
1592
- // NUMBERS AND STRINGS
1593
-
1594
- function integerBetween(thing, bottom, top) {
1595
- return isInteger(thing) && thing >= bottom && thing <= top;
1596
- }
1597
-
1598
- // x % n but takes the sign of n instead of x
1599
- function floorMod(x, n) {
1600
- return x - n * Math.floor(x / n);
1601
- }
1602
- function padStart(input, n = 2) {
1603
- const isNeg = input < 0;
1604
- let padded;
1605
- if (isNeg) {
1606
- padded = "-" + ("" + -input).padStart(n, "0");
1607
- } else {
1608
- padded = ("" + input).padStart(n, "0");
1609
- }
1610
- return padded;
1611
- }
1612
- function parseInteger(string) {
1613
- if (isUndefined$1(string) || string === null || string === "") {
1614
- return undefined;
1615
- } else {
1616
- return parseInt(string, 10);
1617
- }
1618
- }
1619
- function parseFloating(string) {
1620
- if (isUndefined$1(string) || string === null || string === "") {
1621
- return undefined;
1622
- } else {
1623
- return parseFloat(string);
1624
- }
1625
- }
1626
- function parseMillis(fraction) {
1627
- // Return undefined (instead of 0) in these cases, where fraction is not set
1628
- if (isUndefined$1(fraction) || fraction === null || fraction === "") {
1629
- return undefined;
1630
- } else {
1631
- const f = parseFloat("0." + fraction) * 1000;
1632
- return Math.floor(f);
1633
- }
1634
- }
1635
- function roundTo(number, digits, towardZero = false) {
1636
- const factor = 10 ** digits,
1637
- rounder = towardZero ? Math.trunc : Math.round;
1638
- return rounder(number * factor) / factor;
1639
- }
1640
-
1641
- // DATE BASICS
1642
-
1643
- function isLeapYear(year) {
1644
- return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
1645
- }
1646
- function daysInYear(year) {
1647
- return isLeapYear(year) ? 366 : 365;
1648
- }
1649
- function daysInMonth(year, month) {
1650
- const modMonth = floorMod(month - 1, 12) + 1,
1651
- modYear = year + (month - modMonth) / 12;
1652
- if (modMonth === 2) {
1653
- return isLeapYear(modYear) ? 29 : 28;
1654
- } else {
1655
- return [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][modMonth - 1];
1656
- }
1657
- }
1658
-
1659
- // covert a calendar object to a local timestamp (epoch, but with the offset baked in)
1660
- function objToLocalTS(obj) {
1661
- let d = Date.UTC(obj.year, obj.month - 1, obj.day, obj.hour, obj.minute, obj.second, obj.millisecond);
1662
-
1663
- // for legacy reasons, years between 0 and 99 are interpreted as 19XX; revert that
1664
- if (obj.year < 100 && obj.year >= 0) {
1665
- d = new Date(d);
1666
- d.setUTCFullYear(d.getUTCFullYear() - 1900);
1667
- }
1668
- return +d;
1669
- }
1670
- function weeksInWeekYear(weekYear) {
1671
- const p1 = (weekYear + Math.floor(weekYear / 4) - Math.floor(weekYear / 100) + Math.floor(weekYear / 400)) % 7,
1672
- last = weekYear - 1,
1673
- p2 = (last + Math.floor(last / 4) - Math.floor(last / 100) + Math.floor(last / 400)) % 7;
1674
- return p1 === 4 || p2 === 3 ? 53 : 52;
1675
- }
1676
- function untruncateYear(year) {
1677
- if (year > 99) {
1678
- return year;
1679
- } else return year > 60 ? 1900 + year : 2000 + year;
1680
- }
1681
-
1682
- // PARSING
1683
-
1684
- function parseZoneInfo(ts, offsetFormat, locale, timeZone = null) {
1685
- const date = new Date(ts),
1686
- intlOpts = {
1687
- hourCycle: "h23",
1688
- year: "numeric",
1689
- month: "2-digit",
1690
- day: "2-digit",
1691
- hour: "2-digit",
1692
- minute: "2-digit"
1693
- };
1694
- if (timeZone) {
1695
- intlOpts.timeZone = timeZone;
1696
- }
1697
- const modified = {
1698
- timeZoneName: offsetFormat,
1699
- ...intlOpts
1700
- };
1701
- const parsed = new Intl.DateTimeFormat(locale, modified).formatToParts(date).find(m => m.type.toLowerCase() === "timezonename");
1702
- return parsed ? parsed.value : null;
1703
- }
1704
-
1705
- // signedOffset('-5', '30') -> -330
1706
- function signedOffset(offHourStr, offMinuteStr) {
1707
- let offHour = parseInt(offHourStr, 10);
1708
-
1709
- // don't || this because we want to preserve -0
1710
- if (Number.isNaN(offHour)) {
1711
- offHour = 0;
1712
- }
1713
- const offMin = parseInt(offMinuteStr, 10) || 0,
1714
- offMinSigned = offHour < 0 || Object.is(offHour, -0) ? -offMin : offMin;
1715
- return offHour * 60 + offMinSigned;
1716
- }
1717
-
1718
- // COERCION
1719
-
1720
- function asNumber(value) {
1721
- const numericValue = Number(value);
1722
- if (typeof value === "boolean" || value === "" || Number.isNaN(numericValue)) throw new InvalidArgumentError(`Invalid unit value ${value}`);
1723
- return numericValue;
1724
- }
1725
- function normalizeObject(obj, normalizer) {
1726
- const normalized = {};
1727
- for (const u in obj) {
1728
- if (hasOwnProperty(obj, u)) {
1729
- const v = obj[u];
1730
- if (v === undefined || v === null) continue;
1731
- normalized[normalizer(u)] = asNumber(v);
1732
- }
1733
- }
1734
- return normalized;
1735
- }
1736
- function formatOffset(offset, format) {
1737
- const hours = Math.trunc(Math.abs(offset / 60)),
1738
- minutes = Math.trunc(Math.abs(offset % 60)),
1739
- sign = offset >= 0 ? "+" : "-";
1740
- switch (format) {
1741
- case "short":
1742
- return `${sign}${padStart(hours, 2)}:${padStart(minutes, 2)}`;
1743
- case "narrow":
1744
- return `${sign}${hours}${minutes > 0 ? `:${minutes}` : ""}`;
1745
- case "techie":
1746
- return `${sign}${padStart(hours, 2)}${padStart(minutes, 2)}`;
1747
- default:
1748
- throw new RangeError(`Value format ${format} is out of range for property format`);
1749
- }
1750
- }
1751
- function timeObject(obj) {
1752
- return pick(obj, ["hour", "minute", "second", "millisecond"]);
1753
- }
1754
- const ianaRegex = /[A-Za-z_+-]{1,256}(?::?\/[A-Za-z0-9_+-]{1,256}(?:\/[A-Za-z0-9_+-]{1,256})?)?/;
1755
-
1756
- /**
1757
- * @private
1758
- */
1759
-
1760
- const monthsLong = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
1761
- const monthsShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1762
- const monthsNarrow = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
1763
- function months(length) {
1764
- switch (length) {
1765
- case "narrow":
1766
- return [...monthsNarrow];
1767
- case "short":
1768
- return [...monthsShort];
1769
- case "long":
1770
- return [...monthsLong];
1771
- case "numeric":
1772
- return ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
1773
- case "2-digit":
1774
- return ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"];
1775
- default:
1776
- return null;
1777
- }
1778
- }
1779
- const weekdaysLong = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
1780
- const weekdaysShort = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
1781
- const weekdaysNarrow = ["M", "T", "W", "T", "F", "S", "S"];
1782
- function weekdays(length) {
1783
- switch (length) {
1784
- case "narrow":
1785
- return [...weekdaysNarrow];
1786
- case "short":
1787
- return [...weekdaysShort];
1788
- case "long":
1789
- return [...weekdaysLong];
1790
- case "numeric":
1791
- return ["1", "2", "3", "4", "5", "6", "7"];
1792
- default:
1793
- return null;
1794
- }
1795
- }
1796
- const meridiems = ["AM", "PM"];
1797
- const erasLong = ["Before Christ", "Anno Domini"];
1798
- const erasShort = ["BC", "AD"];
1799
- const erasNarrow = ["B", "A"];
1800
- function eras(length) {
1801
- switch (length) {
1802
- case "narrow":
1803
- return [...erasNarrow];
1804
- case "short":
1805
- return [...erasShort];
1806
- case "long":
1807
- return [...erasLong];
1808
- default:
1809
- return null;
1810
- }
1811
- }
1812
- function meridiemForDateTime(dt) {
1813
- return meridiems[dt.hour < 12 ? 0 : 1];
1814
- }
1815
- function weekdayForDateTime(dt, length) {
1816
- return weekdays(length)[dt.weekday - 1];
1817
- }
1818
- function monthForDateTime(dt, length) {
1819
- return months(length)[dt.month - 1];
1820
- }
1821
- function eraForDateTime(dt, length) {
1822
- return eras(length)[dt.year < 0 ? 0 : 1];
1823
- }
1824
- function formatRelativeTime(unit, count, numeric = "always", narrow = false) {
1825
- const units = {
1826
- years: ["year", "yr."],
1827
- quarters: ["quarter", "qtr."],
1828
- months: ["month", "mo."],
1829
- weeks: ["week", "wk."],
1830
- days: ["day", "day", "days"],
1831
- hours: ["hour", "hr."],
1832
- minutes: ["minute", "min."],
1833
- seconds: ["second", "sec."]
1834
- };
1835
- const lastable = ["hours", "minutes", "seconds"].indexOf(unit) === -1;
1836
- if (numeric === "auto" && lastable) {
1837
- const isDay = unit === "days";
1838
- switch (count) {
1839
- case 1:
1840
- return isDay ? "tomorrow" : `next ${units[unit][0]}`;
1841
- case -1:
1842
- return isDay ? "yesterday" : `last ${units[unit][0]}`;
1843
- case 0:
1844
- return isDay ? "today" : `this ${units[unit][0]}`;
1845
- }
1846
- }
1847
-
1848
- const isInPast = Object.is(count, -0) || count < 0,
1849
- fmtValue = Math.abs(count),
1850
- singular = fmtValue === 1,
1851
- lilUnits = units[unit],
1852
- fmtUnit = narrow ? singular ? lilUnits[1] : lilUnits[2] || lilUnits[1] : singular ? units[unit][0] : unit;
1853
- return isInPast ? `${fmtValue} ${fmtUnit} ago` : `in ${fmtValue} ${fmtUnit}`;
1854
- }
1855
-
1856
- function stringifyTokens(splits, tokenToString) {
1857
- let s = "";
1858
- for (const token of splits) {
1859
- if (token.literal) {
1860
- s += token.val;
1861
- } else {
1862
- s += tokenToString(token.val);
1863
- }
1864
- }
1865
- return s;
1866
- }
1867
- const macroTokenToFormatOpts = {
1868
- D: DATE_SHORT,
1869
- DD: DATE_MED,
1870
- DDD: DATE_FULL,
1871
- DDDD: DATE_HUGE,
1872
- t: TIME_SIMPLE,
1873
- tt: TIME_WITH_SECONDS,
1874
- ttt: TIME_WITH_SHORT_OFFSET,
1875
- tttt: TIME_WITH_LONG_OFFSET,
1876
- T: TIME_24_SIMPLE,
1877
- TT: TIME_24_WITH_SECONDS,
1878
- TTT: TIME_24_WITH_SHORT_OFFSET,
1879
- TTTT: TIME_24_WITH_LONG_OFFSET,
1880
- f: DATETIME_SHORT,
1881
- ff: DATETIME_MED,
1882
- fff: DATETIME_FULL,
1883
- ffff: DATETIME_HUGE,
1884
- F: DATETIME_SHORT_WITH_SECONDS,
1885
- FF: DATETIME_MED_WITH_SECONDS,
1886
- FFF: DATETIME_FULL_WITH_SECONDS,
1887
- FFFF: DATETIME_HUGE_WITH_SECONDS
1888
- };
1889
-
1890
- /**
1891
- * @private
1892
- */
1893
-
1894
- class Formatter {
1895
- static create(locale, opts = {}) {
1896
- return new Formatter(locale, opts);
1897
- }
1898
- static parseFormat(fmt) {
1899
- let current = null,
1900
- currentFull = "",
1901
- bracketed = false;
1902
- const splits = [];
1903
- for (let i = 0; i < fmt.length; i++) {
1904
- const c = fmt.charAt(i);
1905
- if (c === "'") {
1906
- if (currentFull.length > 0) {
1907
- splits.push({
1908
- literal: bracketed,
1909
- val: currentFull
1910
- });
1911
- }
1912
- current = null;
1913
- currentFull = "";
1914
- bracketed = !bracketed;
1915
- } else if (bracketed) {
1916
- currentFull += c;
1917
- } else if (c === current) {
1918
- currentFull += c;
1919
- } else {
1920
- if (currentFull.length > 0) {
1921
- splits.push({
1922
- literal: false,
1923
- val: currentFull
1924
- });
1925
- }
1926
- currentFull = c;
1927
- current = c;
1928
- }
1929
- }
1930
- if (currentFull.length > 0) {
1931
- splits.push({
1932
- literal: bracketed,
1933
- val: currentFull
1934
- });
1935
- }
1936
- return splits;
1937
- }
1938
- static macroTokenToFormatOpts(token) {
1939
- return macroTokenToFormatOpts[token];
1940
- }
1941
- constructor(locale, formatOpts) {
1942
- this.opts = formatOpts;
1943
- this.loc = locale;
1944
- this.systemLoc = null;
1945
- }
1946
- formatWithSystemDefault(dt, opts) {
1947
- if (this.systemLoc === null) {
1948
- this.systemLoc = this.loc.redefaultToSystem();
1949
- }
1950
- const df = this.systemLoc.dtFormatter(dt, {
1951
- ...this.opts,
1952
- ...opts
1953
- });
1954
- return df.format();
1955
- }
1956
- formatDateTime(dt, opts = {}) {
1957
- const df = this.loc.dtFormatter(dt, {
1958
- ...this.opts,
1959
- ...opts
1960
- });
1961
- return df.format();
1962
- }
1963
- formatDateTimeParts(dt, opts = {}) {
1964
- const df = this.loc.dtFormatter(dt, {
1965
- ...this.opts,
1966
- ...opts
1967
- });
1968
- return df.formatToParts();
1969
- }
1970
- resolvedOptions(dt, opts = {}) {
1971
- const df = this.loc.dtFormatter(dt, {
1972
- ...this.opts,
1973
- ...opts
1974
- });
1975
- return df.resolvedOptions();
1976
- }
1977
- num(n, p = 0) {
1978
- // we get some perf out of doing this here, annoyingly
1979
- if (this.opts.forceSimple) {
1980
- return padStart(n, p);
1981
- }
1982
- const opts = {
1983
- ...this.opts
1984
- };
1985
- if (p > 0) {
1986
- opts.padTo = p;
1987
- }
1988
- return this.loc.numberFormatter(opts).format(n);
1989
- }
1990
- formatDateTimeFromString(dt, fmt) {
1991
- const knownEnglish = this.loc.listingMode() === "en",
1992
- useDateTimeFormatter = this.loc.outputCalendar && this.loc.outputCalendar !== "gregory",
1993
- string = (opts, extract) => this.loc.extract(dt, opts, extract),
1994
- formatOffset = opts => {
1995
- if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) {
1996
- return "Z";
1997
- }
1998
- return dt.isValid ? dt.zone.formatOffset(dt.ts, opts.format) : "";
1999
- },
2000
- meridiem = () => knownEnglish ? meridiemForDateTime(dt) : string({
2001
- hour: "numeric",
2002
- hourCycle: "h12"
2003
- }, "dayperiod"),
2004
- month = (length, standalone) => knownEnglish ? monthForDateTime(dt, length) : string(standalone ? {
2005
- month: length
2006
- } : {
2007
- month: length,
2008
- day: "numeric"
2009
- }, "month"),
2010
- weekday = (length, standalone) => knownEnglish ? weekdayForDateTime(dt, length) : string(standalone ? {
2011
- weekday: length
2012
- } : {
2013
- weekday: length,
2014
- month: "long",
2015
- day: "numeric"
2016
- }, "weekday"),
2017
- maybeMacro = token => {
2018
- const formatOpts = Formatter.macroTokenToFormatOpts(token);
2019
- if (formatOpts) {
2020
- return this.formatWithSystemDefault(dt, formatOpts);
2021
- } else {
2022
- return token;
2023
- }
2024
- },
2025
- era = length => knownEnglish ? eraForDateTime(dt, length) : string({
2026
- era: length
2027
- }, "era"),
2028
- tokenToString = token => {
2029
- // Where possible: http://cldr.unicode.org/translation/date-time-1/date-time#TOC-Standalone-vs.-Format-Styles
2030
- switch (token) {
2031
- // ms
2032
- case "S":
2033
- return this.num(dt.millisecond);
2034
- case "u":
2035
- // falls through
2036
- case "SSS":
2037
- return this.num(dt.millisecond, 3);
2038
- // seconds
2039
- case "s":
2040
- return this.num(dt.second);
2041
- case "ss":
2042
- return this.num(dt.second, 2);
2043
- // fractional seconds
2044
- case "uu":
2045
- return this.num(Math.floor(dt.millisecond / 10), 2);
2046
- case "uuu":
2047
- return this.num(Math.floor(dt.millisecond / 100));
2048
- // minutes
2049
- case "m":
2050
- return this.num(dt.minute);
2051
- case "mm":
2052
- return this.num(dt.minute, 2);
2053
- // hours
2054
- case "h":
2055
- return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12);
2056
- case "hh":
2057
- return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12, 2);
2058
- case "H":
2059
- return this.num(dt.hour);
2060
- case "HH":
2061
- return this.num(dt.hour, 2);
2062
- // offset
2063
- case "Z":
2064
- // like +6
2065
- return formatOffset({
2066
- format: "narrow",
2067
- allowZ: this.opts.allowZ
2068
- });
2069
- case "ZZ":
2070
- // like +06:00
2071
- return formatOffset({
2072
- format: "short",
2073
- allowZ: this.opts.allowZ
2074
- });
2075
- case "ZZZ":
2076
- // like +0600
2077
- return formatOffset({
2078
- format: "techie",
2079
- allowZ: this.opts.allowZ
2080
- });
2081
- case "ZZZZ":
2082
- // like EST
2083
- return dt.zone.offsetName(dt.ts, {
2084
- format: "short",
2085
- locale: this.loc.locale
2086
- });
2087
- case "ZZZZZ":
2088
- // like Eastern Standard Time
2089
- return dt.zone.offsetName(dt.ts, {
2090
- format: "long",
2091
- locale: this.loc.locale
2092
- });
2093
- // zone
2094
- case "z":
2095
- // like America/New_York
2096
- return dt.zoneName;
2097
- // meridiems
2098
- case "a":
2099
- return meridiem();
2100
- // dates
2101
- case "d":
2102
- return useDateTimeFormatter ? string({
2103
- day: "numeric"
2104
- }, "day") : this.num(dt.day);
2105
- case "dd":
2106
- return useDateTimeFormatter ? string({
2107
- day: "2-digit"
2108
- }, "day") : this.num(dt.day, 2);
2109
- // weekdays - standalone
2110
- case "c":
2111
- // like 1
2112
- return this.num(dt.weekday);
2113
- case "ccc":
2114
- // like 'Tues'
2115
- return weekday("short", true);
2116
- case "cccc":
2117
- // like 'Tuesday'
2118
- return weekday("long", true);
2119
- case "ccccc":
2120
- // like 'T'
2121
- return weekday("narrow", true);
2122
- // weekdays - format
2123
- case "E":
2124
- // like 1
2125
- return this.num(dt.weekday);
2126
- case "EEE":
2127
- // like 'Tues'
2128
- return weekday("short", false);
2129
- case "EEEE":
2130
- // like 'Tuesday'
2131
- return weekday("long", false);
2132
- case "EEEEE":
2133
- // like 'T'
2134
- return weekday("narrow", false);
2135
- // months - standalone
2136
- case "L":
2137
- // like 1
2138
- return useDateTimeFormatter ? string({
2139
- month: "numeric",
2140
- day: "numeric"
2141
- }, "month") : this.num(dt.month);
2142
- case "LL":
2143
- // like 01, doesn't seem to work
2144
- return useDateTimeFormatter ? string({
2145
- month: "2-digit",
2146
- day: "numeric"
2147
- }, "month") : this.num(dt.month, 2);
2148
- case "LLL":
2149
- // like Jan
2150
- return month("short", true);
2151
- case "LLLL":
2152
- // like January
2153
- return month("long", true);
2154
- case "LLLLL":
2155
- // like J
2156
- return month("narrow", true);
2157
- // months - format
2158
- case "M":
2159
- // like 1
2160
- return useDateTimeFormatter ? string({
2161
- month: "numeric"
2162
- }, "month") : this.num(dt.month);
2163
- case "MM":
2164
- // like 01
2165
- return useDateTimeFormatter ? string({
2166
- month: "2-digit"
2167
- }, "month") : this.num(dt.month, 2);
2168
- case "MMM":
2169
- // like Jan
2170
- return month("short", false);
2171
- case "MMMM":
2172
- // like January
2173
- return month("long", false);
2174
- case "MMMMM":
2175
- // like J
2176
- return month("narrow", false);
2177
- // years
2178
- case "y":
2179
- // like 2014
2180
- return useDateTimeFormatter ? string({
2181
- year: "numeric"
2182
- }, "year") : this.num(dt.year);
2183
- case "yy":
2184
- // like 14
2185
- return useDateTimeFormatter ? string({
2186
- year: "2-digit"
2187
- }, "year") : this.num(dt.year.toString().slice(-2), 2);
2188
- case "yyyy":
2189
- // like 0012
2190
- return useDateTimeFormatter ? string({
2191
- year: "numeric"
2192
- }, "year") : this.num(dt.year, 4);
2193
- case "yyyyyy":
2194
- // like 000012
2195
- return useDateTimeFormatter ? string({
2196
- year: "numeric"
2197
- }, "year") : this.num(dt.year, 6);
2198
- // eras
2199
- case "G":
2200
- // like AD
2201
- return era("short");
2202
- case "GG":
2203
- // like Anno Domini
2204
- return era("long");
2205
- case "GGGGG":
2206
- return era("narrow");
2207
- case "kk":
2208
- return this.num(dt.weekYear.toString().slice(-2), 2);
2209
- case "kkkk":
2210
- return this.num(dt.weekYear, 4);
2211
- case "W":
2212
- return this.num(dt.weekNumber);
2213
- case "WW":
2214
- return this.num(dt.weekNumber, 2);
2215
- case "o":
2216
- return this.num(dt.ordinal);
2217
- case "ooo":
2218
- return this.num(dt.ordinal, 3);
2219
- case "q":
2220
- // like 1
2221
- return this.num(dt.quarter);
2222
- case "qq":
2223
- // like 01
2224
- return this.num(dt.quarter, 2);
2225
- case "X":
2226
- return this.num(Math.floor(dt.ts / 1000));
2227
- case "x":
2228
- return this.num(dt.ts);
2229
- default:
2230
- return maybeMacro(token);
2231
- }
2232
- };
2233
- return stringifyTokens(Formatter.parseFormat(fmt), tokenToString);
2234
- }
2235
- formatDurationFromString(dur, fmt) {
2236
- const tokenToField = token => {
2237
- switch (token[0]) {
2238
- case "S":
2239
- return "millisecond";
2240
- case "s":
2241
- return "second";
2242
- case "m":
2243
- return "minute";
2244
- case "h":
2245
- return "hour";
2246
- case "d":
2247
- return "day";
2248
- case "w":
2249
- return "week";
2250
- case "M":
2251
- return "month";
2252
- case "y":
2253
- return "year";
2254
- default:
2255
- return null;
2256
- }
2257
- },
2258
- tokenToString = lildur => token => {
2259
- const mapped = tokenToField(token);
2260
- if (mapped) {
2261
- return this.num(lildur.get(mapped), token.length);
2262
- } else {
2263
- return token;
2264
- }
2265
- },
2266
- tokens = Formatter.parseFormat(fmt),
2267
- realTokens = tokens.reduce((found, {
2268
- literal,
2269
- val
2270
- }) => literal ? found : found.concat(val), []),
2271
- collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter(t => t));
2272
- return stringifyTokens(tokens, tokenToString(collapsed));
2273
- }
2274
- }
2275
-
2276
- class Invalid {
2277
- constructor(reason, explanation) {
2278
- this.reason = reason;
2279
- this.explanation = explanation;
2280
- }
2281
- toMessage() {
2282
- if (this.explanation) {
2283
- return `${this.reason}: ${this.explanation}`;
2284
- } else {
2285
- return this.reason;
2286
- }
2287
- }
2288
- }
2289
-
2290
1524
  /**
2291
1525
  * @interface
2292
1526
  */
@@ -2620,6 +1854,435 @@
2620
1854
  }
2621
1855
  }
2622
1856
 
1857
+ // todo - remap caching
1858
+
1859
+ let intlLFCache = {};
1860
+ function getCachedLF(locString, opts = {}) {
1861
+ const key = JSON.stringify([locString, opts]);
1862
+ let dtf = intlLFCache[key];
1863
+ if (!dtf) {
1864
+ dtf = new Intl.ListFormat(locString, opts);
1865
+ intlLFCache[key] = dtf;
1866
+ }
1867
+ return dtf;
1868
+ }
1869
+ let intlDTCache = {};
1870
+ function getCachedDTF(locString, opts = {}) {
1871
+ const key = JSON.stringify([locString, opts]);
1872
+ let dtf = intlDTCache[key];
1873
+ if (!dtf) {
1874
+ dtf = new Intl.DateTimeFormat(locString, opts);
1875
+ intlDTCache[key] = dtf;
1876
+ }
1877
+ return dtf;
1878
+ }
1879
+ let intlNumCache = {};
1880
+ function getCachedINF(locString, opts = {}) {
1881
+ const key = JSON.stringify([locString, opts]);
1882
+ let inf = intlNumCache[key];
1883
+ if (!inf) {
1884
+ inf = new Intl.NumberFormat(locString, opts);
1885
+ intlNumCache[key] = inf;
1886
+ }
1887
+ return inf;
1888
+ }
1889
+ let intlRelCache = {};
1890
+ function getCachedRTF(locString, opts = {}) {
1891
+ const {
1892
+ base,
1893
+ ...cacheKeyOpts
1894
+ } = opts; // exclude `base` from the options
1895
+ const key = JSON.stringify([locString, cacheKeyOpts]);
1896
+ let inf = intlRelCache[key];
1897
+ if (!inf) {
1898
+ inf = new Intl.RelativeTimeFormat(locString, opts);
1899
+ intlRelCache[key] = inf;
1900
+ }
1901
+ return inf;
1902
+ }
1903
+ let sysLocaleCache = null;
1904
+ function systemLocale() {
1905
+ if (sysLocaleCache) {
1906
+ return sysLocaleCache;
1907
+ } else {
1908
+ sysLocaleCache = new Intl.DateTimeFormat().resolvedOptions().locale;
1909
+ return sysLocaleCache;
1910
+ }
1911
+ }
1912
+ function parseLocaleString(localeStr) {
1913
+ // I really want to avoid writing a BCP 47 parser
1914
+ // see, e.g. https://github.com/wooorm/bcp-47
1915
+ // Instead, we'll do this:
1916
+
1917
+ // a) if the string has no -u extensions, just leave it alone
1918
+ // b) if it does, use Intl to resolve everything
1919
+ // c) if Intl fails, try again without the -u
1920
+
1921
+ // private subtags and unicode subtags have ordering requirements,
1922
+ // and we're not properly parsing this, so just strip out the
1923
+ // private ones if they exist.
1924
+ const xIndex = localeStr.indexOf("-x-");
1925
+ if (xIndex !== -1) {
1926
+ localeStr = localeStr.substring(0, xIndex);
1927
+ }
1928
+ const uIndex = localeStr.indexOf("-u-");
1929
+ if (uIndex === -1) {
1930
+ return [localeStr];
1931
+ } else {
1932
+ let options;
1933
+ let selectedStr;
1934
+ try {
1935
+ options = getCachedDTF(localeStr).resolvedOptions();
1936
+ selectedStr = localeStr;
1937
+ } catch (e) {
1938
+ const smaller = localeStr.substring(0, uIndex);
1939
+ options = getCachedDTF(smaller).resolvedOptions();
1940
+ selectedStr = smaller;
1941
+ }
1942
+ const {
1943
+ numberingSystem,
1944
+ calendar
1945
+ } = options;
1946
+ return [selectedStr, numberingSystem, calendar];
1947
+ }
1948
+ }
1949
+ function intlConfigString(localeStr, numberingSystem, outputCalendar) {
1950
+ if (outputCalendar || numberingSystem) {
1951
+ if (!localeStr.includes("-u-")) {
1952
+ localeStr += "-u";
1953
+ }
1954
+ if (outputCalendar) {
1955
+ localeStr += `-ca-${outputCalendar}`;
1956
+ }
1957
+ if (numberingSystem) {
1958
+ localeStr += `-nu-${numberingSystem}`;
1959
+ }
1960
+ return localeStr;
1961
+ } else {
1962
+ return localeStr;
1963
+ }
1964
+ }
1965
+ function mapMonths(f) {
1966
+ const ms = [];
1967
+ for (let i = 1; i <= 12; i++) {
1968
+ const dt = DateTime.utc(2016, i, 1);
1969
+ ms.push(f(dt));
1970
+ }
1971
+ return ms;
1972
+ }
1973
+ function mapWeekdays(f) {
1974
+ const ms = [];
1975
+ for (let i = 1; i <= 7; i++) {
1976
+ const dt = DateTime.utc(2016, 11, 13 + i);
1977
+ ms.push(f(dt));
1978
+ }
1979
+ return ms;
1980
+ }
1981
+ function listStuff(loc, length, defaultOK, englishFn, intlFn) {
1982
+ const mode = loc.listingMode(defaultOK);
1983
+ if (mode === "error") {
1984
+ return null;
1985
+ } else if (mode === "en") {
1986
+ return englishFn(length);
1987
+ } else {
1988
+ return intlFn(length);
1989
+ }
1990
+ }
1991
+ function supportsFastNumbers(loc) {
1992
+ if (loc.numberingSystem && loc.numberingSystem !== "latn") {
1993
+ return false;
1994
+ } else {
1995
+ return loc.numberingSystem === "latn" || !loc.locale || loc.locale.startsWith("en") || new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === "latn";
1996
+ }
1997
+ }
1998
+
1999
+ /**
2000
+ * @private
2001
+ */
2002
+
2003
+ class PolyNumberFormatter {
2004
+ constructor(intl, forceSimple, opts) {
2005
+ this.padTo = opts.padTo || 0;
2006
+ this.floor = opts.floor || false;
2007
+ const {
2008
+ padTo,
2009
+ floor,
2010
+ ...otherOpts
2011
+ } = opts;
2012
+ if (!forceSimple || Object.keys(otherOpts).length > 0) {
2013
+ const intlOpts = {
2014
+ useGrouping: false,
2015
+ ...opts
2016
+ };
2017
+ if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo;
2018
+ this.inf = getCachedINF(intl, intlOpts);
2019
+ }
2020
+ }
2021
+ format(i) {
2022
+ if (this.inf) {
2023
+ const fixed = this.floor ? Math.floor(i) : i;
2024
+ return this.inf.format(fixed);
2025
+ } else {
2026
+ // to match the browser's numberformatter defaults
2027
+ const fixed = this.floor ? Math.floor(i) : roundTo(i, 3);
2028
+ return padStart(fixed, this.padTo);
2029
+ }
2030
+ }
2031
+ }
2032
+
2033
+ /**
2034
+ * @private
2035
+ */
2036
+
2037
+ class PolyDateFormatter {
2038
+ constructor(dt, intl, opts) {
2039
+ this.opts = opts;
2040
+ let z = undefined;
2041
+ if (dt.zone.isUniversal) {
2042
+ // UTC-8 or Etc/UTC-8 are not part of tzdata, only Etc/GMT+8 and the like.
2043
+ // That is why fixed-offset TZ is set to that unless it is:
2044
+ // 1. Representing offset 0 when UTC is used to maintain previous behavior and does not become GMT.
2045
+ // 2. Unsupported by the browser:
2046
+ // - some do not support Etc/
2047
+ // - < Etc/GMT-14, > Etc/GMT+12, and 30-minute or 45-minute offsets are not part of tzdata
2048
+ const gmtOffset = -1 * (dt.offset / 60);
2049
+ const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
2050
+ if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) {
2051
+ z = offsetZ;
2052
+ this.dt = dt;
2053
+ } else {
2054
+ // Not all fixed-offset zones like Etc/+4:30 are present in tzdata.
2055
+ // So we have to make do. Two cases:
2056
+ // 1. The format options tell us to show the zone. We can't do that, so the best
2057
+ // we can do is format the date in UTC.
2058
+ // 2. The format options don't tell us to show the zone. Then we can adjust them
2059
+ // the time and tell the formatter to show it to us in UTC, so that the time is right
2060
+ // and the bad zone doesn't show up.
2061
+ z = "UTC";
2062
+ if (opts.timeZoneName) {
2063
+ this.dt = dt;
2064
+ } else {
2065
+ this.dt = dt.offset === 0 ? dt : DateTime.fromMillis(dt.ts + dt.offset * 60 * 1000);
2066
+ }
2067
+ }
2068
+ } else if (dt.zone.type === "system") {
2069
+ this.dt = dt;
2070
+ } else {
2071
+ this.dt = dt;
2072
+ z = dt.zone.name;
2073
+ }
2074
+ const intlOpts = {
2075
+ ...this.opts
2076
+ };
2077
+ intlOpts.timeZone = intlOpts.timeZone || z;
2078
+ this.dtf = getCachedDTF(intl, intlOpts);
2079
+ }
2080
+ format() {
2081
+ return this.dtf.format(this.dt.toJSDate());
2082
+ }
2083
+ formatToParts() {
2084
+ return this.dtf.formatToParts(this.dt.toJSDate());
2085
+ }
2086
+ resolvedOptions() {
2087
+ return this.dtf.resolvedOptions();
2088
+ }
2089
+ }
2090
+
2091
+ /**
2092
+ * @private
2093
+ */
2094
+ class PolyRelFormatter {
2095
+ constructor(intl, isEnglish, opts) {
2096
+ this.opts = {
2097
+ style: "long",
2098
+ ...opts
2099
+ };
2100
+ if (!isEnglish && hasRelative()) {
2101
+ this.rtf = getCachedRTF(intl, opts);
2102
+ }
2103
+ }
2104
+ format(count, unit) {
2105
+ if (this.rtf) {
2106
+ return this.rtf.format(count, unit);
2107
+ } else {
2108
+ return formatRelativeTime(unit, count, this.opts.numeric, this.opts.style !== "long");
2109
+ }
2110
+ }
2111
+ formatToParts(count, unit) {
2112
+ if (this.rtf) {
2113
+ return this.rtf.formatToParts(count, unit);
2114
+ } else {
2115
+ return [];
2116
+ }
2117
+ }
2118
+ }
2119
+
2120
+ /**
2121
+ * @private
2122
+ */
2123
+
2124
+ class Locale {
2125
+ static fromOpts(opts) {
2126
+ return Locale.create(opts.locale, opts.numberingSystem, opts.outputCalendar, opts.defaultToEN);
2127
+ }
2128
+ static create(locale, numberingSystem, outputCalendar, defaultToEN = false) {
2129
+ const specifiedLocale = locale || Settings.defaultLocale;
2130
+ // the system locale is useful for human readable strings but annoying for parsing/formatting known formats
2131
+ const localeR = specifiedLocale || (defaultToEN ? "en-US" : systemLocale());
2132
+ const numberingSystemR = numberingSystem || Settings.defaultNumberingSystem;
2133
+ const outputCalendarR = outputCalendar || Settings.defaultOutputCalendar;
2134
+ return new Locale(localeR, numberingSystemR, outputCalendarR, specifiedLocale);
2135
+ }
2136
+ static resetCache() {
2137
+ sysLocaleCache = null;
2138
+ intlDTCache = {};
2139
+ intlNumCache = {};
2140
+ intlRelCache = {};
2141
+ }
2142
+ static fromObject({
2143
+ locale,
2144
+ numberingSystem,
2145
+ outputCalendar
2146
+ } = {}) {
2147
+ return Locale.create(locale, numberingSystem, outputCalendar);
2148
+ }
2149
+ constructor(locale, numbering, outputCalendar, specifiedLocale) {
2150
+ const [parsedLocale, parsedNumberingSystem, parsedOutputCalendar] = parseLocaleString(locale);
2151
+ this.locale = parsedLocale;
2152
+ this.numberingSystem = numbering || parsedNumberingSystem || null;
2153
+ this.outputCalendar = outputCalendar || parsedOutputCalendar || null;
2154
+ this.intl = intlConfigString(this.locale, this.numberingSystem, this.outputCalendar);
2155
+ this.weekdaysCache = {
2156
+ format: {},
2157
+ standalone: {}
2158
+ };
2159
+ this.monthsCache = {
2160
+ format: {},
2161
+ standalone: {}
2162
+ };
2163
+ this.meridiemCache = null;
2164
+ this.eraCache = {};
2165
+ this.specifiedLocale = specifiedLocale;
2166
+ this.fastNumbersCached = null;
2167
+ }
2168
+ get fastNumbers() {
2169
+ if (this.fastNumbersCached == null) {
2170
+ this.fastNumbersCached = supportsFastNumbers(this);
2171
+ }
2172
+ return this.fastNumbersCached;
2173
+ }
2174
+ listingMode() {
2175
+ const isActuallyEn = this.isEnglish();
2176
+ const hasNoWeirdness = (this.numberingSystem === null || this.numberingSystem === "latn") && (this.outputCalendar === null || this.outputCalendar === "gregory");
2177
+ return isActuallyEn && hasNoWeirdness ? "en" : "intl";
2178
+ }
2179
+ clone(alts) {
2180
+ if (!alts || Object.getOwnPropertyNames(alts).length === 0) {
2181
+ return this;
2182
+ } else {
2183
+ return Locale.create(alts.locale || this.specifiedLocale, alts.numberingSystem || this.numberingSystem, alts.outputCalendar || this.outputCalendar, alts.defaultToEN || false);
2184
+ }
2185
+ }
2186
+ redefaultToEN(alts = {}) {
2187
+ return this.clone({
2188
+ ...alts,
2189
+ defaultToEN: true
2190
+ });
2191
+ }
2192
+ redefaultToSystem(alts = {}) {
2193
+ return this.clone({
2194
+ ...alts,
2195
+ defaultToEN: false
2196
+ });
2197
+ }
2198
+ months(length, format = false, defaultOK = true) {
2199
+ return listStuff(this, length, defaultOK, months, () => {
2200
+ const intl = format ? {
2201
+ month: length,
2202
+ day: "numeric"
2203
+ } : {
2204
+ month: length
2205
+ },
2206
+ formatStr = format ? "format" : "standalone";
2207
+ if (!this.monthsCache[formatStr][length]) {
2208
+ this.monthsCache[formatStr][length] = mapMonths(dt => this.extract(dt, intl, "month"));
2209
+ }
2210
+ return this.monthsCache[formatStr][length];
2211
+ });
2212
+ }
2213
+ weekdays(length, format = false, defaultOK = true) {
2214
+ return listStuff(this, length, defaultOK, weekdays, () => {
2215
+ const intl = format ? {
2216
+ weekday: length,
2217
+ year: "numeric",
2218
+ month: "long",
2219
+ day: "numeric"
2220
+ } : {
2221
+ weekday: length
2222
+ },
2223
+ formatStr = format ? "format" : "standalone";
2224
+ if (!this.weekdaysCache[formatStr][length]) {
2225
+ this.weekdaysCache[formatStr][length] = mapWeekdays(dt => this.extract(dt, intl, "weekday"));
2226
+ }
2227
+ return this.weekdaysCache[formatStr][length];
2228
+ });
2229
+ }
2230
+ meridiems(defaultOK = true) {
2231
+ return listStuff(this, undefined, defaultOK, () => meridiems, () => {
2232
+ // In theory there could be aribitrary day periods. We're gonna assume there are exactly two
2233
+ // for AM and PM. This is probably wrong, but it's makes parsing way easier.
2234
+ if (!this.meridiemCache) {
2235
+ const intl = {
2236
+ hour: "numeric",
2237
+ hourCycle: "h12"
2238
+ };
2239
+ this.meridiemCache = [DateTime.utc(2016, 11, 13, 9), DateTime.utc(2016, 11, 13, 19)].map(dt => this.extract(dt, intl, "dayperiod"));
2240
+ }
2241
+ return this.meridiemCache;
2242
+ });
2243
+ }
2244
+ eras(length, defaultOK = true) {
2245
+ return listStuff(this, length, defaultOK, eras, () => {
2246
+ const intl = {
2247
+ era: length
2248
+ };
2249
+
2250
+ // This is problematic. Different calendars are going to define eras totally differently. What I need is the minimum set of dates
2251
+ // to definitely enumerate them.
2252
+ if (!this.eraCache[length]) {
2253
+ this.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map(dt => this.extract(dt, intl, "era"));
2254
+ }
2255
+ return this.eraCache[length];
2256
+ });
2257
+ }
2258
+ extract(dt, intlOpts, field) {
2259
+ const df = this.dtFormatter(dt, intlOpts),
2260
+ results = df.formatToParts(),
2261
+ matching = results.find(m => m.type.toLowerCase() === field);
2262
+ return matching ? matching.value : null;
2263
+ }
2264
+ numberFormatter(opts = {}) {
2265
+ // this forcesimple option is never used (the only caller short-circuits on it, but it seems safer to leave)
2266
+ // (in contrast, the rest of the condition is used heavily)
2267
+ return new PolyNumberFormatter(this.intl, opts.forceSimple || this.fastNumbers, opts);
2268
+ }
2269
+ dtFormatter(dt, intlOpts = {}) {
2270
+ return new PolyDateFormatter(dt, this.intl, intlOpts);
2271
+ }
2272
+ relFormatter(opts = {}) {
2273
+ return new PolyRelFormatter(this.intl, this.isEnglish(), opts);
2274
+ }
2275
+ listFormatter(opts = {}) {
2276
+ return getCachedLF(this.intl, opts);
2277
+ }
2278
+ isEnglish() {
2279
+ return this.locale === "en" || this.locale.toLowerCase() === "en-us" || new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us");
2280
+ }
2281
+ equals(other) {
2282
+ return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
2283
+ }
2284
+ }
2285
+
2623
2286
  let singleton = null;
2624
2287
 
2625
2288
  /**
@@ -2797,6 +2460,7 @@
2797
2460
  defaultLocale = null,
2798
2461
  defaultNumberingSystem = null,
2799
2462
  defaultOutputCalendar = null,
2463
+ twoDigitCutoffYear = 60,
2800
2464
  throwOnInvalid;
2801
2465
 
2802
2466
  /**
@@ -2888,6 +2552,26 @@
2888
2552
  defaultOutputCalendar = outputCalendar;
2889
2553
  }
2890
2554
 
2555
+ /**
2556
+ * Get the cutoff year after which a string encoding a year as two digits is interpreted to occur in the current century.
2557
+ * @type {number}
2558
+ */
2559
+ static get twoDigitCutoffYear() {
2560
+ return twoDigitCutoffYear;
2561
+ }
2562
+
2563
+ /**
2564
+ * Set the cutoff year after which a string encoding a year as two digits is interpreted to occur in the current century.
2565
+ * @type {number}
2566
+ * @example Settings.twoDigitCutoffYear = 0 // cut-off year is 0, so all 'yy' are interpretted as current century
2567
+ * @example Settings.twoDigitCutoffYear = 50 // '49' -> 1949; '50' -> 2050
2568
+ * @example Settings.twoDigitCutoffYear = 1950 // interpretted as 50
2569
+ * @example Settings.twoDigitCutoffYear = 2050 // ALSO interpretted as 50
2570
+ */
2571
+ static set twoDigitCutoffYear(cutoffYear) {
2572
+ twoDigitCutoffYear = cutoffYear % 100;
2573
+ }
2574
+
2891
2575
  /**
2892
2576
  * Get whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals
2893
2577
  * @type {boolean}
@@ -2896,441 +2580,793 @@
2896
2580
  return throwOnInvalid;
2897
2581
  }
2898
2582
 
2899
- /**
2900
- * Set whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals
2901
- * @type {boolean}
2902
- */
2903
- static set throwOnInvalid(t) {
2904
- throwOnInvalid = t;
2905
- }
2583
+ /**
2584
+ * Set whether Luxon will throw when it encounters invalid DateTimes, Durations, or Intervals
2585
+ * @type {boolean}
2586
+ */
2587
+ static set throwOnInvalid(t) {
2588
+ throwOnInvalid = t;
2589
+ }
2590
+
2591
+ /**
2592
+ * Reset Luxon's global caches. Should only be necessary in testing scenarios.
2593
+ * @return {void}
2594
+ */
2595
+ static resetCaches() {
2596
+ Locale.resetCache();
2597
+ IANAZone.resetCache();
2598
+ }
2599
+ }
2600
+
2601
+ /*
2602
+ This is just a junk drawer, containing anything used across multiple classes.
2603
+ Because Luxon is small(ish), this should stay small and we won't worry about splitting
2604
+ it up into, say, parsingUtil.js and basicUtil.js and so on. But they are divided up by feature area.
2605
+ */
2606
+
2607
+ /**
2608
+ * @private
2609
+ */
2610
+
2611
+ // TYPES
2612
+
2613
+ function isUndefined$1(o) {
2614
+ return typeof o === "undefined";
2615
+ }
2616
+ function isNumber$2(o) {
2617
+ return typeof o === "number";
2618
+ }
2619
+ function isInteger(o) {
2620
+ return typeof o === "number" && o % 1 === 0;
2621
+ }
2622
+ function isString$2(o) {
2623
+ return typeof o === "string";
2624
+ }
2625
+ function isDate(o) {
2626
+ return Object.prototype.toString.call(o) === "[object Date]";
2627
+ }
2628
+
2629
+ // CAPABILITIES
2630
+
2631
+ function hasRelative() {
2632
+ try {
2633
+ return typeof Intl !== "undefined" && !!Intl.RelativeTimeFormat;
2634
+ } catch (e) {
2635
+ return false;
2636
+ }
2637
+ }
2638
+
2639
+ // OBJECTS AND ARRAYS
2640
+
2641
+ function maybeArray(thing) {
2642
+ return Array.isArray(thing) ? thing : [thing];
2643
+ }
2644
+ function bestBy(arr, by, compare) {
2645
+ if (arr.length === 0) {
2646
+ return undefined;
2647
+ }
2648
+ return arr.reduce((best, next) => {
2649
+ const pair = [by(next), next];
2650
+ if (!best) {
2651
+ return pair;
2652
+ } else if (compare(best[0], pair[0]) === best[0]) {
2653
+ return best;
2654
+ } else {
2655
+ return pair;
2656
+ }
2657
+ }, null)[1];
2658
+ }
2659
+ function pick(obj, keys) {
2660
+ return keys.reduce((a, k) => {
2661
+ a[k] = obj[k];
2662
+ return a;
2663
+ }, {});
2664
+ }
2665
+ function hasOwnProperty(obj, prop) {
2666
+ return Object.prototype.hasOwnProperty.call(obj, prop);
2667
+ }
2668
+
2669
+ // NUMBERS AND STRINGS
2906
2670
 
2907
- /**
2908
- * Reset Luxon's global caches. Should only be necessary in testing scenarios.
2909
- * @return {void}
2910
- */
2911
- static resetCaches() {
2912
- Locale.resetCache();
2913
- IANAZone.resetCache();
2914
- }
2671
+ function integerBetween(thing, bottom, top) {
2672
+ return isInteger(thing) && thing >= bottom && thing <= top;
2915
2673
  }
2916
2674
 
2917
- // todo - remap caching
2918
-
2919
- let intlLFCache = {};
2920
- function getCachedLF(locString, opts = {}) {
2921
- const key = JSON.stringify([locString, opts]);
2922
- let dtf = intlLFCache[key];
2923
- if (!dtf) {
2924
- dtf = new Intl.ListFormat(locString, opts);
2925
- intlLFCache[key] = dtf;
2926
- }
2927
- return dtf;
2675
+ // x % n but takes the sign of n instead of x
2676
+ function floorMod(x, n) {
2677
+ return x - n * Math.floor(x / n);
2928
2678
  }
2929
- let intlDTCache = {};
2930
- function getCachedDTF(locString, opts = {}) {
2931
- const key = JSON.stringify([locString, opts]);
2932
- let dtf = intlDTCache[key];
2933
- if (!dtf) {
2934
- dtf = new Intl.DateTimeFormat(locString, opts);
2935
- intlDTCache[key] = dtf;
2679
+ function padStart(input, n = 2) {
2680
+ const isNeg = input < 0;
2681
+ let padded;
2682
+ if (isNeg) {
2683
+ padded = "-" + ("" + -input).padStart(n, "0");
2684
+ } else {
2685
+ padded = ("" + input).padStart(n, "0");
2936
2686
  }
2937
- return dtf;
2687
+ return padded;
2938
2688
  }
2939
- let intlNumCache = {};
2940
- function getCachedINF(locString, opts = {}) {
2941
- const key = JSON.stringify([locString, opts]);
2942
- let inf = intlNumCache[key];
2943
- if (!inf) {
2944
- inf = new Intl.NumberFormat(locString, opts);
2945
- intlNumCache[key] = inf;
2689
+ function parseInteger(string) {
2690
+ if (isUndefined$1(string) || string === null || string === "") {
2691
+ return undefined;
2692
+ } else {
2693
+ return parseInt(string, 10);
2946
2694
  }
2947
- return inf;
2948
2695
  }
2949
- let intlRelCache = {};
2950
- function getCachedRTF(locString, opts = {}) {
2951
- const {
2952
- base,
2953
- ...cacheKeyOpts
2954
- } = opts; // exclude `base` from the options
2955
- const key = JSON.stringify([locString, cacheKeyOpts]);
2956
- let inf = intlRelCache[key];
2957
- if (!inf) {
2958
- inf = new Intl.RelativeTimeFormat(locString, opts);
2959
- intlRelCache[key] = inf;
2696
+ function parseFloating(string) {
2697
+ if (isUndefined$1(string) || string === null || string === "") {
2698
+ return undefined;
2699
+ } else {
2700
+ return parseFloat(string);
2960
2701
  }
2961
- return inf;
2962
2702
  }
2963
- let sysLocaleCache = null;
2964
- function systemLocale() {
2965
- if (sysLocaleCache) {
2966
- return sysLocaleCache;
2703
+ function parseMillis(fraction) {
2704
+ // Return undefined (instead of 0) in these cases, where fraction is not set
2705
+ if (isUndefined$1(fraction) || fraction === null || fraction === "") {
2706
+ return undefined;
2967
2707
  } else {
2968
- sysLocaleCache = new Intl.DateTimeFormat().resolvedOptions().locale;
2969
- return sysLocaleCache;
2708
+ const f = parseFloat("0." + fraction) * 1000;
2709
+ return Math.floor(f);
2970
2710
  }
2971
2711
  }
2972
- function parseLocaleString(localeStr) {
2973
- // I really want to avoid writing a BCP 47 parser
2974
- // see, e.g. https://github.com/wooorm/bcp-47
2975
- // Instead, we'll do this:
2712
+ function roundTo(number, digits, towardZero = false) {
2713
+ const factor = 10 ** digits,
2714
+ rounder = towardZero ? Math.trunc : Math.round;
2715
+ return rounder(number * factor) / factor;
2716
+ }
2976
2717
 
2977
- // a) if the string has no -u extensions, just leave it alone
2978
- // b) if it does, use Intl to resolve everything
2979
- // c) if Intl fails, try again without the -u
2718
+ // DATE BASICS
2980
2719
 
2981
- const uIndex = localeStr.indexOf("-u-");
2982
- if (uIndex === -1) {
2983
- return [localeStr];
2984
- } else {
2985
- let options;
2986
- const smaller = localeStr.substring(0, uIndex);
2987
- try {
2988
- options = getCachedDTF(localeStr).resolvedOptions();
2989
- } catch (e) {
2990
- options = getCachedDTF(smaller).resolvedOptions();
2991
- }
2992
- const {
2993
- numberingSystem,
2994
- calendar
2995
- } = options;
2996
- // return the smaller one so that we can append the calendar and numbering overrides to it
2997
- return [smaller, numberingSystem, calendar];
2998
- }
2720
+ function isLeapYear(year) {
2721
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
2999
2722
  }
3000
- function intlConfigString(localeStr, numberingSystem, outputCalendar) {
3001
- if (outputCalendar || numberingSystem) {
3002
- localeStr += "-u";
3003
- if (outputCalendar) {
3004
- localeStr += `-ca-${outputCalendar}`;
3005
- }
3006
- if (numberingSystem) {
3007
- localeStr += `-nu-${numberingSystem}`;
3008
- }
3009
- return localeStr;
2723
+ function daysInYear(year) {
2724
+ return isLeapYear(year) ? 366 : 365;
2725
+ }
2726
+ function daysInMonth(year, month) {
2727
+ const modMonth = floorMod(month - 1, 12) + 1,
2728
+ modYear = year + (month - modMonth) / 12;
2729
+ if (modMonth === 2) {
2730
+ return isLeapYear(modYear) ? 29 : 28;
3010
2731
  } else {
3011
- return localeStr;
2732
+ return [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][modMonth - 1];
3012
2733
  }
3013
2734
  }
3014
- function mapMonths(f) {
3015
- const ms = [];
3016
- for (let i = 1; i <= 12; i++) {
3017
- const dt = DateTime.utc(2016, i, 1);
3018
- ms.push(f(dt));
2735
+
2736
+ // covert a calendar object to a local timestamp (epoch, but with the offset baked in)
2737
+ function objToLocalTS(obj) {
2738
+ let d = Date.UTC(obj.year, obj.month - 1, obj.day, obj.hour, obj.minute, obj.second, obj.millisecond);
2739
+
2740
+ // for legacy reasons, years between 0 and 99 are interpreted as 19XX; revert that
2741
+ if (obj.year < 100 && obj.year >= 0) {
2742
+ d = new Date(d);
2743
+ d.setUTCFullYear(d.getUTCFullYear() - 1900);
3019
2744
  }
3020
- return ms;
2745
+ return +d;
3021
2746
  }
3022
- function mapWeekdays(f) {
3023
- const ms = [];
3024
- for (let i = 1; i <= 7; i++) {
3025
- const dt = DateTime.utc(2016, 11, 13 + i);
3026
- ms.push(f(dt));
3027
- }
3028
- return ms;
2747
+ function weeksInWeekYear(weekYear) {
2748
+ const p1 = (weekYear + Math.floor(weekYear / 4) - Math.floor(weekYear / 100) + Math.floor(weekYear / 400)) % 7,
2749
+ last = weekYear - 1,
2750
+ p2 = (last + Math.floor(last / 4) - Math.floor(last / 100) + Math.floor(last / 400)) % 7;
2751
+ return p1 === 4 || p2 === 3 ? 53 : 52;
3029
2752
  }
3030
- function listStuff(loc, length, defaultOK, englishFn, intlFn) {
3031
- const mode = loc.listingMode(defaultOK);
3032
- if (mode === "error") {
3033
- return null;
3034
- } else if (mode === "en") {
3035
- return englishFn(length);
3036
- } else {
3037
- return intlFn(length);
2753
+ function untruncateYear(year) {
2754
+ if (year > 99) {
2755
+ return year;
2756
+ } else return year > Settings.twoDigitCutoffYear ? 1900 + year : 2000 + year;
2757
+ }
2758
+
2759
+ // PARSING
2760
+
2761
+ function parseZoneInfo(ts, offsetFormat, locale, timeZone = null) {
2762
+ const date = new Date(ts),
2763
+ intlOpts = {
2764
+ hourCycle: "h23",
2765
+ year: "numeric",
2766
+ month: "2-digit",
2767
+ day: "2-digit",
2768
+ hour: "2-digit",
2769
+ minute: "2-digit"
2770
+ };
2771
+ if (timeZone) {
2772
+ intlOpts.timeZone = timeZone;
3038
2773
  }
2774
+ const modified = {
2775
+ timeZoneName: offsetFormat,
2776
+ ...intlOpts
2777
+ };
2778
+ const parsed = new Intl.DateTimeFormat(locale, modified).formatToParts(date).find(m => m.type.toLowerCase() === "timezonename");
2779
+ return parsed ? parsed.value : null;
3039
2780
  }
3040
- function supportsFastNumbers(loc) {
3041
- if (loc.numberingSystem && loc.numberingSystem !== "latn") {
3042
- return false;
3043
- } else {
3044
- return loc.numberingSystem === "latn" || !loc.locale || loc.locale.startsWith("en") || new Intl.DateTimeFormat(loc.intl).resolvedOptions().numberingSystem === "latn";
2781
+
2782
+ // signedOffset('-5', '30') -> -330
2783
+ function signedOffset(offHourStr, offMinuteStr) {
2784
+ let offHour = parseInt(offHourStr, 10);
2785
+
2786
+ // don't || this because we want to preserve -0
2787
+ if (Number.isNaN(offHour)) {
2788
+ offHour = 0;
3045
2789
  }
2790
+ const offMin = parseInt(offMinuteStr, 10) || 0,
2791
+ offMinSigned = offHour < 0 || Object.is(offHour, -0) ? -offMin : offMin;
2792
+ return offHour * 60 + offMinSigned;
3046
2793
  }
3047
2794
 
3048
- /**
3049
- * @private
3050
- */
2795
+ // COERCION
3051
2796
 
3052
- class PolyNumberFormatter {
3053
- constructor(intl, forceSimple, opts) {
3054
- this.padTo = opts.padTo || 0;
3055
- this.floor = opts.floor || false;
3056
- const {
3057
- padTo,
3058
- floor,
3059
- ...otherOpts
3060
- } = opts;
3061
- if (!forceSimple || Object.keys(otherOpts).length > 0) {
3062
- const intlOpts = {
3063
- useGrouping: false,
3064
- ...opts
3065
- };
3066
- if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo;
3067
- this.inf = getCachedINF(intl, intlOpts);
2797
+ function asNumber(value) {
2798
+ const numericValue = Number(value);
2799
+ if (typeof value === "boolean" || value === "" || Number.isNaN(numericValue)) throw new InvalidArgumentError(`Invalid unit value ${value}`);
2800
+ return numericValue;
2801
+ }
2802
+ function normalizeObject(obj, normalizer) {
2803
+ const normalized = {};
2804
+ for (const u in obj) {
2805
+ if (hasOwnProperty(obj, u)) {
2806
+ const v = obj[u];
2807
+ if (v === undefined || v === null) continue;
2808
+ normalized[normalizer(u)] = asNumber(v);
3068
2809
  }
3069
2810
  }
3070
- format(i) {
3071
- if (this.inf) {
3072
- const fixed = this.floor ? Math.floor(i) : i;
3073
- return this.inf.format(fixed);
3074
- } else {
3075
- // to match the browser's numberformatter defaults
3076
- const fixed = this.floor ? Math.floor(i) : roundTo(i, 3);
3077
- return padStart(fixed, this.padTo);
3078
- }
2811
+ return normalized;
2812
+ }
2813
+ function formatOffset(offset, format) {
2814
+ const hours = Math.trunc(Math.abs(offset / 60)),
2815
+ minutes = Math.trunc(Math.abs(offset % 60)),
2816
+ sign = offset >= 0 ? "+" : "-";
2817
+ switch (format) {
2818
+ case "short":
2819
+ return `${sign}${padStart(hours, 2)}:${padStart(minutes, 2)}`;
2820
+ case "narrow":
2821
+ return `${sign}${hours}${minutes > 0 ? `:${minutes}` : ""}`;
2822
+ case "techie":
2823
+ return `${sign}${padStart(hours, 2)}${padStart(minutes, 2)}`;
2824
+ default:
2825
+ throw new RangeError(`Value format ${format} is out of range for property format`);
3079
2826
  }
3080
2827
  }
2828
+ function timeObject(obj) {
2829
+ return pick(obj, ["hour", "minute", "second", "millisecond"]);
2830
+ }
3081
2831
 
3082
2832
  /**
3083
2833
  * @private
3084
2834
  */
3085
2835
 
3086
- class PolyDateFormatter {
3087
- constructor(dt, intl, opts) {
3088
- this.opts = opts;
3089
- let z;
3090
- if (dt.zone.isUniversal) {
3091
- // UTC-8 or Etc/UTC-8 are not part of tzdata, only Etc/GMT+8 and the like.
3092
- // That is why fixed-offset TZ is set to that unless it is:
3093
- // 1. Representing offset 0 when UTC is used to maintain previous behavior and does not become GMT.
3094
- // 2. Unsupported by the browser:
3095
- // - some do not support Etc/
3096
- // - < Etc/GMT-14, > Etc/GMT+12, and 30-minute or 45-minute offsets are not part of tzdata
3097
- const gmtOffset = -1 * (dt.offset / 60);
3098
- const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
3099
- if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) {
3100
- z = offsetZ;
3101
- this.dt = dt;
3102
- } else {
3103
- // Not all fixed-offset zones like Etc/+4:30 are present in tzdata.
3104
- // So we have to make do. Two cases:
3105
- // 1. The format options tell us to show the zone. We can't do that, so the best
3106
- // we can do is format the date in UTC.
3107
- // 2. The format options don't tell us to show the zone. Then we can adjust them
3108
- // the time and tell the formatter to show it to us in UTC, so that the time is right
3109
- // and the bad zone doesn't show up.
3110
- z = "UTC";
3111
- if (opts.timeZoneName) {
3112
- this.dt = dt;
3113
- } else {
3114
- this.dt = dt.offset === 0 ? dt : DateTime.fromMillis(dt.ts + dt.offset * 60 * 1000);
3115
- }
3116
- }
3117
- } else if (dt.zone.type === "system") {
3118
- this.dt = dt;
3119
- } else {
3120
- this.dt = dt;
3121
- z = dt.zone.name;
3122
- }
3123
- const intlOpts = {
3124
- ...this.opts
3125
- };
3126
- if (z) {
3127
- intlOpts.timeZone = z;
3128
- }
3129
- this.dtf = getCachedDTF(intl, intlOpts);
3130
- }
3131
- format() {
3132
- return this.dtf.format(this.dt.toJSDate());
3133
- }
3134
- formatToParts() {
3135
- return this.dtf.formatToParts(this.dt.toJSDate());
2836
+ const monthsLong = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
2837
+ const monthsShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2838
+ const monthsNarrow = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
2839
+ function months(length) {
2840
+ switch (length) {
2841
+ case "narrow":
2842
+ return [...monthsNarrow];
2843
+ case "short":
2844
+ return [...monthsShort];
2845
+ case "long":
2846
+ return [...monthsLong];
2847
+ case "numeric":
2848
+ return ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
2849
+ case "2-digit":
2850
+ return ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"];
2851
+ default:
2852
+ return null;
3136
2853
  }
3137
- resolvedOptions() {
3138
- return this.dtf.resolvedOptions();
2854
+ }
2855
+ const weekdaysLong = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
2856
+ const weekdaysShort = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
2857
+ const weekdaysNarrow = ["M", "T", "W", "T", "F", "S", "S"];
2858
+ function weekdays(length) {
2859
+ switch (length) {
2860
+ case "narrow":
2861
+ return [...weekdaysNarrow];
2862
+ case "short":
2863
+ return [...weekdaysShort];
2864
+ case "long":
2865
+ return [...weekdaysLong];
2866
+ case "numeric":
2867
+ return ["1", "2", "3", "4", "5", "6", "7"];
2868
+ default:
2869
+ return null;
3139
2870
  }
3140
2871
  }
3141
-
3142
- /**
3143
- * @private
3144
- */
3145
- class PolyRelFormatter {
3146
- constructor(intl, isEnglish, opts) {
3147
- this.opts = {
3148
- style: "long",
3149
- ...opts
3150
- };
3151
- if (!isEnglish && hasRelative()) {
3152
- this.rtf = getCachedRTF(intl, opts);
3153
- }
2872
+ const meridiems = ["AM", "PM"];
2873
+ const erasLong = ["Before Christ", "Anno Domini"];
2874
+ const erasShort = ["BC", "AD"];
2875
+ const erasNarrow = ["B", "A"];
2876
+ function eras(length) {
2877
+ switch (length) {
2878
+ case "narrow":
2879
+ return [...erasNarrow];
2880
+ case "short":
2881
+ return [...erasShort];
2882
+ case "long":
2883
+ return [...erasLong];
2884
+ default:
2885
+ return null;
3154
2886
  }
3155
- format(count, unit) {
3156
- if (this.rtf) {
3157
- return this.rtf.format(count, unit);
3158
- } else {
3159
- return formatRelativeTime(unit, count, this.opts.numeric, this.opts.style !== "long");
2887
+ }
2888
+ function meridiemForDateTime(dt) {
2889
+ return meridiems[dt.hour < 12 ? 0 : 1];
2890
+ }
2891
+ function weekdayForDateTime(dt, length) {
2892
+ return weekdays(length)[dt.weekday - 1];
2893
+ }
2894
+ function monthForDateTime(dt, length) {
2895
+ return months(length)[dt.month - 1];
2896
+ }
2897
+ function eraForDateTime(dt, length) {
2898
+ return eras(length)[dt.year < 0 ? 0 : 1];
2899
+ }
2900
+ function formatRelativeTime(unit, count, numeric = "always", narrow = false) {
2901
+ const units = {
2902
+ years: ["year", "yr."],
2903
+ quarters: ["quarter", "qtr."],
2904
+ months: ["month", "mo."],
2905
+ weeks: ["week", "wk."],
2906
+ days: ["day", "day", "days"],
2907
+ hours: ["hour", "hr."],
2908
+ minutes: ["minute", "min."],
2909
+ seconds: ["second", "sec."]
2910
+ };
2911
+ const lastable = ["hours", "minutes", "seconds"].indexOf(unit) === -1;
2912
+ if (numeric === "auto" && lastable) {
2913
+ const isDay = unit === "days";
2914
+ switch (count) {
2915
+ case 1:
2916
+ return isDay ? "tomorrow" : `next ${units[unit][0]}`;
2917
+ case -1:
2918
+ return isDay ? "yesterday" : `last ${units[unit][0]}`;
2919
+ case 0:
2920
+ return isDay ? "today" : `this ${units[unit][0]}`;
3160
2921
  }
3161
2922
  }
3162
- formatToParts(count, unit) {
3163
- if (this.rtf) {
3164
- return this.rtf.formatToParts(count, unit);
2923
+
2924
+ const isInPast = Object.is(count, -0) || count < 0,
2925
+ fmtValue = Math.abs(count),
2926
+ singular = fmtValue === 1,
2927
+ lilUnits = units[unit],
2928
+ fmtUnit = narrow ? singular ? lilUnits[1] : lilUnits[2] || lilUnits[1] : singular ? units[unit][0] : unit;
2929
+ return isInPast ? `${fmtValue} ${fmtUnit} ago` : `in ${fmtValue} ${fmtUnit}`;
2930
+ }
2931
+
2932
+ function stringifyTokens(splits, tokenToString) {
2933
+ let s = "";
2934
+ for (const token of splits) {
2935
+ if (token.literal) {
2936
+ s += token.val;
3165
2937
  } else {
3166
- return [];
2938
+ s += tokenToString(token.val);
3167
2939
  }
3168
2940
  }
2941
+ return s;
3169
2942
  }
3170
-
3171
- /**
3172
- * @private
3173
- */
3174
-
3175
- class Locale {
3176
- static fromOpts(opts) {
3177
- return Locale.create(opts.locale, opts.numberingSystem, opts.outputCalendar, opts.defaultToEN);
3178
- }
3179
- static create(locale, numberingSystem, outputCalendar, defaultToEN = false) {
3180
- const specifiedLocale = locale || Settings.defaultLocale;
3181
- // the system locale is useful for human readable strings but annoying for parsing/formatting known formats
3182
- const localeR = specifiedLocale || (defaultToEN ? "en-US" : systemLocale());
3183
- const numberingSystemR = numberingSystem || Settings.defaultNumberingSystem;
3184
- const outputCalendarR = outputCalendar || Settings.defaultOutputCalendar;
3185
- return new Locale(localeR, numberingSystemR, outputCalendarR, specifiedLocale);
3186
- }
3187
- static resetCache() {
3188
- sysLocaleCache = null;
3189
- intlDTCache = {};
3190
- intlNumCache = {};
3191
- intlRelCache = {};
3192
- }
3193
- static fromObject({
3194
- locale,
3195
- numberingSystem,
3196
- outputCalendar
3197
- } = {}) {
3198
- return Locale.create(locale, numberingSystem, outputCalendar);
3199
- }
3200
- constructor(locale, numbering, outputCalendar, specifiedLocale) {
3201
- const [parsedLocale, parsedNumberingSystem, parsedOutputCalendar] = parseLocaleString(locale);
3202
- this.locale = parsedLocale;
3203
- this.numberingSystem = numbering || parsedNumberingSystem || null;
3204
- this.outputCalendar = outputCalendar || parsedOutputCalendar || null;
3205
- this.intl = intlConfigString(this.locale, this.numberingSystem, this.outputCalendar);
3206
- this.weekdaysCache = {
3207
- format: {},
3208
- standalone: {}
3209
- };
3210
- this.monthsCache = {
3211
- format: {},
3212
- standalone: {}
3213
- };
3214
- this.meridiemCache = null;
3215
- this.eraCache = {};
3216
- this.specifiedLocale = specifiedLocale;
3217
- this.fastNumbersCached = null;
2943
+ const macroTokenToFormatOpts = {
2944
+ D: DATE_SHORT,
2945
+ DD: DATE_MED,
2946
+ DDD: DATE_FULL,
2947
+ DDDD: DATE_HUGE,
2948
+ t: TIME_SIMPLE,
2949
+ tt: TIME_WITH_SECONDS,
2950
+ ttt: TIME_WITH_SHORT_OFFSET,
2951
+ tttt: TIME_WITH_LONG_OFFSET,
2952
+ T: TIME_24_SIMPLE,
2953
+ TT: TIME_24_WITH_SECONDS,
2954
+ TTT: TIME_24_WITH_SHORT_OFFSET,
2955
+ TTTT: TIME_24_WITH_LONG_OFFSET,
2956
+ f: DATETIME_SHORT,
2957
+ ff: DATETIME_MED,
2958
+ fff: DATETIME_FULL,
2959
+ ffff: DATETIME_HUGE,
2960
+ F: DATETIME_SHORT_WITH_SECONDS,
2961
+ FF: DATETIME_MED_WITH_SECONDS,
2962
+ FFF: DATETIME_FULL_WITH_SECONDS,
2963
+ FFFF: DATETIME_HUGE_WITH_SECONDS
2964
+ };
2965
+
2966
+ /**
2967
+ * @private
2968
+ */
2969
+
2970
+ class Formatter {
2971
+ static create(locale, opts = {}) {
2972
+ return new Formatter(locale, opts);
3218
2973
  }
3219
- get fastNumbers() {
3220
- if (this.fastNumbersCached == null) {
3221
- this.fastNumbersCached = supportsFastNumbers(this);
2974
+ static parseFormat(fmt) {
2975
+ let current = null,
2976
+ currentFull = "",
2977
+ bracketed = false;
2978
+ const splits = [];
2979
+ for (let i = 0; i < fmt.length; i++) {
2980
+ const c = fmt.charAt(i);
2981
+ if (c === "'") {
2982
+ if (currentFull.length > 0) {
2983
+ splits.push({
2984
+ literal: bracketed,
2985
+ val: currentFull
2986
+ });
2987
+ }
2988
+ current = null;
2989
+ currentFull = "";
2990
+ bracketed = !bracketed;
2991
+ } else if (bracketed) {
2992
+ currentFull += c;
2993
+ } else if (c === current) {
2994
+ currentFull += c;
2995
+ } else {
2996
+ if (currentFull.length > 0) {
2997
+ splits.push({
2998
+ literal: false,
2999
+ val: currentFull
3000
+ });
3001
+ }
3002
+ currentFull = c;
3003
+ current = c;
3004
+ }
3222
3005
  }
3223
- return this.fastNumbersCached;
3006
+ if (currentFull.length > 0) {
3007
+ splits.push({
3008
+ literal: bracketed,
3009
+ val: currentFull
3010
+ });
3011
+ }
3012
+ return splits;
3224
3013
  }
3225
- listingMode() {
3226
- const isActuallyEn = this.isEnglish();
3227
- const hasNoWeirdness = (this.numberingSystem === null || this.numberingSystem === "latn") && (this.outputCalendar === null || this.outputCalendar === "gregory");
3228
- return isActuallyEn && hasNoWeirdness ? "en" : "intl";
3014
+ static macroTokenToFormatOpts(token) {
3015
+ return macroTokenToFormatOpts[token];
3229
3016
  }
3230
- clone(alts) {
3231
- if (!alts || Object.getOwnPropertyNames(alts).length === 0) {
3232
- return this;
3233
- } else {
3234
- return Locale.create(alts.locale || this.specifiedLocale, alts.numberingSystem || this.numberingSystem, alts.outputCalendar || this.outputCalendar, alts.defaultToEN || false);
3235
- }
3017
+ constructor(locale, formatOpts) {
3018
+ this.opts = formatOpts;
3019
+ this.loc = locale;
3020
+ this.systemLoc = null;
3236
3021
  }
3237
- redefaultToEN(alts = {}) {
3238
- return this.clone({
3239
- ...alts,
3240
- defaultToEN: true
3022
+ formatWithSystemDefault(dt, opts) {
3023
+ if (this.systemLoc === null) {
3024
+ this.systemLoc = this.loc.redefaultToSystem();
3025
+ }
3026
+ const df = this.systemLoc.dtFormatter(dt, {
3027
+ ...this.opts,
3028
+ ...opts
3241
3029
  });
3030
+ return df.format();
3242
3031
  }
3243
- redefaultToSystem(alts = {}) {
3244
- return this.clone({
3245
- ...alts,
3246
- defaultToEN: false
3032
+ formatDateTime(dt, opts = {}) {
3033
+ const df = this.loc.dtFormatter(dt, {
3034
+ ...this.opts,
3035
+ ...opts
3247
3036
  });
3037
+ return df.format();
3248
3038
  }
3249
- months(length, format = false, defaultOK = true) {
3250
- return listStuff(this, length, defaultOK, months, () => {
3251
- const intl = format ? {
3252
- month: length,
3253
- day: "numeric"
3254
- } : {
3255
- month: length
3256
- },
3257
- formatStr = format ? "format" : "standalone";
3258
- if (!this.monthsCache[formatStr][length]) {
3259
- this.monthsCache[formatStr][length] = mapMonths(dt => this.extract(dt, intl, "month"));
3260
- }
3261
- return this.monthsCache[formatStr][length];
3039
+ formatDateTimeParts(dt, opts = {}) {
3040
+ const df = this.loc.dtFormatter(dt, {
3041
+ ...this.opts,
3042
+ ...opts
3262
3043
  });
3044
+ return df.formatToParts();
3263
3045
  }
3264
- weekdays(length, format = false, defaultOK = true) {
3265
- return listStuff(this, length, defaultOK, weekdays, () => {
3266
- const intl = format ? {
3267
- weekday: length,
3268
- year: "numeric",
3269
- month: "long",
3270
- day: "numeric"
3271
- } : {
3272
- weekday: length
3273
- },
3274
- formatStr = format ? "format" : "standalone";
3275
- if (!this.weekdaysCache[formatStr][length]) {
3276
- this.weekdaysCache[formatStr][length] = mapWeekdays(dt => this.extract(dt, intl, "weekday"));
3277
- }
3278
- return this.weekdaysCache[formatStr][length];
3046
+ formatInterval(interval, opts = {}) {
3047
+ const df = this.loc.dtFormatter(interval.start, {
3048
+ ...this.opts,
3049
+ ...opts
3279
3050
  });
3051
+ return df.dtf.formatRange(interval.start.toJSDate(), interval.end.toJSDate());
3280
3052
  }
3281
- meridiems(defaultOK = true) {
3282
- return listStuff(this, undefined, defaultOK, () => meridiems, () => {
3283
- // In theory there could be aribitrary day periods. We're gonna assume there are exactly two
3284
- // for AM and PM. This is probably wrong, but it's makes parsing way easier.
3285
- if (!this.meridiemCache) {
3286
- const intl = {
3287
- hour: "numeric",
3288
- hourCycle: "h12"
3289
- };
3290
- this.meridiemCache = [DateTime.utc(2016, 11, 13, 9), DateTime.utc(2016, 11, 13, 19)].map(dt => this.extract(dt, intl, "dayperiod"));
3291
- }
3292
- return this.meridiemCache;
3053
+ resolvedOptions(dt, opts = {}) {
3054
+ const df = this.loc.dtFormatter(dt, {
3055
+ ...this.opts,
3056
+ ...opts
3293
3057
  });
3058
+ return df.resolvedOptions();
3294
3059
  }
3295
- eras(length, defaultOK = true) {
3296
- return listStuff(this, length, defaultOK, eras, () => {
3297
- const intl = {
3060
+ num(n, p = 0) {
3061
+ // we get some perf out of doing this here, annoyingly
3062
+ if (this.opts.forceSimple) {
3063
+ return padStart(n, p);
3064
+ }
3065
+ const opts = {
3066
+ ...this.opts
3067
+ };
3068
+ if (p > 0) {
3069
+ opts.padTo = p;
3070
+ }
3071
+ return this.loc.numberFormatter(opts).format(n);
3072
+ }
3073
+ formatDateTimeFromString(dt, fmt) {
3074
+ const knownEnglish = this.loc.listingMode() === "en",
3075
+ useDateTimeFormatter = this.loc.outputCalendar && this.loc.outputCalendar !== "gregory",
3076
+ string = (opts, extract) => this.loc.extract(dt, opts, extract),
3077
+ formatOffset = opts => {
3078
+ if (dt.isOffsetFixed && dt.offset === 0 && opts.allowZ) {
3079
+ return "Z";
3080
+ }
3081
+ return dt.isValid ? dt.zone.formatOffset(dt.ts, opts.format) : "";
3082
+ },
3083
+ meridiem = () => knownEnglish ? meridiemForDateTime(dt) : string({
3084
+ hour: "numeric",
3085
+ hourCycle: "h12"
3086
+ }, "dayperiod"),
3087
+ month = (length, standalone) => knownEnglish ? monthForDateTime(dt, length) : string(standalone ? {
3088
+ month: length
3089
+ } : {
3090
+ month: length,
3091
+ day: "numeric"
3092
+ }, "month"),
3093
+ weekday = (length, standalone) => knownEnglish ? weekdayForDateTime(dt, length) : string(standalone ? {
3094
+ weekday: length
3095
+ } : {
3096
+ weekday: length,
3097
+ month: "long",
3098
+ day: "numeric"
3099
+ }, "weekday"),
3100
+ maybeMacro = token => {
3101
+ const formatOpts = Formatter.macroTokenToFormatOpts(token);
3102
+ if (formatOpts) {
3103
+ return this.formatWithSystemDefault(dt, formatOpts);
3104
+ } else {
3105
+ return token;
3106
+ }
3107
+ },
3108
+ era = length => knownEnglish ? eraForDateTime(dt, length) : string({
3298
3109
  era: length
3110
+ }, "era"),
3111
+ tokenToString = token => {
3112
+ // Where possible: http://cldr.unicode.org/translation/date-time-1/date-time#TOC-Standalone-vs.-Format-Styles
3113
+ switch (token) {
3114
+ // ms
3115
+ case "S":
3116
+ return this.num(dt.millisecond);
3117
+ case "u":
3118
+ // falls through
3119
+ case "SSS":
3120
+ return this.num(dt.millisecond, 3);
3121
+ // seconds
3122
+ case "s":
3123
+ return this.num(dt.second);
3124
+ case "ss":
3125
+ return this.num(dt.second, 2);
3126
+ // fractional seconds
3127
+ case "uu":
3128
+ return this.num(Math.floor(dt.millisecond / 10), 2);
3129
+ case "uuu":
3130
+ return this.num(Math.floor(dt.millisecond / 100));
3131
+ // minutes
3132
+ case "m":
3133
+ return this.num(dt.minute);
3134
+ case "mm":
3135
+ return this.num(dt.minute, 2);
3136
+ // hours
3137
+ case "h":
3138
+ return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12);
3139
+ case "hh":
3140
+ return this.num(dt.hour % 12 === 0 ? 12 : dt.hour % 12, 2);
3141
+ case "H":
3142
+ return this.num(dt.hour);
3143
+ case "HH":
3144
+ return this.num(dt.hour, 2);
3145
+ // offset
3146
+ case "Z":
3147
+ // like +6
3148
+ return formatOffset({
3149
+ format: "narrow",
3150
+ allowZ: this.opts.allowZ
3151
+ });
3152
+ case "ZZ":
3153
+ // like +06:00
3154
+ return formatOffset({
3155
+ format: "short",
3156
+ allowZ: this.opts.allowZ
3157
+ });
3158
+ case "ZZZ":
3159
+ // like +0600
3160
+ return formatOffset({
3161
+ format: "techie",
3162
+ allowZ: this.opts.allowZ
3163
+ });
3164
+ case "ZZZZ":
3165
+ // like EST
3166
+ return dt.zone.offsetName(dt.ts, {
3167
+ format: "short",
3168
+ locale: this.loc.locale
3169
+ });
3170
+ case "ZZZZZ":
3171
+ // like Eastern Standard Time
3172
+ return dt.zone.offsetName(dt.ts, {
3173
+ format: "long",
3174
+ locale: this.loc.locale
3175
+ });
3176
+ // zone
3177
+ case "z":
3178
+ // like America/New_York
3179
+ return dt.zoneName;
3180
+ // meridiems
3181
+ case "a":
3182
+ return meridiem();
3183
+ // dates
3184
+ case "d":
3185
+ return useDateTimeFormatter ? string({
3186
+ day: "numeric"
3187
+ }, "day") : this.num(dt.day);
3188
+ case "dd":
3189
+ return useDateTimeFormatter ? string({
3190
+ day: "2-digit"
3191
+ }, "day") : this.num(dt.day, 2);
3192
+ // weekdays - standalone
3193
+ case "c":
3194
+ // like 1
3195
+ return this.num(dt.weekday);
3196
+ case "ccc":
3197
+ // like 'Tues'
3198
+ return weekday("short", true);
3199
+ case "cccc":
3200
+ // like 'Tuesday'
3201
+ return weekday("long", true);
3202
+ case "ccccc":
3203
+ // like 'T'
3204
+ return weekday("narrow", true);
3205
+ // weekdays - format
3206
+ case "E":
3207
+ // like 1
3208
+ return this.num(dt.weekday);
3209
+ case "EEE":
3210
+ // like 'Tues'
3211
+ return weekday("short", false);
3212
+ case "EEEE":
3213
+ // like 'Tuesday'
3214
+ return weekday("long", false);
3215
+ case "EEEEE":
3216
+ // like 'T'
3217
+ return weekday("narrow", false);
3218
+ // months - standalone
3219
+ case "L":
3220
+ // like 1
3221
+ return useDateTimeFormatter ? string({
3222
+ month: "numeric",
3223
+ day: "numeric"
3224
+ }, "month") : this.num(dt.month);
3225
+ case "LL":
3226
+ // like 01, doesn't seem to work
3227
+ return useDateTimeFormatter ? string({
3228
+ month: "2-digit",
3229
+ day: "numeric"
3230
+ }, "month") : this.num(dt.month, 2);
3231
+ case "LLL":
3232
+ // like Jan
3233
+ return month("short", true);
3234
+ case "LLLL":
3235
+ // like January
3236
+ return month("long", true);
3237
+ case "LLLLL":
3238
+ // like J
3239
+ return month("narrow", true);
3240
+ // months - format
3241
+ case "M":
3242
+ // like 1
3243
+ return useDateTimeFormatter ? string({
3244
+ month: "numeric"
3245
+ }, "month") : this.num(dt.month);
3246
+ case "MM":
3247
+ // like 01
3248
+ return useDateTimeFormatter ? string({
3249
+ month: "2-digit"
3250
+ }, "month") : this.num(dt.month, 2);
3251
+ case "MMM":
3252
+ // like Jan
3253
+ return month("short", false);
3254
+ case "MMMM":
3255
+ // like January
3256
+ return month("long", false);
3257
+ case "MMMMM":
3258
+ // like J
3259
+ return month("narrow", false);
3260
+ // years
3261
+ case "y":
3262
+ // like 2014
3263
+ return useDateTimeFormatter ? string({
3264
+ year: "numeric"
3265
+ }, "year") : this.num(dt.year);
3266
+ case "yy":
3267
+ // like 14
3268
+ return useDateTimeFormatter ? string({
3269
+ year: "2-digit"
3270
+ }, "year") : this.num(dt.year.toString().slice(-2), 2);
3271
+ case "yyyy":
3272
+ // like 0012
3273
+ return useDateTimeFormatter ? string({
3274
+ year: "numeric"
3275
+ }, "year") : this.num(dt.year, 4);
3276
+ case "yyyyyy":
3277
+ // like 000012
3278
+ return useDateTimeFormatter ? string({
3279
+ year: "numeric"
3280
+ }, "year") : this.num(dt.year, 6);
3281
+ // eras
3282
+ case "G":
3283
+ // like AD
3284
+ return era("short");
3285
+ case "GG":
3286
+ // like Anno Domini
3287
+ return era("long");
3288
+ case "GGGGG":
3289
+ return era("narrow");
3290
+ case "kk":
3291
+ return this.num(dt.weekYear.toString().slice(-2), 2);
3292
+ case "kkkk":
3293
+ return this.num(dt.weekYear, 4);
3294
+ case "W":
3295
+ return this.num(dt.weekNumber);
3296
+ case "WW":
3297
+ return this.num(dt.weekNumber, 2);
3298
+ case "o":
3299
+ return this.num(dt.ordinal);
3300
+ case "ooo":
3301
+ return this.num(dt.ordinal, 3);
3302
+ case "q":
3303
+ // like 1
3304
+ return this.num(dt.quarter);
3305
+ case "qq":
3306
+ // like 01
3307
+ return this.num(dt.quarter, 2);
3308
+ case "X":
3309
+ return this.num(Math.floor(dt.ts / 1000));
3310
+ case "x":
3311
+ return this.num(dt.ts);
3312
+ default:
3313
+ return maybeMacro(token);
3314
+ }
3299
3315
  };
3300
-
3301
- // This is problematic. Different calendars are going to define eras totally differently. What I need is the minimum set of dates
3302
- // to definitely enumerate them.
3303
- if (!this.eraCache[length]) {
3304
- this.eraCache[length] = [DateTime.utc(-40, 1, 1), DateTime.utc(2017, 1, 1)].map(dt => this.extract(dt, intl, "era"));
3305
- }
3306
- return this.eraCache[length];
3307
- });
3308
- }
3309
- extract(dt, intlOpts, field) {
3310
- const df = this.dtFormatter(dt, intlOpts),
3311
- results = df.formatToParts(),
3312
- matching = results.find(m => m.type.toLowerCase() === field);
3313
- return matching ? matching.value : null;
3314
- }
3315
- numberFormatter(opts = {}) {
3316
- // this forcesimple option is never used (the only caller short-circuits on it, but it seems safer to leave)
3317
- // (in contrast, the rest of the condition is used heavily)
3318
- return new PolyNumberFormatter(this.intl, opts.forceSimple || this.fastNumbers, opts);
3319
- }
3320
- dtFormatter(dt, intlOpts = {}) {
3321
- return new PolyDateFormatter(dt, this.intl, intlOpts);
3322
- }
3323
- relFormatter(opts = {}) {
3324
- return new PolyRelFormatter(this.intl, this.isEnglish(), opts);
3316
+ return stringifyTokens(Formatter.parseFormat(fmt), tokenToString);
3325
3317
  }
3326
- listFormatter(opts = {}) {
3327
- return getCachedLF(this.intl, opts);
3318
+ formatDurationFromString(dur, fmt) {
3319
+ const tokenToField = token => {
3320
+ switch (token[0]) {
3321
+ case "S":
3322
+ return "millisecond";
3323
+ case "s":
3324
+ return "second";
3325
+ case "m":
3326
+ return "minute";
3327
+ case "h":
3328
+ return "hour";
3329
+ case "d":
3330
+ return "day";
3331
+ case "w":
3332
+ return "week";
3333
+ case "M":
3334
+ return "month";
3335
+ case "y":
3336
+ return "year";
3337
+ default:
3338
+ return null;
3339
+ }
3340
+ },
3341
+ tokenToString = lildur => token => {
3342
+ const mapped = tokenToField(token);
3343
+ if (mapped) {
3344
+ return this.num(lildur.get(mapped), token.length);
3345
+ } else {
3346
+ return token;
3347
+ }
3348
+ },
3349
+ tokens = Formatter.parseFormat(fmt),
3350
+ realTokens = tokens.reduce((found, {
3351
+ literal,
3352
+ val
3353
+ }) => literal ? found : found.concat(val), []),
3354
+ collapsed = dur.shiftTo(...realTokens.map(tokenToField).filter(t => t));
3355
+ return stringifyTokens(tokens, tokenToString(collapsed));
3328
3356
  }
3329
- isEnglish() {
3330
- return this.locale === "en" || this.locale.toLowerCase() === "en-us" || new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us");
3357
+ }
3358
+
3359
+ class Invalid {
3360
+ constructor(reason, explanation) {
3361
+ this.reason = reason;
3362
+ this.explanation = explanation;
3331
3363
  }
3332
- equals(other) {
3333
- return this.locale === other.locale && this.numberingSystem === other.numberingSystem && this.outputCalendar === other.outputCalendar;
3364
+ toMessage() {
3365
+ if (this.explanation) {
3366
+ return `${this.reason}: ${this.explanation}`;
3367
+ } else {
3368
+ return this.reason;
3369
+ }
3334
3370
  }
3335
3371
  }
3336
3372
 
@@ -3344,6 +3380,7 @@
3344
3380
  * Some extractions are super dumb and simpleParse and fromStrings help DRY them.
3345
3381
  */
3346
3382
 
3383
+ const ianaRegex = /[A-Za-z_+-]{1,256}(?::?\/[A-Za-z0-9_+-]{1,256}(?:\/[A-Za-z0-9_+-]{1,256})?)?/;
3347
3384
  function combineRegexes(...regexes) {
3348
3385
  const full = regexes.reduce((f, r) => f + r.source, "");
3349
3386
  return RegExp(`^${full}$`);
@@ -3496,7 +3533,7 @@
3496
3533
  }
3497
3534
  function preprocessRFC2822(s) {
3498
3535
  // Remove comments and folding whitespace and replace multiple-spaces with a single space
3499
- return s.replace(/\([^)]*\)|[\n\t]/g, " ").replace(/(\s\s+)/g, " ").trim();
3536
+ return s.replace(/\([^()]*\)|[\n\t]/g, " ").replace(/(\s\s+)/g, " ").trim();
3500
3537
  }
3501
3538
 
3502
3539
  // http date
@@ -3690,6 +3727,17 @@
3690
3727
  }, null);
3691
3728
  }
3692
3729
 
3730
+ // Remove all properties with a value of 0 from an object
3731
+ function removeZeroes(vals) {
3732
+ const newVals = {};
3733
+ for (const [key, value] of Object.entries(vals)) {
3734
+ if (value !== 0) {
3735
+ newVals[key] = value;
3736
+ }
3737
+ }
3738
+ return newVals;
3739
+ }
3740
+
3693
3741
  /**
3694
3742
  * A Duration object represents a period of time, like "2 months" or "1 day, 1 hour". Conceptually, it's just a map of units to their quantities, accompanied by some additional configuration and methods for creating, parsing, interrogating, transforming, and formatting them. They can be used on their own or in conjunction with other Luxon types; for example, you can use {@link DateTime#plus} to add a Duration object to a DateTime, producing another DateTime.
3695
3743
  *
@@ -4238,6 +4286,19 @@
4238
4286
  }, true);
4239
4287
  }
4240
4288
 
4289
+ /**
4290
+ * Rescale units to its largest representation
4291
+ * @example Duration.fromObject({ milliseconds: 90000 }).rescale().toObject() //=> { minutes: 1, seconds: 30 }
4292
+ * @return {Duration}
4293
+ */
4294
+ rescale() {
4295
+ if (!this.isValid) return this;
4296
+ const vals = removeZeroes(this.normalize().shiftToAll().toObject());
4297
+ return clone$2(this, {
4298
+ values: vals
4299
+ }, true);
4300
+ }
4301
+
4241
4302
  /**
4242
4303
  * Convert this Duration into its representation in a different set of units.
4243
4304
  * @example Duration.fromObject({ hours: 1, seconds: 30 }).shiftTo('minutes', 'milliseconds').toObject() //=> { minutes: 60, milliseconds: 30000 }
@@ -4296,6 +4357,16 @@
4296
4357
  }, true).normalize();
4297
4358
  }
4298
4359
 
4360
+ /**
4361
+ * Shift this Duration to all available units.
4362
+ * Same as shiftTo("years", "months", "weeks", "days", "hours", "minutes", "seconds", "milliseconds")
4363
+ * @return {Duration}
4364
+ */
4365
+ shiftToAll() {
4366
+ if (!this.isValid) return this;
4367
+ return this.shiftTo("years", "months", "weeks", "days", "hours", "minutes", "seconds", "milliseconds");
4368
+ }
4369
+
4299
4370
  /**
4300
4371
  * Return the negative of this Duration.
4301
4372
  * @example Duration.fromObject({ hours: 1, seconds: 30 }).negate().toObject() //=> { hours: -1, seconds: -30 }
@@ -4461,7 +4532,7 @@
4461
4532
  * * **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}.
4462
4533
  * * **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}.
4463
4534
  * * **Comparison** To compare this Interval to another one, use {@link Interval#equals}, {@link Interval#overlaps}, {@link Interval#abutsStart}, {@link Interval#abutsEnd}, {@link Interval#engulfs}
4464
- * * **Output** To convert the Interval into other representations, see {@link Interval#toString}, {@link Interval#toISO}, {@link Interval#toISODate}, {@link Interval#toISOTime}, {@link Interval#toFormat}, and {@link Interval#toDuration}.
4535
+ * * **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}.
4465
4536
  */
4466
4537
  class Interval {
4467
4538
  /**
@@ -4941,6 +5012,28 @@
4941
5012
  return `[${this.s.toISO()} – ${this.e.toISO()})`;
4942
5013
  }
4943
5014
 
5015
+ /**
5016
+ * Returns a localized string representing this Interval. Accepts the same options as the
5017
+ * Intl.DateTimeFormat constructor and any presets defined by Luxon, such as
5018
+ * {@link DateTime.DATE_FULL} or {@link DateTime.TIME_SIMPLE}. The exact behavior of this method
5019
+ * is browser-specific, but in general it will return an appropriate representation of the
5020
+ * Interval in the assigned locale. Defaults to the system's locale if no locale has been
5021
+ * specified.
5022
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
5023
+ * @param {Object} [formatOpts=DateTime.DATE_SHORT] - Either a DateTime preset or
5024
+ * Intl.DateTimeFormat constructor options.
5025
+ * @param {Object} opts - Options to override the configuration of the start DateTime.
5026
+ * @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(); //=> 11/7/2022 – 11/8/2022
5027
+ * @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL); //=> November 7 – 8, 2022
5028
+ * @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL, { locale: 'fr-FR' }); //=> 7–8 novembre 2022
5029
+ * @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString(DateTime.TIME_SIMPLE); //=> 6:00 – 8:00 PM
5030
+ * @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
5031
+ * @return {string}
5032
+ */
5033
+ toLocaleString(formatOpts = DATE_SHORT, opts = {}) {
5034
+ return this.isValid ? Formatter.create(this.s.loc.clone(opts), formatOpts).formatInterval(this) : INVALID$3;
5035
+ }
5036
+
4944
5037
  /**
4945
5038
  * Returns an ISO 8601-compliant string representation of this Interval.
4946
5039
  * @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
@@ -4976,10 +5069,14 @@
4976
5069
  }
4977
5070
 
4978
5071
  /**
4979
- * Returns a string representation of this Interval formatted according to the specified format string.
4980
- * @param {string} dateFormat - the format string. This string formats the start and end time. See {@link DateTime#toFormat} for details.
4981
- * @param {Object} opts - options
4982
- * @param {string} [opts.separator = ' '] - a separator to place between the start and end representations
5072
+ * Returns a string representation of this Interval formatted according to the specified format
5073
+ * string. **You may not want this.** See {@link Interval#toLocaleString} for a more flexible
5074
+ * formatting tool.
5075
+ * @param {string} dateFormat - The format string. This string formats the start and end time.
5076
+ * See {@link DateTime#toFormat} for details.
5077
+ * @param {Object} opts - Options.
5078
+ * @param {string} [opts.separator = ' – '] - A separator to place between the start and end
5079
+ * representations.
4983
5080
  * @return {string}
4984
5081
  */
4985
5082
  toFormat(dateFormat, {
@@ -5213,23 +5310,19 @@
5213
5310
  return (days - days % 7) / 7;
5214
5311
  }], ["days", dayDiff]];
5215
5312
  const results = {};
5313
+ const earlier = cursor;
5216
5314
  let lowestOrder, highWater;
5217
5315
  for (const [unit, differ] of differs) {
5218
5316
  if (units.indexOf(unit) >= 0) {
5219
5317
  lowestOrder = unit;
5220
- let delta = differ(cursor, later);
5221
- highWater = cursor.plus({
5222
- [unit]: delta
5223
- });
5318
+ results[unit] = differ(cursor, later);
5319
+ highWater = earlier.plus(results);
5224
5320
  if (highWater > later) {
5225
- cursor = cursor.plus({
5226
- [unit]: delta - 1
5227
- });
5228
- delta -= 1;
5321
+ results[unit]--;
5322
+ cursor = earlier.plus(results);
5229
5323
  } else {
5230
5324
  cursor = highWater;
5231
5325
  }
5232
- results[unit] = delta;
5233
5326
  }
5234
5327
  }
5235
5328
  return [cursor, results, highWater, lowestOrder];
@@ -5554,7 +5647,7 @@
5554
5647
  short: "ZZZ"
5555
5648
  }
5556
5649
  };
5557
- function tokenForPart(part, locale, formatOpts) {
5650
+ function tokenForPart(part, formatOpts) {
5558
5651
  const {
5559
5652
  type,
5560
5653
  value
@@ -5743,7 +5836,7 @@
5743
5836
  }
5744
5837
  const formatter = Formatter.create(locale, formatOpts);
5745
5838
  const parts = formatter.formatDateTimeParts(getDummyDateTime());
5746
- return parts.map(p => tokenForPart(p, locale, formatOpts));
5839
+ return parts.map(p => tokenForPart(p, formatOpts));
5747
5840
  }
5748
5841
 
5749
5842
  const nonLeapLadder = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
@@ -7765,7 +7858,7 @@
7765
7858
 
7766
7859
  /**
7767
7860
  * Equality check
7768
- * Two DateTimes are equal iff they represent the same millisecond, have the same zone and location, and are both valid.
7861
+ * Two DateTimes are equal if and only if they represent the same millisecond, have the same zone and location, and are both valid.
7769
7862
  * To compare just the millisecond values, use `+dt1 === +dt2`.
7770
7863
  * @param {DateTime} other - the other DateTime
7771
7864
  * @return {boolean}
@@ -47486,7 +47579,7 @@
47486
47579
  return e$1("div", {
47487
47580
  class: "fjs-palette-field fjs-drag-copy fjs-no-drop",
47488
47581
  "data-field-type": type,
47489
- title: `Create a ${label} element`,
47582
+ title: `Create ${getIndefiniteArticle(type)} ${label} element`,
47490
47583
  children: [Icon ? e$1(Icon, {
47491
47584
  class: "fjs-palette-field-icon",
47492
47585
  width: "36",
@@ -47525,6 +47618,12 @@
47525
47618
  });
47526
47619
  return groups.filter(g => g.entries.length);
47527
47620
  }
47621
+ function getIndefiniteArticle(type) {
47622
+ if (['image'].includes(type)) {
47623
+ return 'an';
47624
+ }
47625
+ return 'a';
47626
+ }
47528
47627
  const CURSOR_CLS_PATTERN = /^fjs-cursor-.*$/;
47529
47628
  function set(mode) {
47530
47629
  const classes$1 = classes(document.body);
@@ -52289,6 +52388,7 @@
52289
52388
  setValue
52290
52389
  });
52291
52390
  }
52391
+ const EMPTY_OPTION = null;
52292
52392
  function DefaultOptionEntry(props) {
52293
52393
  const {
52294
52394
  editField,
@@ -52434,13 +52534,14 @@
52434
52534
  label
52435
52535
  } = props;
52436
52536
  const {
52437
- defaultValue,
52537
+ defaultValue = EMPTY_OPTION,
52438
52538
  values = []
52439
52539
  } = field;
52440
52540
  const path = ['defaultValue'];
52441
52541
  const getOptions = () => {
52442
52542
  return [{
52443
- label: '<none>'
52543
+ label: '<none>',
52544
+ value: EMPTY_OPTION
52444
52545
  }, ...values];
52445
52546
  };
52446
52547
  const setValue = value => {