Repository: moment/luxon
Pull request #930 (2021-05-06 17:47:59)

Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/lodash/lodash/commit/f299b52f39486275a9e6483b60a410e06520c538"><code>f299b52</code></a> Bump to v4.17.21</li>
<li><a href="https://github.com/lodash/lodash/commit/c4847ebe7d14540bb28a8b932a9ce1b9ecbfee1a"><code>c4847eb</code></a> Improve performance of <code>toNumber</code>, <code>trim</code> and <code>trimEnd</code> on large input strings</li>
<li><a href="https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c"><code>3469357</code></a> Prevent command injection through <code>_.template</code>'s <code>variable</code> option</li>
<li><a href="https://github.com/lodash/lodash/commit/ded9bc66583ed0b4e3b7dc906206d40757b4a90a"><code>ded9bc6</code></a> Bump to v4.17.20.</li>
<li><a href="https://github.com/lodash/lodash/commit/63150ef7645ac07961b63a86490f419f356429aa"><code>63150ef</code></a> Documentation fixes.</li>
<li><a href="https://github.com/lodash/lodash/commit/00f0f62a979d2f5fa0287c06eae70cf9a62d8794"><code>00f0f62</code></a> test.js: Remove trailing comma.</li>
<li><a href="https://github.com/lodash/lodash/commit/846e434c7a5b5692c55ebf5715ed677b70a32389"><code>846e434</code></a> Temporarily use a custom fork of <code>lodash-cli</code>.</li>
<li><a href="https://github.com/lodash/lodash/commit/5d046f39cbd27f573914768e3b36eeefcc4f1229"><code>5d046f3</code></a> Re-enable Travis tests on <code>4.17</code> branch.</li>
<li><a href="https://github.com/lodash/lodash/commit/aa816b36d402a1ad9385142ce7188f17dae514fd"><code>aa816b3</code></a> Remove <code>/npm-package</code>.</li>
<li>See full diff in <a href="https://github.com/lodash/lodash/compare/4.17.19...4.17.21">compare view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a href="https://www.npmjs.com/~bnjmnt4n">bnjmnt4n</a>, a new releaser for lodash since your current version.</p>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=lodash&package-manager=npm_and_yarn&previous-version=4.17.19&new-version=4.17.21)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/moment/luxon/network/alerts).

</details>


