diff --git a/docs/install.md b/docs/install.md
index f29a875bf..13da769ea 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -66,8 +66,8 @@ requirejs(["luxon"], function(luxon) {
## ES6
-- [Download full](https://moment.github.io/luxon/es6/luxon.js)
-- [Download minified](https://moment.github.io/luxon/es6/luxon.min.js)
+- [Download full](https://moment.github.io/luxon/es6/luxon.mjs)
+- [Download minified](https://moment.github.io/luxon/es6/luxon.min.mjs)
```js
import { DateTime } from "luxon";
diff --git a/package.json b/package.json
index f80269b17..ed1f34b9c 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
"uglify-js": "^3.13.10"
},
"main": "build/node/luxon.js",
- "module": "src/luxon.js",
+ "module": "build/es6/luxon.mjs",
"browser": "build/cjs-browser/luxon.js",
"jsdelivr": "build/global/luxon.min.js",
"unpkg": "build/global/luxon.min.js",
diff --git a/site/plugins/dark-theme-toggle.css b/site/plugins/dark-theme-toggle.css
index 463a79174..11129d4f6 100644
--- a/site/plugins/dark-theme-toggle.css
+++ b/site/plugins/dark-theme-toggle.css
@@ -1,4 +1,4 @@
-#docsify-dark-theme-toggle {
+.docsify-dark-theme-toggle {
position: absolute;
display: inline-block;
width: 52px;
@@ -13,30 +13,38 @@
.sidebar > .app-name {
position: relative;
}
-.app-name > #docsify-dark-theme-toggle {
+.app-name > .docsify-dark-theme-toggle {
margin-right: 1rem;
margin-top: 0;
right: 0;
left: unset;
+ transition: opacity 250ms ease, visibility 250ms steps(1, jump-both);
+ opacity: 0;
+ visibility: hidden;
+}
+.sticky .app-name > .docsify-dark-theme-toggle {
+ opacity: 1;
+ visibility: visible;
}
-#docsify-dark-theme-toggle::before, #docsify-dark-theme-toggle::after {
+
+.docsify-dark-theme-toggle::before, .docsify-dark-theme-toggle::after {
position: absolute;
top: 0.1em;
font-size: 16px;
transition: opacity 0.3s;
}
-#docsify-dark-theme-toggle::before {
+.docsify-dark-theme-toggle::before {
content: "🌙";
left: 0.1em;
opacity: 0;
z-index: 1;
}
-#docsify-dark-theme-toggle::after {
+.docsify-dark-theme-toggle::after {
content: "🌞";
right: 0.1em;
opacity: 1;
}
-#docsify-dark-theme-toggle > span {
+.docsify-dark-theme-toggle > span {
position: absolute;
top: 0;
left: 0;
@@ -46,7 +54,7 @@
border-radius: 28px;
transition: background-color 0.3s;
}
-#docsify-dark-theme-toggle > span::before {
+.docsify-dark-theme-toggle > span::before {
content: "";
position: absolute;
height: 22px;
@@ -58,15 +66,15 @@
border-radius: 50%;
transition: transform 0.3s;
}
-.dark #docsify-dark-theme-toggle::after {
+.dark .docsify-dark-theme-toggle::after {
opacity: 0;
}
-.dark #docsify-dark-theme-toggle::before {
+.dark .docsify-dark-theme-toggle::before {
opacity: 1;
}
-.dark #docsify-dark-theme-toggle > span {
+.dark .docsify-dark-theme-toggle > span {
background-color: var(--theme-color, #ea6f5a);
}
-.dark #docsify-dark-theme-toggle > span::before {
+.dark .docsify-dark-theme-toggle > span::before {
transform: translateX(24px);
-}
\ No newline at end of file
+}
diff --git a/site/plugins/dark-theme-toggle.js b/site/plugins/dark-theme-toggle.js
index 13c2e68e9..e6f74cfff 100644
--- a/site/plugins/dark-theme-toggle.js
+++ b/site/plugins/dark-theme-toggle.js
@@ -3,7 +3,6 @@
const TOGGLE_ID = "docsify-dark-theme-toggle",
dom = Docsify.dom,
darkThemeStyleSheet = dom.find('link[href$="dark.css"]'),
- toggleEl = dom.create("div", ""),
applyTheme = (swap = false) => {
const isDark = Boolean(swap ^ (localStorage[TOGGLE_ID] == "true"));
localStorage[TOGGLE_ID] = isDark;
@@ -11,10 +10,18 @@
dom.toggleClass(dom.body, isDark ? "add" : "remove", "dark");
};
localStorage[TOGGLE_ID] ??= matchMedia("(prefers-color-scheme: dark)").matches;
- toggleEl.id = TOGGLE_ID;
- dom.on(toggleEl, "click", () => applyTheme(true));
hook.init(applyTheme);
- hook.doneEach(() => dom.before(dom.find(".cover.show, .sidebar > .app-name"), toggleEl));
+ hook.doneEach(() => {
+ dom.findAll(".cover.show, .sidebar > .app-name").forEach((target) => {
+ let toggleEl = dom.find(target, `.${TOGGLE_ID}`);
+ if (null == toggleEl) {
+ toggleEl = dom.create("div", "");
+ toggleEl.className = TOGGLE_ID;
+ dom.on(toggleEl, "click", () => applyTheme(true));
+ }
+ dom.before(target, toggleEl);
+ });
+ });
};
$docsify ??= {};
$docsify.plugins = [...($docsify.plugins ?? []), darkThemeTogglePlugin];
diff --git a/src/datetime.js b/src/datetime.js
index 3ac0d5f8c..a528c76e7 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -60,6 +60,7 @@ function unsupportedZone(zone) {
// we cache week data on the DT object and this intermediates the cache
/**
+ * @ignore
* @param {DateTime} dt
*/
function possiblyCachedWeekData(dt) {
@@ -70,6 +71,7 @@ function possiblyCachedWeekData(dt) {
}
/**
+ * @ignore
* @param {DateTime} dt
*/
function possiblyCachedLocalWeekData(dt) {
@@ -93,6 +95,7 @@ function clone(inst, alts) {
o: inst.o,
loc: inst.loc,
invalid: inst.invalid,
+ wasHole: inst.wasHole,
};
return new DateTime({ ...current, ...alts, old: current });
}
@@ -108,7 +111,7 @@ function fixOffset(localTS, o, tz) {
// If so, offset didn't change and we're done
if (o === o2) {
- return [utcGuess, o];
+ return [utcGuess, o, false];
}
// If not, change the ts by the difference in the offset
@@ -117,11 +120,11 @@ function fixOffset(localTS, o, tz) {
// If that gives us the local time we want, we're done
const o3 = tz.offset(utcGuess);
if (o2 === o3) {
- return [utcGuess, o2];
+ return [utcGuess, o2, false];
}
// If it's different, we're in a hole time. The offset has changed, but the we don't adjust the time
- return [localTS - Math.min(o2, o3) * 60 * 1000, Math.max(o2, o3)];
+ return [localTS - Math.min(o2, o3) * 60 * 1000, Math.max(o2, o3), true];
}
// convert an epoch timestamp into a calendar object with the given offset
@@ -173,7 +176,7 @@ function adjustTime(inst, dur) {
}).as("milliseconds"),
localTS = objToLocalTS(c);
- let [ts, o] = fixOffset(localTS, oPre, inst.zone);
+ let [ts, o, wasHole] = fixOffset(localTS, oPre, inst.zone);
if (millisToAdd !== 0) {
ts += millisToAdd;
@@ -181,7 +184,7 @@ function adjustTime(inst, dur) {
o = inst.zone.offset(ts);
}
- return { ts, o };
+ return { ts, o, wasHole };
}
// helper useful in turning the results of parsing into real dates
@@ -410,6 +413,7 @@ function normalizeUnitWithLocalWeeks(unit) {
/**
* @param {Zone} zone
* @return {number}
+ * @ignore
*/
function guessOffsetForZone(zone) {
if (zoneOffsetTs === undefined) {
@@ -441,7 +445,7 @@ function quickDT(obj, opts) {
const loc = Locale.fromObject(opts);
- let ts, o;
+ let ts, o, wasHole;
// assume we have the higher-order units
if (!isUndefined(obj.year)) {
@@ -457,12 +461,12 @@ function quickDT(obj, opts) {
}
const offsetProvis = guessOffsetForZone(zone);
- [ts, o] = objToTS(obj, offsetProvis, zone);
+ [ts, o, wasHole] = objToTS(obj, offsetProvis, zone);
} else {
ts = Settings.now();
}
- return new DateTime({ ts, zone, loc, o });
+ return new DateTime({ ts, zone, loc, o, wasHole });
}
function diffRelative(start, end, opts) {
@@ -510,6 +514,7 @@ function lastOpts(argList) {
/**
* Timestamp to use for cached zone offset guesses (exposed for test)
+ * @ignore
*/
let zoneOffsetTs;
/**
@@ -517,6 +522,8 @@ let zoneOffsetTs;
*
* This optimizes quickDT via guessOffsetForZone to avoid repeated calls of
* zone.offset().
+ *
+ * @ignore
*/
const zoneOffsetGuessCache = new Map();
@@ -535,7 +542,7 @@ const zoneOffsetGuessCache = new Map();
* {@link DateTime#day}, {@link DateTime#hour}, {@link DateTime#minute}, {@link DateTime#second}, {@link DateTime#millisecond} accessors.
* * **Week calendar**: For ISO week calendar attributes, see the {@link DateTime#weekYear}, {@link DateTime#weekNumber}, and {@link DateTime#weekday} accessors.
* * **Configuration** See the {@link DateTime#locale} and {@link DateTime#numberingSystem} accessors.
- * * **Transformation**: To transform the DateTime into other DateTimes, use {@link DateTime#set}, {@link DateTime#reconfigure}, {@link DateTime#setZone}, {@link DateTime#setLocale}, {@link DateTime.plus}, {@link DateTime#minus}, {@link DateTime#endOf}, {@link DateTime#startOf}, {@link DateTime#toUTC}, and {@link DateTime#toLocal}.
+ * * **Transformation**: To transform the DateTime into other DateTimes, use {@link DateTime#set}, {@link DateTime#reconfigure}, {@link DateTime#setZone}, {@link DateTime#setLocale}, {@link DateTime#plus}, {@link DateTime#minus}, {@link DateTime#endOf}, {@link DateTime#startOf}, {@link DateTime#toUTC}, and {@link DateTime#toLocal}.
* * **Output**: To convert the DateTime to other representations, use the {@link DateTime#toRelative}, {@link DateTime#toRelativeCalendar}, {@link DateTime#toJSON}, {@link DateTime#toISO}, {@link DateTime#toHTTP}, {@link DateTime#toObject}, {@link DateTime#toRFC2822}, {@link DateTime#toString}, {@link DateTime#toLocaleString}, {@link DateTime#toFormat}, {@link DateTime#toMillis} and {@link DateTime#toJSDate}.
*
* There's plenty others documented below. In addition, for more information on subtler topics like internationalization, time zones, alternative calendars, validity, and so on, see the external documentation.
@@ -556,13 +563,14 @@ export default class DateTime {
*/
this.ts = isUndefined(config.ts) ? Settings.now() : config.ts;
- let c = null,
- o = null;
+ let c = null;
+ let o = null;
if (!invalid) {
const unchanged = config.old && config.old.ts === this.ts && config.old.zone.equals(zone);
if (unchanged) {
- [c, o] = [config.old.c, config.old.o];
+ c = config.old.c;
+ o = config.old.o;
} else {
// If an offset has been passed and we have not been called from
// clone(), we can trust it and avoid the offset calculation.
@@ -598,9 +606,12 @@ export default class DateTime {
* @access private
*/
this.c = c;
+
/**
* @access private
*/
+ this._wasHole = config.wasHole || false;
+
this.o = o;
/**
* @access private
@@ -873,16 +884,18 @@ export default class DateTime {
// compute the actual time
const gregorian = useWeekData
- ? weekToGregorian(normalized, minDaysInFirstWeek, startOfWeek)
- : containsOrdinal
- ? ordinalToGregorian(normalized)
- : normalized,
- [tsFinal, offsetFinal] = objToTS(gregorian, offsetProvis, zoneToUse),
+ ? weekToGregorian(normalized, minDaysInFirstWeek, startOfWeek)
+ : containsOrdinal
+ ? ordinalToGregorian(normalized)
+ : normalized;
+
+ const [tsFinal, offsetFinal, wasHole] = objToTS(gregorian, offsetProvis, zoneToUse),
inst = new DateTime({
ts: tsFinal,
zone: zoneToUse,
o: offsetFinal,
loc,
+ wasHole,
});
// gregorian data + weekday serves only to validate
@@ -1168,6 +1181,19 @@ export default class DateTime {
return this.isValid ? this.zone.name : null;
}
+ /**
+ * Whether this DateTime was created from a "hole time" that can exist during DST due to the
+ * clocks moving forward.
+ *
+ * @example DateTime.local(2017, 3, 12, 2).wasHole; //=> true
+ * @example DateTime.local(2017, 3, 12, 4).wasHole; //=> false
+ *
+ * @return {boolean}
+ */
+ get wasHole() {
+ return this._wasHole;
+ }
+
/**
* Get the year
* @example DateTime.local(2017, 5, 25).year //=> 2017
@@ -1571,23 +1597,32 @@ export default class DateTime {
return DateTime.invalid(unsupportedZone(zone));
} else {
let newTS = this.ts;
+ let wasHole = false;
if (keepLocalTime || keepCalendarTime) {
const offsetGuess = zone.offset(this.ts);
const asObj = this.toObject();
- [newTS] = objToTS(asObj, offsetGuess, zone);
+ [newTS, , wasHole] = objToTS(asObj, offsetGuess, zone);
}
- return clone(this, { ts: newTS, zone });
+ return clone(this, { ts: newTS, zone, wasHole });
}
}
/**
- * "Set" the locale, numberingSystem, or outputCalendar. Returns a newly-constructed DateTime.
+ * "Set" the locale, numberingSystem, outputCalendar, or weekSettings. Returns a newly-constructed DateTime.
* @param {Object} properties - the properties to set
+ * @param {string} [properties.locale] - the locale to set
+ * @param {string} [properties.numberingSystem] - the numbering system to set
+ * @param {string} [properties.outputCalendar] - the output calendar to set
+ * @param {Object} [properties.weekSettings] - the week settings to set
+ * @param {number} [properties.weekSettings.firstDay] - the first day of the week (1-7, Monday-Sunday)
+ * @param {number} [properties.weekSettings.minimalDays] - the minimum number of days in the first week
+ * @param {number[]} [properties.weekSettings.weekend] - the weekend days
* @example DateTime.local(2017, 5, 25).reconfigure({ locale: 'en-GB' })
+ * @example DateTime.local(2017, 5, 25).reconfigure({ weekSettings: { firstDay: 1 } })
* @return {DateTime}
*/
- reconfigure({ locale, numberingSystem, outputCalendar } = {}) {
- const loc = this.loc.clone({ locale, numberingSystem, outputCalendar });
+ reconfigure({ locale, numberingSystem, outputCalendar, weekSettings } = {}) {
+ const loc = this.loc.clone({ locale, numberingSystem, outputCalendar, weekSettings });
return clone(this, { loc });
}
@@ -1659,8 +1694,8 @@ export default class DateTime {
}
}
- const [ts, o] = objToTS(mixed, this.o, this.zone);
- return clone(this, { ts, o });
+ const [ts, o, wasHole] = objToTS(mixed, this.o, this.zone);
+ return clone(this, { ts, o, wasHole });
}
/**
diff --git a/src/impl/conversions.js b/src/impl/conversions.js
index 4c7d1170e..417611b9a 100644
--- a/src/impl/conversions.js
+++ b/src/impl/conversions.js
@@ -112,6 +112,7 @@ export function ordinalToGregorian(ordinalData) {
* If so, validates that they are not mixed with ISO week units and then copies them to the normal week unit properties.
* Modifies obj in-place!
* @param obj the object values
+ * @ignore
*/
export function usesLocalWeekValues(obj, loc) {
const hasLocaleWeekData =
diff --git a/src/impl/regexParser.js b/src/impl/regexParser.js
index c39dd2082..807a42ea2 100644
--- a/src/impl/regexParser.js
+++ b/src/impl/regexParser.js
@@ -75,7 +75,7 @@ const isoTimeExtensionRegex = RegExp(`(?:[Tt]${isoTimeRegex.source})?`);
const isoYmdRegex = /([+-]\d{6}|\d{4})(?:-?(\d\d)(?:-?(\d\d))?)?/;
const isoWeekRegex = /(\d{4})-?W(\d\d)(?:-?(\d))?/;
const isoOrdinalRegex = /(\d{4})-?(\d{3})/;
-const extractISOWeekData = simpleParse("weekYear", "weekNumber", "weekDay");
+const extractISOWeekData = simpleParse("weekYear", "weekNumber", "weekday");
const extractISOOrdinalData = simpleParse("year", "ordinal");
const sqlYmdRegex = /(\d{4})-(\d\d)-(\d\d)/; // dumbed-down version of the ISO one
const sqlTimeRegex = RegExp(
@@ -305,7 +305,7 @@ const extractPartialIsoIntervalEndDate = simpleParse(
"month",
"day",
"weekNumber",
- "weekDay",
+ "weekday",
"ordinal"
);
diff --git a/src/impl/tokenParser.js b/src/impl/tokenParser.js
index 0190efc5f..7a2196c7e 100644
--- a/src/impl/tokenParser.js
+++ b/src/impl/tokenParser.js
@@ -52,6 +52,7 @@ function escapeToken(value) {
/**
* @param token
* @param {Locale} loc
+ * @ignore
*/
function unitForToken(token, loc) {
const one = digitRegex(loc),
diff --git a/src/impl/util.js b/src/impl/util.js
index 916425020..32a5d9d72 100644
--- a/src/impl/util.js
+++ b/src/impl/util.js
@@ -307,6 +307,7 @@ export function normalizeObject(obj, normalizer) {
* @param {string} format - What style of offset to return.
* Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
* @return {string}
+ * @ignore
*/
export function formatOffset(offset, format) {
const hours = Math.trunc(Math.abs(offset / 60)),
diff --git a/src/interval.js b/src/interval.js
index 1cdb6b118..83ab504ff 100644
--- a/src/interval.js
+++ b/src/interval.js
@@ -589,6 +589,9 @@ export default class Interval {
* 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.
+ *
+ * Requires support for Intl.DateTimeFormat.prototype.formatRange().
+ *
* @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.
diff --git a/test/datetime/dst.test.js b/test/datetime/dst.test.js
index 8ab5d77ab..16d5a7867 100644
--- a/test/datetime/dst.test.js
+++ b/test/datetime/dst.test.js
@@ -15,6 +15,7 @@ for (const [name, local] of Object.entries(dateTimeConstructors)) {
const d = local(2017, 3, 12, 2);
expect(d.hour).toBe(3);
expect(d.offset).toBe(-4 * 60);
+ expect(d.wasHole).toBe(true);
});
if (name == "fromObject") {
@@ -190,3 +191,59 @@ describe("DateTime.local() with offset caching", () => {
}
}
});
+
+describe("DateTime maintains the wasHole setting properly", () => {
+ test("is false by default", () => {
+ expect(DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 4 }).wasHole).toBe(false);
+ });
+
+ test("is set on hole times", () => {
+ expect(DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 2 }).wasHole).toBe(true);
+ });
+
+ test("is set on hole times with DateTime.local", () => {
+ expect(DateTime.local(2017, 3, 12, 2).wasHole).toBe(true);
+ });
+
+ test("is set on hole times with DateTime.fromISO", () => {
+ expect(DateTime.fromISO("2017-03-12T02:00:00").wasHole).toBe(true);
+ });
+
+ test("is false when setting to non-hole", () => {
+ const fromHole = DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 2 });
+ expect(fromHole.set({ hour: 4 }).wasHole).toBe(false);
+ });
+
+ test("is true when setting hole-time on time from hole", () => {
+ const dt = DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 2 });
+ expect(dt.wasHole).toBe(true);
+ expect(dt.set({ hour: 2 }).wasHole).toBe(true);
+ });
+
+ test("is true when setting hole-time on time from non-hole", () => {
+ const dt = DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 4 });
+ expect(dt.wasHole).toBe(false);
+ expect(dt.set({ hour: 2 }).wasHole).toBe(true);
+ });
+
+ test("is dropped on math", () => {
+ expect(
+ DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 2 }).plus({ hours: 2 }).wasHole
+ ).toBe(false);
+ });
+
+ test("is kept on reconfigure", () => {
+ expect(
+ DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 2 }).reconfigure({
+ locale: "es-ES",
+ }).wasHole
+ ).toBe(true);
+ });
+
+ test("is dropped on rezoning", () => {
+ expect(
+ DateTime.fromObject({ year: 2017, month: 3, day: 12, hour: 2 }).setZone("Europe/London")
+ .wasHole
+ ).toBe(false);
+ });
+});
diff --git a/test/datetime/reconfigure.test.js b/test/datetime/reconfigure.test.js
index 3801419c7..50fa77bf7 100644
--- a/test/datetime/reconfigure.test.js
+++ b/test/datetime/reconfigure.test.js
@@ -41,3 +41,21 @@ test("DateTime#reconfigure() with no arguments no opts", () => {
expect(recon.numberingSystem).toBe("beng");
expect(recon.outputCalendar).toBe("coptic");
});
+
+test("DateTime#reconfigure() sets the weekSettings", () => {
+ const original = DateTime.local(2022, 1, 4, { locale: "en-US" });
+ const recon = original.reconfigure({
+ weekSettings: { firstDay: 6, minimalDays: 1, weekend: [1, 2] },
+ });
+ expect(recon.startOf("week", { useLocaleWeeks: true }).weekday).toBe(6);
+});
+
+test("DateTime#reconfigure() preserves weekSettings when setting other options", () => {
+ const original = DateTime.local(2022, 1, 4, {
+ locale: "en-US",
+ weekSettings: { firstDay: 3, minimalDays: 1, weekend: [] },
+ });
+ const recon = original.reconfigure({ locale: "de-DE" });
+ expect(recon.locale).toBe("de-DE");
+ expect(recon.startOf("week", { useLocaleWeeks: true }).weekday).toBe(3);
+});