Repository: moment/luxon
Pull request #641 (2020-01-05 21:38:44)

Merge latest `master` into the `2.0` branch


Patch:
diff --git a/README.md b/README.md
index 28b61f88..feaaeab3 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ See [contributing](contributing.md).
 ![Phasers to stun][phasers-image]
 
 [license-image]: http://img.shields.io/badge/license-MIT-blue.svg
-[license]: license.txt
+[license]: license.md
 
 [travis-url]: http://travis-ci.org/moment/luxon
 [travis-image]: https://api.travis-ci.org/moment/luxon.svg?branch=master
diff --git a/changelog.md b/changelog.md
index 34b16e88..88084a08 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## 1.21.3
+
+ * Fix parsing of meridiems in macro tokens in newer versions of v8
+
+## 1.21.2
+
+ * Fix bug in Chrome Canary that threw off time zone calculations
+
 ## 1.21.1
 
  * Fix for quarter parsing
diff --git a/docs/install.md b/docs/install.md
index 8f1d06e8..f30358c5 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -107,7 +107,3 @@ npm install --save-dev @types/luxon
 ## React Native
 
 React Native works just fine, but React Native for Android doesn't ship with Intl support, which you need for [a lot of Luxon's functionality](matrix.html). Use [jsc-android-buildscripts](https://github.com/SoftwareMansion/jsc-android-buildscripts) to fix it.
-
-## Meteor
-
-[Help wanted.]
diff --git a/docs/math.md b/docs/math.md
index 3f4063a6..13817cf4 100644
--- a/docs/math.md
+++ b/docs/math.md
@@ -258,6 +258,8 @@ Be careful of converting between units. It's easy to lose information. Let's say
 ```js
 var end = DateTime.fromISO('2017-03-13');
 var start = DateTime.fromISO('2017-02-13');
+
+var diffInMonths = end.diff(start, 'months');
 diffInMonths.as('days'); //=> 30
 ```
 
@@ -272,7 +274,7 @@ It's important to remember that diffs are Duration objects, and a Duration is ju
 
 
 ```js
-var diff = end.diff(start) // default unit is milliseconds
+var diff = end.diff(start); // default unit is milliseconds
 
 // wtf, that's not a month!
 diff.as('months'); //=> 0.9319444 
@@ -281,7 +283,14 @@ diff.as('months'); //=> 0.9319444
 diff.shiftTo('hours').as('days'); //=> 27.958333333333332
 ```
 
-Normally you won't run into this problem if you think clearly about what you want to do with a diff. But sometimes you really do want an object that represents the subtraction itself, not the result. [Intervals](../class/src/interval.js~Interval.html) can help. Intervals are mostly used to keep track of ranges of time, but they make for "anchored" diffs too. For example:
+Normally you won't run into this problem if you think clearly about what you want to do with a diff. Specifically, make sure you diff in the units you actually want to use. Then Luxon knows to answer the question you really want to ask.
+
+```js
+var monthsDiff = end.diff(start, "months");
+var daysDiff = end.diff(start, "days");
+```
+
+But sometimes you really do want an object that represents the subtraction itself, not the result. [Intervals](../class/src/interval.js~Interval.html) can help. Intervals are mostly used to keep track of ranges of time, but they make for "anchored" diffs too. For example:
 
 ```js
 var end = DateTime.fromISO('2017-03-13');
@@ -306,3 +315,5 @@ end = DateTime.fromISO('2018-05-25');
 i = start.until(end);
 i.toDuration(['years', 'months', 'days']).toObject(); //=> { years: 1, months: 3, days: 12 }
 ```
+
+Of course, once you've converted to a Duration, you're back in the same spot you were with the diff case; *further* conversions will be lossy. So the point is to think carefully about what information you have when.
diff --git a/docs/matrix.md b/docs/matrix.md
index 49f90bd1..b8cf6418 100644
--- a/docs/matrix.md
+++ b/docs/matrix.md
@@ -20,7 +20,8 @@ Luxon officially supports the last two versions of the major browsers, with some
 | iOS Safari (iOS version numbers) | >= 11    | ✓     | ✓           | ✓           | ✗                             |
 |                                  | 10       | ✓     | ✗           | ✗           | ✗                             |
 |                                  | 9        | ✗     | ✓           | ✗           | ✗                             |
-| Node w/ICU                       | >= 8     | ✓     | ✓           | ✓           | ✗                             |
+| Node w/ICU                       | >= 12    | ✓     | ✓           | ✓           | ✓                             |
+|                                  | >= 8     | ✓     | ✓           | ✓           | ✗                             |
 |                                  | 6        | ✗     | ✓           | ✗           | ✗                             |
 | Node w/o ICU                     | >= 8     | ✓     | ✗           | ✗           | ✗                             |
 |                                  | 6        | ✗     | ✗           | ✗           | ✗                             |
diff --git a/package-lock.json b/package-lock.json
index 49e848cf..426f210e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "luxon",
-  "version": "1.21.1",
+  "version": "1.21.3",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -6554,9 +6554,9 @@
       "dev": true
     },
     "handlebars": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
