From cb02c9b01abdcfe318a7eaa5ce56ed6f296ea048 Mon Sep 17 00:00:00 2001 From: simonkundrik <117163790+simonkundrik@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:07:49 +0200 Subject: [PATCH 1/3] fix(docs): correct swapped standalone/format token columns in parsing table (#1789) The parsing token table listed the format tokens (E, M) under the Standalone token column and the standalone tokens (c, L) under the Format token column -- the reverse of docs/formatting.md and of Luxon's API, where c/L are standalone and E/M are format. Swap the two columns for all affected weekday and month rows so the parsing table is correct and consistent with the formatting docs. Closes #1777 --- docs/parsing.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/parsing.md b/docs/parsing.md index f3b98493e..e3fad2872 100644 --- a/docs/parsing.md +++ b/docs/parsing.md @@ -205,13 +205,13 @@ Because Luxon was able to parse the string without difficulty, the output is a l | a | | meridiem | `AM` | | d | | day of the month, no padding | `6` | | dd | | day of the month, padded to 2 | `06` | -| E | c | day of the week, as number from 1-7 (Monday is 1, Sunday is 7) | `3` | -| EEE | ccc | day of the week, as an abbreviate localized string | `Wed` | -| EEEE | cccc | day of the week, as an unabbreviated localized string | `Wednesday` | -| M | L | month as an unpadded number | `8` | -| MM | LL | month as an padded number | `08` | -| MMM | LLL | month as an abbreviated localized string | `Aug` | -| MMMM | LLLL | month as an unabbreviated localized string | `August` | +| c | E | day of the week, as number from 1-7 (Monday is 1, Sunday is 7) | `3` | +| ccc | EEE | day of the week, as an abbreviate localized string | `Wed` | +| cccc | EEEE | day of the week, as an unabbreviated localized string | `Wednesday` | +| L | M | month as an unpadded number | `8` | +| LL | MM | month as an padded number | `08` | +| LLL | MMM | month as an abbreviated localized string | `Aug` | +| LLLL | MMMM | month as an unabbreviated localized string | `August` | | y | | year, 1-6 digits, very literally | `2014` | | yy | | two-digit year, interpreted as > 1960 by default (also accepts 4) | `14` | | yyyy | | four-digit year | `2014` | From 9ae1ab928bab18dae0b1b788ab6aa870805860dc Mon Sep 17 00:00:00 2001 From: Alexander Kireyev Date: Thu, 6 Aug 2026 02:27:46 +0700 Subject: [PATCH 2/3] fix: mark FixedOffsetZone invalid when constructed with a non-numeric offset (#1781) FixedOffsetZone#isValid always returned true, even when the constructor was given an input it does not support (e.g. new FixedOffsetZone('CDT')). Such a zone produced a NaN offset and a 'UTC-NaN' name, yet reported itself as valid, so DateTimes using it failed downstream with a confusing 'invalid input' reason instead of being cleanly invalid. isValid now reflects whether the offset is a number, so unsupported inputs produce an invalid zone (and, in turn, an 'unsupported zone' DateTime), matching the behaviour of IANAZone. Closes #1068 --- src/zones/fixedOffsetZone.js | 6 ++++-- test/zones/fixedOffset.test.js | 26 +++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/zones/fixedOffsetZone.js b/src/zones/fixedOffsetZone.js index b1ab4ab1d..bc46e24aa 100644 --- a/src/zones/fixedOffsetZone.js +++ b/src/zones/fixedOffsetZone.js @@ -50,6 +50,8 @@ export default class FixedOffsetZone extends Zone { super(); /** @private **/ this.fixed = offset; + /** @private **/ + this.valid = Number.isInteger(offset); } /** @@ -140,11 +142,11 @@ export default class FixedOffsetZone extends Zone { /** * Return whether this Zone is valid: - * All fixed offset zones are valid. + * All fixed offset zones are valid, provided they were constructed with a numeric offset. * @override * @type {boolean} */ get isValid() { - return true; + return this.valid; } } diff --git a/test/zones/fixedOffset.test.js b/test/zones/fixedOffset.test.js index 606b38406..b33260d71 100644 --- a/test/zones/fixedOffset.test.js +++ b/test/zones/fixedOffset.test.js @@ -1,5 +1,5 @@ /* global test expect */ -import { FixedOffsetZone, IANAZone } from "../../src/luxon"; +import { DateTime, FixedOffsetZone, IANAZone } from "../../src/luxon"; test("FixedOffsetZone.utcInstance returns a singleton", () => { expect(FixedOffsetZone.utcInstance).toBe(FixedOffsetZone.utcInstance); @@ -63,6 +63,30 @@ test("FixedOffsetZone.formatOffset prints the correct sign before the offset", ( expect(FixedOffsetZone.instance(300).formatOffset(0, "short")).toBe("+05:00"); }); +test("FixedOffsetZone is valid when constructed with a numeric offset", () => { + expect(new FixedOffsetZone(0).isValid).toBe(true); + expect(new FixedOffsetZone(120).isValid).toBe(true); + expect(new FixedOffsetZone(-300).isValid).toBe(true); + expect(FixedOffsetZone.instance(60).isValid).toBe(true); +}); + +test("FixedOffsetZone is invalid when constructed with a non-numeric offset", () => { + // see https://github.com/moment/luxon/issues/1068 + expect(new FixedOffsetZone("CDT").isValid).toBe(false); + expect(new FixedOffsetZone("5").isValid).toBe(false); + expect(new FixedOffsetZone("abc").isValid).toBe(false); + expect(new FixedOffsetZone(NaN).isValid).toBe(false); + expect(new FixedOffsetZone(undefined).isValid).toBe(false); + expect(new FixedOffsetZone(null).isValid).toBe(false); + expect(new FixedOffsetZone({}).isValid).toBe(false); +}); + +test("A DateTime in an invalid FixedOffsetZone is itself invalid", () => { + const dt = DateTime.fromMillis(0, { zone: new FixedOffsetZone("CDT") }); + expect(dt.isValid).toBe(false); + expect(dt.invalidReason).toBe("unsupported zone"); +}); + test("FixedOffsetZone.equals requires both zones to be fixed", () => { expect(FixedOffsetZone.utcInstance.equals(IANAZone.create("UTC"))).toBe(false); }); From d249e0eda7180720e741cab1feaebd9234dc196b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20L=C3=AA?= <115204145+DucMinhNe@users.noreply.github.com> Date: Thu, 6 Aug 2026 02:33:19 +0700 Subject: [PATCH 3/3] Accept the obsolete UT zone in fromRFC2822 (#1782) The rfc2822 regex already matches UT in its zone alternation (UT|GMT|[ECMP][SD]T), but UT was missing from the obsOffsets lookup table, so obsOffsets["UT"] was undefined and the parse failed with an "unsupported zone" error. RFC 2822 / 5322 section 4.3 lists UT alongside GMT as an obsolete zone meaning +0000, so it should parse the same as GMT. Add UT: 0 to the table. --- src/impl/regexParser.js | 1 + test/datetime/regexParse.test.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/impl/regexParser.js b/src/impl/regexParser.js index 807a42ea2..fcd6d45ff 100644 --- a/src/impl/regexParser.js +++ b/src/impl/regexParser.js @@ -159,6 +159,7 @@ function extractISODuration(match) { // I'm just going to ignore that const obsOffsets = { GMT: 0, + UT: 0, EDT: -4 * 60, EST: -5 * 60, CDT: -5 * 60, diff --git a/test/datetime/regexParse.test.js b/test/datetime/regexParse.test.js index 85d12002e..f4c54154f 100644 --- a/test/datetime/regexParse.test.js +++ b/test/datetime/regexParse.test.js @@ -831,6 +831,20 @@ test("DateTime.fromRFC2822() can use a weird subset of offset abbreviations", () }); }); +test("DateTime.fromRFC2822() can use the obsolete UT zone", () => { + const dt = DateTime.fromRFC2822("01 Nov 2016 13:23:12 UT"); + expect(dt.isValid).toBe(true); + expect(dt.toUTC().toObject()).toEqual({ + year: 2016, + month: 11, + day: 1, + hour: 13, + minute: 23, + second: 12, + millisecond: 0, + }); +}); + //------ // .fromHTTP //-------