Repository: moment/luxon
Pull request #683 (2020-04-02 06:39:00)

Duration from ISO leading minus
This PR fixes [moment/luxon#681](https://github.com/moment/luxon/issues/681).

The issue above links to a similar issue with moment ([moment/moment#2408](https://github.com/moment/moment/issues/2408)) which was resolved.

Although the [ISO 8601 duration format](https://www.digi.com/resources/documentation/digidocs/90001437-13/reference/r_iso_8601_duration_format.htm) does not mention support for a leading minus, there are many other libraries that support ISO duration parsing in this way.

The assumed rule is that a leading minus sign inverts the sign of all values.

Examples of leading-minus ISO strings and their equivalent non-leading-minus ISO strings:

 -P1D == P-1D
  -P5Y3M == P-5Y-3M
  -P-5Y-3M == P5Y3M
  -P-1W1DT13H-23M34S == P1W-1DT-13H23M-34S


jsf-clabot: [![CLA assistant check](https://cla.js.foundation/pull/badge/not_signed)](https://cla.js.foundation/moment/luxon?pullRequest=683) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla.js.foundation/moment/luxon?pullRequest=683) before we can accept your contribution.<br/>
icambron: Looks good, thanks! I'll get this released very soon.

Patch:
diff --git a/src/impl/regexParser.js b/src/impl/regexParser.js
index bdb9762a..1ceca9f9 100644
--- a/src/impl/regexParser.js
+++ b/src/impl/regexParser.js
@@ -120,11 +120,11 @@ function extractIANAZone(match, cursor) {
 
 // ISO duration parsing
 
-const isoDuration = /^P(?:(?:(-?\d{1,9})Y)?(?:(-?\d{1,9})M)?(?:(-?\d{1,9})W)?(?:(-?\d{1,9})D)?(?:T(?:(-?\d{1,9})H)?(?:(-?\d{1,9})M)?(?:(-?\d{1,9})(?:[.,](-?\d{1,9}))?S)?)?)$/;
+const isoDuration = /^-?P(?:(?:(-?\d{1,9})Y)?(?:(-?\d{1,9})M)?(?:(-?\d{1,9})W)?(?:(-?\d{1,9})D)?(?:T(?:(-?\d{1,9})H)?(?:(-?\d{1,9})M)?(?:(-?\d{1,9})(?:[.,](-?\d{1,9}))?S)?)?)$/;
 
 function extractISODuration(match) {
   const [
-    ,
+    s,
     yearStr,
     monthStr,
     weekStr,
@@ -135,16 +135,20 @@ function extractISODuration(match) {
     millisecondsStr
   ] = match;
 
+  const hasNegativePrefix = s[0] === "-";
+
+  const maybeNegate = num => (num && hasNegativePrefix ? -num : num);
+
   return [
     {
-      years: parseInteger(yearStr),
-      months: parseInteger(monthStr),
-      weeks: parseInteger(weekStr),
-      days: parseInteger(dayStr),
-      hours: parseInteger(hourStr),
-      minutes: parseInteger(minuteStr),
-      seconds: parseInteger(secondStr),
-      milliseconds: parseMillis(millisecondsStr)
+      years: maybeNegate(parseInteger(yearStr)),
+      months: maybeNegate(parseInteger(monthStr)),
+      weeks: maybeNegate(parseInteger(weekStr)),
+      days: maybeNegate(parseInteger(dayStr)),
+      hours: maybeNegate(parseInteger(hourStr)),
+      minutes: maybeNegate(parseInteger(minuteStr)),
+      seconds: maybeNegate(parseInteger(secondStr)),
+      milliseconds: maybeNegate(parseMillis(millisecondsStr))
     }
   ];
 }


Test patch:
diff --git a/test/duration/parse.test.js b/test/duration/parse.test.js
index 405eb61b..b7827b4e 100644
--- a/test/duration/parse.test.js
+++ b/test/duration/parse.test.js
@@ -26,6 +26,10 @@ test("Duration.fromISO can parse mixed or negative durations", () => {
   check("P1YT-34000S", { years: 1, seconds: -34000 });
   check("P-1W1DT13H23M34S", { weeks: -1, days: 1, hours: 13, minutes: 23, seconds: 34 });
   check("P-2W", { weeks: -2 });
+  check("-P1D", { days: -1 });
+  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 });
 });
 
 test("Duration.fromISO can parse fractions of seconds", () => {