Patch:
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/package-lock.json b/package-lock.json
index 55c9bbea..b8fbe2e4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8027,9 +8027,9 @@
       }
     },
     "lodash": {
-      "version": "4.17.19",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
-      "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
       "dev": true
     },
     "lodash.assignin": {
diff --git a/src/datetime.js b/src/datetime.js
index aa563c10..3a32f3a8 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(
         {
@@ -751,8 +751,8 @@ export default class DateTime {
    * @param {string|Zone} [opts.zone='local'] - use this zone if no offset is specified in the input string itself. Will also convert the time to this zone
    * @param {boolean} [opts.setZone=false] - override the zone with a fixed-offset zone specified in the string itself, if it specifies one
    * @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance
-   * @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance
-   * @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance
+   * @param {string} [opts.outputCalendar] - the output calendar to set on the resulting DateTime instance
+   * @param {string} [opts.numberingSystem] - the numbering system to set on the resulting DateTime instance
    * @example DateTime.fromISO('2016-05-25T09:08:34.123')
    * @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00')
    * @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00', {setZone: true})
@@ -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/duration.js b/src/duration.js
index b2a6679c..14c4e692 100644
--- a/src/duration.js
+++ b/src/duration.js
@@ -593,9 +593,9 @@ export default class Duration {
   /**
    * Get the value of unit.
    * @param {string} unit - a unit such as 'minute' or 'day'
-   * @example Duration.fromObject({years: 2, days: 3}).years //=> 2
-   * @example Duration.fromObject({years: 2, days: 3}).months //=> 0
-   * @example Duration.fromObject({years: 2, days: 3}).days //=> 3
+   * @example Duration.fromObject({years: 2, days: 3}).get('years') //=> 2
+   * @example Duration.fromObject({years: 2, days: 3}).get('months') //=> 0
+   * @example Duration.fromObject({years: 2, days: 3}).get('days') //=> 3
    * @return {number}
    */
   get(unit) {
diff --git a/src/impl/locale.js b/src/impl/locale.js
index fe7a146d..44a92660 100644
--- a/src/impl/locale.js
+++ b/src/impl/locale.js
@@ -3,6 +3,7 @@ import * as English from "./english.js";
 import Settings from "../settings.js";
 import DateTime from "../datetime.js";
 import Formatter from "./formatter.js";
+import IANAZone from "../zones/IANAZone.js";
 
 let intlDTCache = {};
 function getCachedDTF(locString, opts = {}) {
@@ -185,11 +186,15 @@ class PolyDateFormatter {
     if (dt.zone.universal && this.hasIntl) {
       // UTC-8 or Etc/UTC-8 are not part of tzdata, only Etc/GMT+8 and the like.
       // That is why fixed-offset TZ is set to that unless it is:
-      // 1. Outside of the supported range Etc/GMT-14 to Etc/GMT+12.
-      // 2. Not a whole hour, e.g. UTC+4:30.
+      // 1. Representing offset 0 when UTC is used to maintain previous behavior and does not become GMT.
+      // 2. Unsupported by the browser:
+      //    - some do not support Etc/
+      //    - < Etc/GMT-14, > Etc/GMT+12, and 30-minute or 45-minute offsets are not part of tzdata
       const gmtOffset = -1 * (dt.offset / 60);
-      if (gmtOffset >= -14 && gmtOffset <= 12 && gmtOffset % 1 === 0) {
-        z = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
+      const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
+      const isOffsetZoneSupported = IANAZone.isValidZone(offsetZ);
+      if (dt.offset !== 0 && isOffsetZoneSupported) {
+        z = offsetZ;
         this.dt = dt;
       } else {
         // Not all fixed-offset zones like Etc/+4:30 are present in tzdata.
diff --git a/src/impl/regexParser.js b/src/impl/regexParser.js
index 8a000430..8e4308b6 100644
--- a/src/impl/regexParser.js
+++ b/src/impl/regexParser.js
@@ -140,8 +140,10 @@ function extractISODuration(match) {
   ] = match;
 
   const hasNegativePrefix = s[0] === "-";
+  const negativeSeconds = secondStr && secondStr[0] === "-";
 
-  const maybeNegate = num => (num && hasNegativePrefix ? -num : num);
+  const maybeNegate = (num, force = false) =>
+    num !== undefined && (force || (num && hasNegativePrefix)) ? -num : num;
 
   return [
     {
@@ -151,8 +153,8 @@ function extractISODuration(match) {
       days: maybeNegate(parseInteger(dayStr)),
       hours: maybeNegate(parseInteger(hourStr)),
       minutes: maybeNegate(parseInteger(minuteStr)),
-      seconds: maybeNegate(parseInteger(secondStr)),
-      milliseconds: maybeNegate(parseMillis(millisecondsStr))
+      seconds: maybeNegate(parseInteger(secondStr), secondStr === "-0"),
+      milliseconds: maybeNegate(parseMillis(millisecondsStr), negativeSeconds)
     }
   ];
 }
diff --git a/src/zones/IANAZone.js b/src/zones/IANAZone.js
index e2ad296e..777958df 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]);
       }
@@ -152,8 +152,11 @@ export default class IANAZone extends Zone {
 
   /** @override **/
   offset(ts) {
-    const date = new Date(ts),
-      dtf = makeDTF(this.name),
+    const date = new Date(ts);
+
+    if (isNaN(date)) return NaN;
+
+    const dtf = makeDTF(this.name),
       [year, month, day, hour, minute, second] = dtf.formatToParts
         ? partsOffset(dtf, date)
         : hackyOffset(dtf, date),


Test patch:
diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js
index eb04f9d3..b3383d78 100644
--- a/test/datetime/format.test.js
+++ b/test/datetime/format.test.js
@@ -333,6 +333,12 @@ test("DateTime#toLocaleString() shows things in the right fixed-offset zone when
   );
 });
 
+test("DateTime#toLocaleString() shows things with UTC if fixed-offset zone with 0 offset is used", () => {
+  expect(dt.setZone("UTC").toLocaleString(DateTime.DATETIME_FULL)).toBe(
+    "May 25, 1982, 9:23 AM UTC"
+  );
+});
+
 test("DateTime#toLocaleString() does the best it can with unsupported fixed-offset zone when showing the zone", () => {
   expect(dt.setZone("UTC+4:30").toLocaleString(DateTime.DATETIME_FULL)).toBe(
     "May 25, 1982, 9:23 AM UTC"
diff --git a/test/datetime/math.test.js b/test/datetime/math.test.js
index 8c84bfc0..e1b5475a 100644
--- a/test/datetime/math.test.js
+++ b/test/datetime/math.test.js
@@ -116,6 +116,13 @@ test("DateTime#plus renders invalid when out of max. datetime range using second
   expect(d.isValid).toBe(false);
 });
 
+test("DateTime#plus renders invalid when out of max. datetime range using IANAZone", () => {
+  const d = DateTime.utc(1970, 1, 1, 0, 0, 0, 0)
+    .setZone("America/Los_Angeles")
+    .plus({ second: 1e8 * 24 * 60 * 60 + 1 });
+  expect(d.isValid).toBe(false);
+});
+
 test("DateTime#plus handles fractional days", () => {
   const d = DateTime.fromISO("2016-01-31T10:00");
   expect(d.plus({ days: 0.8 })).toEqual(d.plus({ hours: (24 * 4) / 5 }));
@@ -205,6 +212,13 @@ test("DateTime#minus renders invalid when out of max. datetime range using secon
   expect(d.isValid).toBe(false);
 });
 
+test("DateTime#plus renders invalid when out of max. datetime range using IANAZone", () => {
+  const d = DateTime.utc(1970, 1, 1, 0, 0, 0, 0)
+    .setZone("America/Los_Angeles")
+    .minus({ second: 1e8 * 24 * 60 * 60 + 1 });
+  expect(d.isValid).toBe(false);
+});
+
 test("DateTime#minus handles fractional days", () => {
   const d = DateTime.fromISO("2016-01-31T10:00");
   expect(d.minus({ days: 0.8 })).toEqual(d.minus({ hours: (24 * 4) / 5 }));
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/duration/parse.test.js b/test/duration/parse.test.js
index cbabbf2d..8704b958 100644
--- a/test/duration/parse.test.js
+++ b/test/duration/parse.test.js
@@ -31,6 +31,10 @@ test("Duration.fromISO can parse mixed or negative durations", () => {
   check("-P5Y3M", { years: -5, months: -3 });
   check("-P-5Y-3M", { years: 5, months: 3 });
   check("-P-1W1DT13H-23M34S", { weeks: 1, days: -1, hours: -13, minutes: 23, seconds: -34 });
+  check("PT-1.5S", { seconds: -1, milliseconds: -500 });
+  check("PT-0.5S", { seconds: 0, milliseconds: -500 });
+  check("PT1.5S", { seconds: 1, milliseconds: 500 });
+  check("PT0.5S", { seconds: 0, milliseconds: 500 });
 });
 
 test("Duration.fromISO can parse fractions of seconds", () => {
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", () => {