-      "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
+      "version": "4.5.3",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
+      "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
       "dev": true,
       "requires": {
         "neo-async": "^2.6.0",
diff --git a/package.json b/package.json
index e96ac328..35deb553 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "luxon",
-  "version": "1.21.1",
+  "version": "1.21.3",
   "description": "Immutable date wrapper",
   "author": "Isaac Cambron",
   "keywords": [
@@ -60,7 +60,7 @@
     "eslint-plugin-react": "latest",
     "eslint-plugin-standard": "latest",
     "fs-extra": "^6.0.1",
-    "full-icu": "latest",
+    "full-icu": "^1.3.0",
     "husky": "latest",
     "jest": "^24.7.1",
     "lint-staged": "latest",
diff --git a/src/datetime.js b/src/datetime.js
index ad77a7da..f382d8ef 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -451,7 +451,11 @@ export default class DateTime {
    * @param {number} [minute=0] - The minute of the hour, meaning a number between 0 and 59
    * @param {number} [second=0] - The second of the minute, meaning a number between 0 and 59
    * @param {number} [millisecond=0] - The millisecond of the second, meaning a number between 0 and 999
-   * @param {object} [opts] - Options for creation
+   * @param {Object} options - configuration options for the DateTime
+   * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into
+   * @param {string} [options.locale] - a locale to set on the resulting DateTime instance
+   * @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance
+   * @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance
    * @example DateTime.local()                                  //~> now
    * @example DateTime.local({ zone: "America/New_York" })      //~> now, in US east coast time
    * @example DateTime.local(2017)                              //~> 2017-01-01T00:00:00
@@ -462,11 +466,6 @@ export default class DateTime {
    * @example DateTime.local(2017, 3, 12, 5, 45)                //~> 2017-03-12T05:45:00
    * @example DateTime.local(2017, 3, 12, 5, 45, 10)            //~> 2017-03-12T05:45:10
    * @example DateTime.local(2017, 3, 12, 5, 45, 10, 765)       //~> 2017-03-12T05:45:10.765
-   * @param {Object} options - configuration options for the DateTime
-   * @param {string|Zone} [options.zone='local'] - the zone to place the DateTime into
-   * @param {string} [options.locale] - a locale to set on the resulting DateTime instance
-   * @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance
-   * @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance
    * @return {DateTime}
    */
   static local() {
@@ -486,17 +485,17 @@ export default class DateTime {
    * @param {number} [millisecond=0] - The millisecond of the second, meaning a number between 0 and 999
    * @param {Object} options - configuration options for the DateTime
    * @param {string} [options.locale] - a locale to set on the resulting DateTime instance
-   * @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance
-   * @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance
-   * @example DateTime.utc()                                               //~> now
-   * @example DateTime.utc(2017)                                           //~> 2017-01-01T00:00:00Z
-   * @example DateTime.utc(2017, 3)                                        //~> 2017-03-01T00:00:00Z
-   * @example DateTime.utc(2017, 3, 12)                                    //~> 2017-03-12T00:00:00Z
-   * @example DateTime.utc(2017, 3, 12, 5)                                 //~> 2017-03-12T05:00:00Z
-   * @example DateTime.utc(2017, 3, 12, 5, 45)                             //~> 2017-03-12T05:45:00Z
-   * @example DateTime.utc(2017, 3, 12, 5, 45, { locale: "fr" } )          //~> 2017-03-12T05:45:00Z with a French locale
-   * @example DateTime.utc(2017, 3, 12, 5, 45, 10)                         //~> 2017-03-12T05:45:10Z
-   * @example DateTime.utc(2017, 3, 12, 5, 45, 10, 765, { locale: "fr")    //~> 2017-03-12T05:45:10.765Z with a French locale
+   * @param {string} [options.outputCalendar] - the output calendar to set on the resulting DateTime instance
+   * @param {string} [options.numberingSystem] - the numbering system to set on the resulting DateTime instance
+   * @example DateTime.utc()                                            //~> now
+   * @example DateTime.utc(2017)                                        //~> 2017-01-01T00:00:00Z
+   * @example DateTime.utc(2017, 3)                                     //~> 2017-03-01T00:00:00Z
+   * @example DateTime.utc(2017, 3, 12)                                 //~> 2017-03-12T00:00:00Z
+   * @example DateTime.utc(2017, 3, 12, 5)                              //~> 2017-03-12T05:00:00Z
+   * @example DateTime.utc(2017, 3, 12, 5, 45)                          //~> 2017-03-12T05:45:00Z
+   * @example DateTime.utc(2017, 3, 12, 5, 45, { locale: "fr" } )       //~> 2017-03-12T05:45:00Z with a French locale
+   * @example DateTime.utc(2017, 3, 12, 5, 45, 10)                      //~> 2017-03-12T05:45:10Z
+   * @example DateTime.utc(2017, 3, 12, 5, 45, 10, 765, { locale: "fr") //~> 2017-03-12T05:45:10.765Z with a French locale
    * @return {DateTime}
    */
   static utc() {
@@ -1410,13 +1409,13 @@ export default class DateTime {
    * Defaults to the system's locale if no locale has been specified
    * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/formatToParts
    * @param opts {Object} - Intl.DateTimeFormat constructor options, same as `toLocaleString`.
-   * @example DateTime.local().toLocaleString(); //=> [
-   *                                    //=>   { type: 'day', value: '25' },
-   *                                    //=>   { type: 'literal', value: '/' },
-   *                                    //=>   { type: 'month', value: '05' },
-   *                                    //=>   { type: 'literal', value: '/' },
-   *                                    //=>   { type: 'year', value: '1982' }
-   *                                    //=> ]
+   * @example DateTime.local().toLocaleParts(); //=> [
+   *                                   //=>   { type: 'day', value: '25' },
+   *                                   //=>   { type: 'literal', value: '/' },
+   *                                   //=>   { type: 'month', value: '05' },
+   *                                   //=>   { type: 'literal', value: '/' },
+   *                                   //=>   { type: 'year', value: '1982' }
+   *                                   //=> ]
    */
   toLocaleParts(opts = {}) {
     return Formatter.create(this.loc.clone(opts), opts).formatDateTimeParts(this);
diff --git a/src/duration.js b/src/duration.js
index 1dfa277f..996475b1 100644
--- a/src/duration.js
+++ b/src/duration.js
@@ -1,7 +1,7 @@
+import { InvalidArgumentError, InvalidUnitError, UnparsableStringError } from "./errors.js";
 import Formatter from "./impl/formatter.js";
 import Locale from "./impl/locale.js";
 import { parseISODuration } from "./impl/regexParser.js";
-import { InvalidArgumentError, InvalidUnitError, UnparsableStringError } from "./errors.js";
 import {
   asNumber,
   hasOwnProperty,
diff --git a/src/impl/locale.js b/src/impl/locale.js
index 7cfcf4ef..9f879602 100644
--- a/src/impl/locale.js
+++ b/src/impl/locale.js
@@ -16,7 +16,7 @@ function getCachedDTF(locString, opts = {}) {
 }
 
 let intlNumCache = {};
-function getCachendINF(locString, opts = {}) {
+function getCachedINF(locString, opts = {}) {
   const key = JSON.stringify([locString, opts]);
   let inf = intlNumCache[key];
   if (!inf) {
@@ -27,7 +27,7 @@ function getCachendINF(locString, opts = {}) {
 }
 
 let intlRelCache = {};
-function getCachendRTF(locString, opts = {}) {
+function getCachedRTF(locString, opts = {}) {
   const key = JSON.stringify([locString, opts]);
   let inf = intlRelCache[key];
   if (!inf) {
@@ -155,7 +155,7 @@ class PolyNumberFormatter {
     if (!forceSimple && hasIntl()) {
       const intlOpts = { useGrouping: false };
       if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo;
-      this.inf = getCachendINF(intl, intlOpts);
+      this.inf = getCachedINF(intl, intlOpts);
     }
   }
 
@@ -253,7 +253,7 @@ class PolyRelFormatter {
   constructor(intl, isEnglish, opts) {
     this.opts = Object.assign({ style: "long" }, opts);
     if (!isEnglish && hasRelative()) {
-      this.rtf = getCachendRTF(intl, opts);
+      this.rtf = getCachedRTF(intl, opts);
     }
   }
 
diff --git a/src/impl/tokenParser.js b/src/impl/tokenParser.js
index 9434117c..4e6c0d82 100644
--- a/src/impl/tokenParser.js
+++ b/src/impl/tokenParser.js
@@ -4,6 +4,7 @@ import FixedOffsetZone from "../zones/fixedOffsetZone.js";
 import IANAZone from "../zones/IANAZone.js";
 import DateTime from "../datetime.js";
 import { digitRegex, parseDigits } from "./digits.js";
+import { ConflictingSpecificationError } from "../errors.js";
 
 const MISSING_FTP = "missing Intl.DateTimeFormat.formatToParts support";
 
@@ -201,7 +202,7 @@ const partTypeStyleToTokenVal = {
     short: "EEE",
     long: "EEEE"
   },
-  dayperiod: "a",
+  dayPeriod: "a",
   hour: {
     numeric: "h",
     "2-digit": "hh"
@@ -399,7 +400,11 @@ export function explainFromTokens(locale, input, format) {
       regex = RegExp(regexString, "i"),
       [rawMatches, matches] = match(input, regex, handlers),
       [result, zone] = matches ? dateTimeFromMatches(matches) : [null, null];
-
+    if (hasOwnProperty(matches, "a") && hasOwnProperty(matches, "H")) {
+      throw new ConflictingSpecificationError(
+        "Can't include meridiem when specifying 24-hour format"
+      );
+    }
     return { input, tokens, regex, rawMatches, matches, result, zone };
   }
 }
diff --git a/src/zones/IANAZone.js b/src/zones/IANAZone.js
index a5f0e111..148d4018 100644
--- a/src/zones/IANAZone.js
+++ b/src/zones/IANAZone.js
@@ -156,8 +156,20 @@ export default class IANAZone extends Zone {
       dtf = makeDTF(this.name),
       [year, month, day, hour, minute, second] = dtf.formatToParts
         ? partsOffset(dtf, date)
-        : hackyOffset(dtf, date);
-    const asUTC = objToLocalTS({ year, month, day, hour, minute, second, millisecond: 0 });
+        : hackyOffset(dtf, date),
+      // work around https://bugs.chromium.org/p/chromium/issues/detail?id=1025564&can=2&q=%2224%3A00%22%20datetimeformat
+      adjustedHour = hour === 24 ? 0 : hour;
+
+    const asUTC = objToLocalTS({
+      year,
+      month,
+      day,
+      hour: adjustedHour,
+      minute,
+      second,
+      millisecond: 0
+    });
+
     let asTS = date.valueOf();
     asTS -= asTS % 1000;
     return (asUTC - asTS) / (60 * 1000);


Test patch:
diff --git a/test/datetime/tokenParse.test.js b/test/datetime/tokenParse.test.js
index edb9fc53..5558ffe0 100644
--- a/test/datetime/tokenParse.test.js
+++ b/test/datetime/tokenParse.test.js
@@ -67,6 +67,10 @@ test("DateTime.fromFormat() parses meridiems", () => {
   expect(i.hour).toBe(12);
 });
 
+test("DateTime.fromFormat() throws if you specify meridiem with 24-hour time", () => {
+  expect(() => DateTime.fromFormat("930PM", "Hmma")).toThrow(ConflictingSpecificationError);
+});
+
 test("DateTime.fromFormat() parses variable-digit years", () => {
   expect(() => DateTime.fromFormat("", "y")).toThrow(UnparsableStringError);
   expect(DateTime.fromFormat("2", "y").year).toBe(2);
diff --git a/test/interval/format.test.js b/test/interval/format.test.js
index 581784a4..f96bb565 100644
--- a/test/interval/format.test.js
+++ b/test/interval/format.test.js
@@ -41,6 +41,25 @@ test("Interval#toISOTime accepts ISO options", () => {
   expect(interval.toISOTime({ suppressMilliseconds: true })).toBe("09:00:00Z/13:30:00Z");
 });
 
+//------
+// .toISODate()
+//------
+
+test("Interval#toISODate returns a simple ISO date interval format", () =>
+  expect(interval.toISODate()).toBe("1982-05-25/1983-10-14"));
+
+//------
+// .toISOTime()
+//------
+
+test("Interval#toISOTime returns a simple ISO time interval format", () =>
+  expect(interval.toISOTime()).toBe("09:00:00.000Z/13:30:00.000Z"));
+
+test("Interval#toISOTime accepts ISO options", () => {
+  expect(interval.toISOTime({ suppressSeconds: true })).toBe("09:00Z/13:30Z");
+  expect(interval.toISOTime({ suppressMilliseconds: true })).toBe("09:00:00Z/13:30:00Z");
+});
+
 //------
 // .toFormat()
 //------