Repository: moment/luxon
Pull request #1464 (2023-07-09 22:57:11)

fix: Info.months for Islamic calendar
`Info.months()` for Islamic has duplicate month "**Rabiʻ I**" and also Islamic months' order is incorrect.

Fixed by changing the year from 2016 to 2009 which is the most recent year that Islamic and Gregorian months are sync.

[Islamic months and their order](https://en.wikipedia.org/wiki/Islamic_calendar#Months)

linux-foundation-easycla[bot]:  <a href="https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/19772872/47118129/1464/#/?version=2"><img src="https://s3.amazonaws.com/cla-project-logo-prod/cla-not-signed.svg" alt="CLA Not Signed" align="left" height="28" width="328"></a><br/><br /><ul><li><a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/19772872/47118129/1464/#/?version=2' target='_blank'>:x:</a> - login: @adotnusiyan . The commit (59ad1bbebc0368a6f0e65c25ac449e3a81274a91) is not authorized under a signed CLA. <a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/19772872/47118129/1464/#/?version=2' target='_blank'>Please click here to be authorized</a>. For further assistance with EasyCLA, <a href='https://jira.linuxfoundation.org/servicedesk/customer/portal/4' target='_blank'>please submit a support request ticket</a>.</li></ul>

Patch:
diff --git a/src/datetime.js b/src/datetime.js
index 90279094..40c1e6ae 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -894,7 +894,7 @@ export default class DateTime {
 
   /**
    * Create an invalid DateTime.
-   * @param {DateTime} reason - simple string of why this DateTime is invalid. Should not contain parameters or anything else data-dependent
+   * @param {string} reason - simple string of why this DateTime is invalid. Should not contain parameters or anything else data-dependent.
    * @param {string} [explanation=null] - longer explanation, may include parameters and other useful debugging information
    * @return {DateTime}
    */
diff --git a/src/duration.js b/src/duration.js
index 72f67fb4..49033a47 100644
--- a/src/duration.js
+++ b/src/duration.js
@@ -126,20 +126,19 @@ function clone(dur, alts, clear = false) {
   return new Duration(conf);
 }
 
-function antiTrunc(n) {
-  return n < 0 ? Math.floor(n) : Math.ceil(n);
+// this is needed since in some test cases it would return 0.9999999999999999 instead of 1
+function removePrecisionIssue(a) {
+  return Math.trunc(a * 1e3) / 1e3;
 }
 
 // NB: mutates parameters
 function convert(matrix, fromMap, fromUnit, toMap, toUnit) {
   const conv = matrix[toUnit][fromUnit],
     raw = fromMap[fromUnit] / conv,
-    sameSign = Math.sign(raw) === Math.sign(toMap[toUnit]),
-    // ok, so this is wild, but see the matrix in the tests
-    added =
-      !sameSign && toMap[toUnit] !== 0 && Math.abs(raw) <= 1 ? antiTrunc(raw) : Math.trunc(raw);
-  toMap[toUnit] += added;
-  fromMap[fromUnit] -= added * conv;
+    added = Math.floor(raw);
+
+  toMap[toUnit] = removePrecisionIssue(toMap[toUnit] + added);
+  fromMap[fromUnit] = removePrecisionIssue(fromMap[fromUnit] - added * conv);
 }
 
 // NB: mutates parameters
@@ -577,7 +576,13 @@ export default class Duration {
    * @return {number}
    */
   toMillis() {
-    return this.as("milliseconds");
+    let sum = this.values.milliseconds ?? 0;
+    for (let unit of reverseUnits.slice(1)) {
+      if (this.values?.[unit]) {
+        sum += this.values[unit] * this.matrix[unit]["milliseconds"];
+      }
+    }
+    return sum;
   }
 
   /**
@@ -694,8 +699,11 @@ export default class Duration {
   normalize() {
     if (!this.isValid) return this;
     const vals = this.toObject();
-    normalizeValues(this.matrix, vals);
-    return clone(this, { values: vals }, true);
+    if (this.valueOf() >= 0) {
+      normalizeValues(this.matrix, vals);
+      return clone(this, { values: vals }, true);
+    }
+    return this.negate().normalize().negate();
   }
 
   /**
diff --git a/src/impl/locale.js b/src/impl/locale.js
index a1dcd33b..4a2f14ba 100644
--- a/src/impl/locale.js
+++ b/src/impl/locale.js
@@ -120,7 +120,7 @@ function intlConfigString(localeStr, numberingSystem, outputCalendar) {
 function mapMonths(f) {
   const ms = [];
   for (let i = 1; i <= 12; i++) {
-    const dt = DateTime.utc(2016, i, 1);
+    const dt = DateTime.utc(2009, i, 1);
     ms.push(f(dt));
   }
   return ms;


Test patch:
diff --git a/test/duration/units.test.js b/test/duration/units.test.js
index 676f4d6f..2b37fad4 100644
--- a/test/duration/units.test.js
+++ b/test/duration/units.test.js
@@ -189,6 +189,10 @@ test("Duration#normalize handles the full grid partially negative durations", ()
       { months: 0, days: -28 },
       { months: 0, days: -28 },
     ],
+    [
+      { hours: 96, minutes: 0, seconds: -10 },
+      { hours: 95, minutes: 59, seconds: 50 },
+    ],
   ];
 
   sets.forEach(([from, to]) => {
diff --git a/test/info/listers.test.js b/test/info/listers.test.js
index b54deb59..7b4597d0 100644
--- a/test/info/listers.test.js
+++ b/test/info/listers.test.js
@@ -105,6 +105,8 @@ test("Info.months respects the numbering system", () => {
 
 test("Info.months respects the calendar", () => {
   expect(Info.months("long", { locale: "en", outputCalendar: "islamic" })).toEqual([
+    "Muharram",
+    "Safar",
     "Rabiʻ I",
     "Rabiʻ II",
     "Jumada I",
@@ -115,8 +117,6 @@ test("Info.months respects the calendar", () => {
     "Shawwal",
     "Dhuʻl-Qiʻdah",
     "Dhuʻl-Hijjah",
-    "Safar",
-    "Rabiʻ I",
   ]);
 });