Repository: moment/luxon
Pull request #897 (2021-03-13 10:46:57)

856: fixed Etc/GMT0 yields Invalid DateTime
Fixed [Issue 856](https://github.com/moment/luxon/issues/856)


Patch:
diff --git a/docs/calendars.md b/docs/calendars.md
index 2118bfff..c1bfef4d 100644
--- a/docs/calendars.md
+++ b/docs/calendars.md
@@ -57,5 +57,5 @@ DateTime.fromObject({ outputCalendar: c }).toLocaleString(DateTime.DATE_FULL);
 You can set the default output calendar for new DateTime instances like this:
 
 ```js
-Settings.defaultOuputCalendar = 'persian';
+Settings.defaultOutputCalendar = 'persian';
 ```
diff --git a/docs/math.md b/docs/math.md
index f2ea27a4..cbaa3f8f 100644
--- a/docs/math.md
+++ b/docs/math.md
@@ -263,7 +263,7 @@ var diffInMonths = end.diff(start, 'months');
 diffInMonths.as('days'); //=> 30
 ```
 
-That's our conversion between months and days (you could also do a longterm-accurate conversion; it wouldn't fix the issue ahead). But this isn't the number of days between February 15 and March 15!
+That's our conversion between months and days (you could also do a longterm-accurate conversion; it wouldn't fix the issue ahead). But this isn't the number of days between February 13 and March 13!
 
 ```js
 var diffInDays = end.diff(start, 'days');
diff --git a/src/datetime.js b/src/datetime.js
index aa563c10..b165cc6c 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -479,7 +479,7 @@ export default class DateTime {
    */
   static local(year, month, day, hour, minute, second, millisecond) {
     if (isUndefined(year)) {
-      return new DateTime({});
+      return DateTime.now();
     } else {
       return quickDT(
         {
@@ -1339,7 +1339,22 @@ export default class DateTime {
       settingWeekStuff =
         !isUndefined(normalized.weekYear) ||
         !isUndefined(normalized.weekNumber) ||
-        !isUndefined(normalized.weekday);
+        !isUndefined(normalized.weekday),
+      containsOrdinal = !isUndefined(normalized.ordinal),
+      containsGregorYear = !isUndefined(normalized.year),
+      containsGregorMD = !isUndefined(normalized.month) || !isUndefined(normalized.day),
+      containsGregor = containsGregorYear || containsGregorMD,
+      definiteWeekDef = normalized.weekYear || normalized.weekNumber;
+
+    if ((containsGregor || containsOrdinal) && definiteWeekDef) {
+      throw new ConflictingSpecificationError(
+        "Can't mix weekYear/weekNumber units with year/month/day or ordinals"
+      );
+    }
+
+    if (containsGregorMD && containsOrdinal) {
+      throw new ConflictingSpecificationError("Can't mix ordinal dates with month/day");
+    }
 
     let mixed;
     if (settingWeekStuff) {
diff --git a/src/interval.js b/src/interval.js
index e8a419bc..71273fd1 100644
--- a/src/interval.js
+++ b/src/interval.js
@@ -429,7 +429,7 @@ export default class Interval {
     const s = this.s > other.s ? this.s : other.s,
       e = this.e < other.e ? this.e : other.e;
 
-    if (s > e) {
+    if (s >= e) {
       return null;
     } else {
       return Interval.fromDateTimes(s, e);
diff --git a/src/zones/IANAZone.js b/src/zones/IANAZone.js
index e2ad296e..0e319d0f 100644
--- a/src/zones/IANAZone.js
+++ b/src/zones/IANAZone.js
@@ -109,7 +109,7 @@ export default class IANAZone extends Zone {
   /** @ignore */
   static parseGMTOffset(specifier) {
     if (specifier) {
-      const match = specifier.match(/^Etc\/GMT([+-]\d{1,2})$/i);
+      const match = specifier.match(/^Etc\/GMT(0|[+-]\d{1,2})$/i);
       if (match) {
         return -60 * parseInt(match[1]);
       }


Test patch:
diff --git a/test/datetime/set.test.js b/test/datetime/set.test.js
index aecd7807..9da2c52c 100644
--- a/test/datetime/set.test.js
+++ b/test/datetime/set.test.js
@@ -123,6 +123,12 @@ test("DateTime#set throws for metadata", () => {
   expect(() => dt.set({ invalid: true })).toThrow();
 });
 
+test("DateTime#set throws for mixing incompatible units", () => {
+  expect(() => dt.set({ year: 2020, weekNumber: 22 })).toThrow();
+  expect(() => dt.set({ ordinal: 200, weekNumber: 22 })).toThrow();
+  expect(() => dt.set({ ordinal: 200, month: 8 })).toThrow();
+});
+
 test("DateTime#set maintains invalidity", () => {
   expect(DateTime.invalid("because").set({ ordinal: 200 }).isValid).toBe(false);
 });
diff --git a/test/interval/many.test.js b/test/interval/many.test.js
index 002bd97b..69d3aec4 100644
--- a/test/interval/many.test.js
+++ b/test/interval/many.test.js
@@ -111,12 +111,8 @@ test("Interval#intersection returns the intersection for overlapping intervals",
   ).toBeTruthy();
 });
 
-test("Interval#intersection returns empty for adjacent intervals", () => {
-  expect(
-    todayFrom(5, 8)
-      .intersection(todayFrom(8, 10))
-      .isEmpty()
-  ).toBeTruthy();
+test("Interval#intersection returns null for adjacent intervals", () => {
+  expect(todayFrom(5, 8).intersection(todayFrom(8, 10))).toBeNull();
 });
 
 test("Interval#intersection returns invalid for invalid intervals", () => {
diff --git a/test/zones/IANA.test.js b/test/zones/IANA.test.js
index f5f4a671..15e101b4 100644
--- a/test/zones/IANA.test.js
+++ b/test/zones/IANA.test.js
@@ -33,6 +33,9 @@ test("IANAZone.isValidZone", () => {
 
 test("IANAZone.parseGMTOffset returns a number for a valid input", () => {
   expect(IANAZone.parseGMTOffset("Etc/GMT+8")).toBe(-480);
+  expect(IANAZone.parseGMTOffset("Etc/GMT+0")).toBe(-0);
+  expect(IANAZone.parseGMTOffset("Etc/GMT-0")).toBe(+0);
+  expect(IANAZone.parseGMTOffset("Etc/GMT0")).toBe(-0);
 });
 
 test("IANAZone.parseGMTOffset returns null for invalid input", () => {