Repository: moment/luxon
Pull request #1095 (2021-12-15 10:59:05)

Update formatting.md
correct typo

linux-foundation-easycla[bot]: <a href="https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/19772872/47118129/1095/#/?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/1095/#/?version=2' target='_blank'>:x:</a> - drmrbrewer The commit (8553d812af6097d80e4ac66920ff509aae375179) is not authorized under a signed CLA. <a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/19772872/47118129/1095/#/?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/CHANGELOG.md b/CHANGELOG.md
index dd055388..28f8a208 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## 2.3.0 (2022-01-02)
+
+ * Major perf improvements to `toISO()`, `toISODate()`, `toISOTime()`, and `toSQLDate()`
+ * Fixed date padding for negative years in `toISO()`
+ * Added Duration#toHuman()
+
 ## 2.2.0 (2021-12-10)
 
  * Allow offsets to pick among ambiguous times when both an offset and zone are provided to `fromFormat`
diff --git a/benchmarks/datetime.js b/benchmarks/datetime.js
index 1fcd0faf..0362f8ee 100644
--- a/benchmarks/datetime.js
+++ b/benchmarks/datetime.js
@@ -24,11 +24,11 @@ function runDateTimeSuite() {
       .add("DateTime.fromSQL", () => {
         DateTime.fromSQL("2016-05-14 10:23:54.2346");
       })
-      .add("DateTime.fromString", () => {
-        DateTime.fromString("1982/05/25 09:10:11.445", "yyyy/MM/dd HH:mm:ss.SSS");
+      .add("DateTime.fromFormat", () => {
+        DateTime.fromFormat("1982/05/25 09:10:11.445", "yyyy/MM/dd HH:mm:ss.SSS");
       })
-      .add("DateTime.fromString with zone", () => {
-        DateTime.fromString("1982/05/25 09:10:11.445", "yyyy/MM/dd HH:mm:ss.SSS", {
+      .add("DateTime.fromFormat with zone", () => {
+        DateTime.fromFormat("1982/05/25 09:10:11.445", "yyyy/MM/dd HH:mm:ss.SSS", {
           zone: "America/Los_Angeles",
         });
       })
diff --git a/docs/formatting.md b/docs/formatting.md
index 7eb1dd61..c08ee3e1 100644
--- a/docs/formatting.md
+++ b/docs/formatting.md
@@ -204,7 +204,7 @@ The macro options available correspond one-to-one with the preset formats define
 | cccc             | EEEE         | day of the week, as an unabbreviated localized string          | `Wednesday`                                                   |
 | ccccc            | EEEEE        | day of the week, as a single localized letter                  | `W`                                                           |
 | L                | M            | month as an unpadded number                                    | `8`                                                           |
-| LL               | MM           | month as an padded number                                      | `08`                                                          |
+| LL               | MM           | month as a padded number                                       | `08`                                                          |
 | LLL              | MMM          | month as an abbreviated localized string                       | `Aug`                                                         |
 | LLLL             | MMMM         | month as an unabbreviated localized string                     | `August`                                                      |
 | LLLLL            | MMMMM        | month as a single localized letter                             | `A`                                                           |
diff --git a/package-lock.json b/package-lock.json
index d29e2a0b..adbb8e64 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "luxon",
-  "version": "2.2.0",
+  "version": "2.3.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "luxon",
-      "version": "2.2.0",
+      "version": "2.3.0",
       "license": "MIT",
       "devDependencies": {
         "@babel/core": "^7.16.0",
diff --git a/package.json b/package.json
index 9401f617..2b5f26e1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "luxon",
-  "version": "2.2.0",
+  "version": "2.3.0",
   "description": "Immutable date wrapper",
   "author": "Isaac Cambron",
   "keywords": [
diff --git a/scripts/version b/scripts/version
index 82ecd8ba..905b48ba 100755
--- a/scripts/version
+++ b/scripts/version
@@ -5,12 +5,7 @@ abort() {
   exit 1
 }
 
-if [[ -x $(which ggrep) ]]
-then
-  grepper='ggrep'
-else
-  grepper='grep'
-fi
+grepper='grep'
 
 PACKAGE_VERSION=$(node -p "require('./package.json').version")
 PACKAGE_LOCK_VERSION=$(node -p "require('./package-lock.json').version")
diff --git a/src/datetime.js b/src/datetime.js
index eb044c2e..46e8e117 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -18,6 +18,7 @@ import {
   normalizeObject,
   roundTo,
   objToLocalTS,
+  padStart,
 } from "./impl/util.js";
 import { normalizeZone } from "./impl/zoneUtil.js";
 import diff from "./impl/diff.js";
@@ -188,46 +189,61 @@ function toTechFormat(dt, format, allowZ = true) {
     : null;
 }
 
-// technical time formats (e.g. the time part of ISO 8601), take some options
-// and this commonizes their handling
-function toTechTimeFormat(
-  dt,
-  {
-    suppressSeconds = false,
-    suppressMilliseconds = false,
-    includeOffset,
-    includePrefix = false,
-    includeZone = false,
-    spaceZone = false,
-    format = "extended",
+function toISODate(o, extended) {
+  const longFormat = o.c.year > 9999 || o.c.year < 0;
+  let c = "";
+  if (longFormat && o.c.year >= 0) c += "+";
+  c += padStart(o.c.year, longFormat ? 6 : 4);
+
+  if (extended) {
+    c += "-";
+    c += padStart(o.c.month);
+    c += "-";
+    c += padStart(o.c.day);
+  } else {
+    c += padStart(o.c.month);
+    c += padStart(o.c.day);
   }
-) {
-  let fmt = format === "basic" ? "HHmm" : "HH:mm";
+  return c;
+}
 
-  if (!suppressSeconds || dt.second !== 0 || dt.millisecond !== 0) {
-    fmt += format === "basic" ? "ss" : ":ss";
-    if (!suppressMilliseconds || dt.millisecond !== 0) {
-      fmt += ".SSS";
+function toISOTime(o, extended, suppressSeconds, suppressMilliseconds, includeOffset) {
+  let c = padStart(o.c.hour);
+  if (extended) {
+    c += ":";
+    c += padStart(o.c.minute);
+    if (o.c.second !== 0 || !suppressSeconds) {
+      c += ":";
     }
+  } else {
+    c += padStart(o.c.minute);
   }
 
-  if ((includeZone || includeOffset) && spaceZone) {
-    fmt += " ";
-  }
+  if (o.c.second !== 0 || !suppressSeconds) {
+    c += padStart(o.c.second);
 
-  if (includeZone) {
-    fmt += "z";
-  } else if (includeOffset) {
-    fmt += format === "basic" ? "ZZZ" : "ZZ";
+    if (o.c.millisecond !== 0 || !suppressMilliseconds) {
+      c += ".";
+      c += padStart(o.c.millisecond, 3);
+    }
   }
 
-  let str = toTechFormat(dt, fmt);
-
-  if (includePrefix) {
-    str = "T" + str;
+  if (includeOffset) {
+    if (o.isOffsetFixed && o.offset === 0) {
+      c += "Z";
+    } else if (o.o < 0) {
+      c += "-";
+      c += padStart(Math.trunc(-o.o / 60));
+      c += ":";
+      c += padStart(Math.trunc(-o.o % 60));
+    } else {
+      c += "+";
+      c += padStart(Math.trunc(o.o / 60));
+      c += ":";
+      c += padStart(Math.trunc(o.o % 60));
+    }
   }
-
-  return str;
+  return c;
 }
 
 // defaults for unspecified units in the supported calendars
@@ -1394,7 +1410,7 @@ export default class DateTime {
    * See {@link DateTime#plus}
    * @param {Duration|Object|number} duration - The amount to subtract. Either a Luxon Duration, a number of milliseconds, the object argument to Duration.fromObject()
    @return {DateTime}
-  */
+   */
   minus(duration) {
     if (!this.isValid) return this;
     const dur = Duration.fromDurationLike(duration).negate();
@@ -1542,18 +1558,28 @@ export default class DateTime {
    * @param {boolean} [opts.suppressSeconds=false] - exclude seconds from the format if they're 0
    * @param {boolean} [opts.includeOffset=true] - include the offset, such as 'Z' or '-04:00'
    * @param {string} [opts.format='extended'] - choose between the basic and extended format
-   * @example DateTime.utc(1982, 5, 25).toISO() //=> '1982-05-25T00:00:00.000Z'
+   * @example DateTime.utc(1983, 5, 25).toISO() //=> '1982-05-25T00:00:00.000Z'
    * @example DateTime.now().toISO() //=> '2017-04-22T20:47:05.335-04:00'
    * @example DateTime.now().toISO({ includeOffset: false }) //=> '2017-04-22T20:47:05.335'
    * @example DateTime.now().toISO({ format: 'basic' }) //=> '20170422T204705.335-0400'
    * @return {string}
    */
-  toISO(opts = {}) {
+  toISO({
+    format = "extended",
+    suppressSeconds = false,
+    suppressMilliseconds = false,
+    includeOffset = true,
+  } = {}) {
     if (!this.isValid) {
       return null;
     }
 
-    return `${this.toISODate(opts)}T${this.toISOTime(opts)}`;
+    const ext = format === "extended";
+
+    let c = toISODate(this, ext);
+    c += "T";
+    c += toISOTime(this, ext, suppressSeconds, suppressMilliseconds, includeOffset);
+    return c;
   }
 
   /**
@@ -1565,12 +1591,11 @@ export default class DateTime {
    * @return {string}
    */
   toISODate({ format = "extended" } = {}) {
-    let fmt = format === "basic" ? "yyyyMMdd" : "yyyy-MM-dd";
-    if (this.year > 9999) {
-      fmt = "+" + fmt;
+    if (!this.isValid) {
+      return null;
     }
 
-    return toTechFormat(this, fmt);
+    return toISODate(this, format === "extended");
   }
 
   /**
@@ -1603,17 +1628,19 @@ export default class DateTime {
     includePrefix = false,
     format = "extended",
   } = {}) {
-    return toTechTimeFormat(this, {
-      suppressSeconds,
-      suppressMilliseconds,
-      includeOffset,
-      includePrefix,
-      format,
-    });
+    if (!this.isValid) {
+      return null;
+    }
+
+    let c = includePrefix ? "T" : "";
+    return (
+      c +
+      toISOTime(this, format === "extended", suppressSeconds, suppressMilliseconds, includeOffset)
+    );
   }
 
   /**
-   * Returns an RFC 2822-compatible string representation of this DateTime, always in UTC
+   * Returns an RFC 2822-compatible string representation of this DateTime
    * @example DateTime.utc(2014, 7, 13).toRFC2822() //=> 'Sun, 13 Jul 2014 00:00:00 +0000'
    * @example DateTime.local(2014, 7, 13).toRFC2822() //=> 'Sun, 13 Jul 2014 00:00:00 -0400'
    * @return {string}
@@ -1623,7 +1650,7 @@ export default class DateTime {
   }
 
   /**
-   * Returns a string representation of this DateTime appropriate for use in HTTP headers.
+   * Returns a string representation of this DateTime appropriate for use in HTTP headers. The output is always expressed in GMT.
    * Specifically, the string conforms to RFC 1123.
    * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
    * @example DateTime.utc(2014, 7, 13).toHTTP() //=> 'Sun, 13 Jul 2014 00:00:00 GMT'
@@ -1640,7 +1667,10 @@ export default class DateTime {
    * @return {string}
    */
   toSQLDate() {
-    return toTechFormat(this, "yyyy-MM-dd");
+    if (!this.isValid) {
+      return null;
+    }
+    return toISODate(this, true);
   }
 
   /**
@@ -1655,11 +1685,18 @@ export default class DateTime {
    * @return {string}
    */
   toSQLTime({ includeOffset = true, includeZone = false } = {}) {
-    return toTechTimeFormat(this, {
-      includeOffset,
-      includeZone,
-      spaceZone: true,
-    });
+    let fmt = "HH:mm:ss.SSS";
+
+    if (includeZone || includeOffset) {
+      fmt += " ";
+      if (includeZone) {
+        fmt += "z";
+      } else if (includeOffset) {
+        fmt += "ZZ";
+      }
+    }
+
+    return toTechFormat(this, fmt, true);
   }
 
   /**
@@ -1824,8 +1861,8 @@ export default class DateTime {
     if (!this.isValid) return false;
 
     const inputMs = otherDateTime.valueOf();
-    const otherZoneDateTime = this.setZone(otherDateTime.zone, { keepLocalTime: true });
-    return otherZoneDateTime.startOf(unit) <= inputMs && inputMs <= otherZoneDateTime.endOf(unit);
+    const adjustedToZone = this.setZone(otherDateTime.zone, { keepLocalTime: true });
+    return adjustedToZone.startOf(unit) <= inputMs && inputMs <= adjustedToZone.endOf(unit);
   }
 
   /**
diff --git a/src/duration.js b/src/duration.js
index fdd5ad5c..5db8ddd8 100644
--- a/src/duration.js
+++ b/src/duration.js
@@ -425,6 +425,36 @@ export default class Duration {
       : INVALID;
   }
 
+  /**
+   * Returns a string representation of a Duration with all units included
+   * To modify its behavior use the `listStyle` and any Intl.NumberFormat option, though `unitDisplay` is especially relevant. See {@link Intl.NumberFormat}.
+   * @param opts - On option object to override the formatting. Accepts the same keys as the options parameter of the native `Int.NumberFormat` constructor, as well as `listStyle`.
+   * @example
+   * ```js
+   * var dur = Duration.fromObject({ days: 1, hours: 5, minutes: 6 })
+   * dur.toHuman() //=> '1 day, 5 hours, 6 minutes'
+   * dur.toHuman({ listStyle: "long" }) //=> '1 day, 5 hours, and 6 minutes'
+   * dur.toHuman({ unitDisplay: "short" }) //=> '1 day, 5 hr, 6 min'
+   * ```
+   */
+  toHuman(opts = {}) {
+    const l = orderedUnits
+      .map((unit) => {
+        const val = this.values[unit];
+        if (isUndefined(val)) {
+          return null;
+        }
+        return this.loc
+          .numberFormatter({ style: "unit", unitDisplay: "long", ...opts, unit: unit.slice(0, -1) })
+          .format(val);
+      })
+      .filter((n) => n);
+
+    return this.loc
+      .listFormatter({ type: "conjunction", style: opts.listStyle || "narrow", ...opts })
+      .format(l);
+  }
+
   /**
    * Returns a JavaScript object with this Duration's values.
    * @example Duration.fromObject({ years: 1, days: 6, seconds: 2 }).toObject() //=> { years: 1, days: 6, seconds: 2 }
diff --git a/src/impl/locale.js b/src/impl/locale.js
index 500a60c1..257907b7 100644
--- a/src/impl/locale.js
+++ b/src/impl/locale.js
@@ -4,6 +4,19 @@ import Settings from "../settings.js";
 import DateTime from "../datetime.js";
 import IANAZone from "../zones/IANAZone.js";
 
+// todo - remap caching
+
+let intlLFCache = {};
+function getCachedLF(locString, opts = {}) {
+  const key = JSON.stringify([locString, opts]);
+  let dtf = intlLFCache[key];
+  if (!dtf) {
+    dtf = new Intl.ListFormat(locString, opts);
+    intlLFCache[key] = dtf;
+  }
+  return dtf;
+}
+
 let intlDTCache = {};
 function getCachedDTF(locString, opts = {}) {
   const key = JSON.stringify([locString, opts]);
@@ -144,8 +157,10 @@ class PolyNumberFormatter {
     this.padTo = opts.padTo || 0;
     this.floor = opts.floor || false;
 
-    if (!forceSimple) {
-      const intlOpts = { useGrouping: false };
+    const { padTo, floor, ...otherOpts } = opts;
+
+    if (!forceSimple || Object.keys(otherOpts).length > 0) {
+      const intlOpts = { useGrouping: false, ...opts };
       if (opts.padTo > 0) intlOpts.minimumIntegerDigits = opts.padTo;
       this.inf = getCachedINF(intl, intlOpts);
     }
@@ -308,7 +323,7 @@ export default class Locale {
     return this.fastNumbersCached;
   }
 
-  listingMode(defaultOK = true) {
+  listingMode() {
     const isActuallyEn = this.isEnglish();
     const hasNoWeirdness =
       (this.numberingSystem === null || this.numberingSystem === "latn") &&
@@ -421,6 +436,10 @@ export default class Locale {
     return new PolyRelFormatter(this.intl, this.isEnglish(), opts);
   }
 
+  listFormatter(opts = {}) {
+    return getCachedLF(this.intl, opts);
+  }
+
   isEnglish() {
     return (
       this.locale === "en" ||
diff --git a/src/impl/util.js b/src/impl/util.js
index 9f267211..43def24c 100644
--- a/src/impl/util.js
+++ b/src/impl/util.js
@@ -87,17 +87,14 @@ export function floorMod(x, n) {
 }
 
 export function padStart(input, n = 2) {
-  const minus = input < 0 ? "-" : "";
-  const target = minus ? input * -1 : input;
-  let result;
-
-  if (target.toString().length < n) {
-    result = ("0".repeat(n) + target).slice(-n);
+  const isNeg = input < 0;
+  let padded;
+  if (isNeg) {
+    padded = "-" + ("" + -input).padStart(n, "0");
   } else {
-    result = target.toString();
+    padded = ("" + input).padStart(n, "0");
   }
-
-  return `${minus}${result}`;
+  return padded;
 }
 
 export function parseInteger(string) {
diff --git a/src/impl/zoneUtil.js b/src/impl/zoneUtil.js
index 4b38ea94..c6de7bb8 100644
--- a/src/impl/zoneUtil.js
+++ b/src/impl/zoneUtil.js
@@ -19,8 +19,7 @@ export function normalizeZone(input, defaultZone) {
     const lowered = input.toLowerCase();
     if (lowered === "local" || lowered === "system") return defaultZone;
     else if (lowered === "utc" || lowered === "gmt") return FixedOffsetZone.utcInstance;
-    else if (IANAZone.isValidSpecifier(lowered)) return IANAZone.create(input);
-    else return FixedOffsetZone.parseSpecifier(lowered) || new InvalidZone(input);
+    else return FixedOffsetZone.parseSpecifier(lowered) || IANAZone.create(input);
   } else if (isNumber(input)) {
     return FixedOffsetZone.instance(input);
   } else if (typeof input === "object" && input.offset && typeof input.offset === "number") {
diff --git a/src/info.js b/src/info.js
index 1f30b32f..d8c8e08a 100644
--- a/src/info.js
+++ b/src/info.js
@@ -27,7 +27,7 @@ export default class Info {
    * @return {boolean}
    */
   static isValidIANAZone(zone) {
-    return IANAZone.isValidSpecifier(zone) && IANAZone.isValidZone(zone);
+    return IANAZone.isValidZone(zone);
   }
 
   /**
diff --git a/src/luxon.js b/src/luxon.js
index d3786104..8957d6e9 100644
--- a/src/luxon.js
+++ b/src/luxon.js
@@ -9,7 +9,7 @@ import InvalidZone from "./zones/invalidZone.js";
 import SystemZone from "./zones/systemZone.js";
 import Settings from "./settings.js";
 
-const VERSION = "2.2.0";
+const VERSION = "2.3.0";
 
 export {
   VERSION,
diff --git a/src/zones/IANAZone.js b/src/zones/IANAZone.js
index a0d002c6..efe7e564 100644
--- a/src/zones/IANAZone.js
+++ b/src/zones/IANAZone.js
@@ -80,12 +80,12 @@ export default class IANAZone extends Zone {
    * Returns whether the provided string is a valid specifier. This only checks the string's format, not that the specifier identifies a known zone; see isValidZone for that.
    * @param {string} s - The string to check validity on
    * @example IANAZone.isValidSpecifier("America/New_York") //=> true
-   * @example IANAZone.isValidSpecifier("Fantasia/Castle") //=> true
    * @example IANAZone.isValidSpecifier("Sport~~blorp") //=> false
+   * @deprecated This method returns false some valid IANA names. Use isValidZone instead
    * @return {boolean}
    */
   static isValidSpecifier(s) {
-    return !!(s && s.match(matchingRegex));
+    return this.isValidZone(s);
   }
 
   /**


Test patch:
diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js
index 762d6e5c..2d4dd550 100644
--- a/test/datetime/format.test.js
+++ b/test/datetime/format.test.js
@@ -65,6 +65,26 @@ test("DateTime#toISO() rounds fractional timezone minute offsets", () => {
   );
 });
 
+test("DateTime#toISO() handles long gregorian format", () => {
+  const negativeYear = dt.set({ year: 12345 });
+  expect(negativeYear.toISO()).toBe("+012345-05-25T09:23:54.123Z");
+});
+
+test("DateTime#toISO() handles negative years", () => {
+  const negativeYear = dt.set({ year: -12345 });
+  expect(negativeYear.toISO()).toBe("-012345-05-25T09:23:54.123Z");
+});
+
+test("DateTime#toISO() default to Z when timezone is 00:00", () => {
+  const negativeYear = dt.setZone("utc");
+  expect(negativeYear.toISO()).toBe("1982-05-25T09:23:54.123Z");
+});
+
+test("DateTime#toISO() renders 00:00 for non-offset but non utc datetimes", () => {
+  const negativeYear = dt.setZone("Africa/Abidjan");
+  expect(negativeYear.toISO()).toBe("1982-05-25T09:23:54.123+00:00");
+});
+
 //------
 // #toISODate()
 //------
@@ -95,10 +115,10 @@ test("DateTime#toISODate() returns ISO 8601 date in format [±YYYYY]", () => {
 
 test("DateTime#toISODate() correctly pads negative years", () => {
   expect(DateTime.fromObject({ year: -1, month: 1, day: 1 }, { zone: "utc" }).toISODate()).toBe(
-    "-0001-01-01"
+    "-000001-01-01"
   );
   expect(DateTime.fromObject({ year: -10, month: 1, day: 1 }, { zone: "utc" }).toISODate()).toBe(
-    "-0010-01-01"
+    "-000010-01-01"
   );
 });
 
diff --git a/test/datetime/zone.test.js b/test/datetime/zone.test.js
index 4d5fb548..cc5567fc 100644
--- a/test/datetime/zone.test.js
+++ b/test/datetime/zone.test.js
@@ -145,6 +145,18 @@ test("DateTime#setZone accepts IANA zone names", () => {
   expect(zoned.hour).toBe(6); // cedt is +2
 });
 
+test.each([
+  ["PST8PDT", -7], // my date is in daylight time
+  ["EST5EDT", -4],
+  ["GMT+0", 0],
+  ["GMT0", 0],
+])("DateTime#setZone accepts whacky zone %p", (iana, offset) => {
+  const zoned = dt().setZone(iana);
+  expect(zoned.isValid).toBe(true);
+  expect(zoned.zoneName).toBe(iana);
+  expect(zoned.offset).toBe(offset * 60);
+});
+
 test("DateTime#setZone accepts a keepLocalTime option", () => {
   const zoned = dt().toUTC().setZone("America/Los_Angeles", { keepLocalTime: true });
   expect(zoned.zoneName).toBe("America/Los_Angeles");
diff --git a/test/duration/format.test.js b/test/duration/format.test.js
index 9a50c9d7..686154fe 100644
--- a/test/duration/format.test.js
+++ b/test/duration/format.test.js
@@ -263,3 +263,35 @@ test("Duration#toFormat localizes the numbers", () => {
 test("Duration#toFormat returns a lame string for invalid durations", () => {
   expect(Duration.invalid("because").toFormat("yy")).toBe("Invalid Duration");
 });
+
+//------
+// #humanize()
+//------
+
+test("Duration#toHuman formats out a list", () => {
+  expect(dur().toHuman()).toEqual(
+    "1 year, 2 months, 1 week, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds"
+  );
+});
+
+test("Duration#toHuman only shows the units you have", () => {
+  expect(Duration.fromObject({ years: 3, hours: 4 }).toHuman()).toEqual("3 years, 4 hours");
+});
+
+test("Duration#toHuman accepts a listStyle", () => {
+  expect(dur().toHuman({ listStyle: "long" })).toEqual(
+    "1 year, 2 months, 1 week, 3 days, 4 hours, 5 minutes, 6 seconds, and 7 milliseconds"
+  );
+});
+
+test("Duration#toHuman accepts number format opts", () => {
+  expect(dur().toHuman({ unitDisplay: "short" })).toEqual(
+    "1 yr, 2 mths, 1 wk, 3 days, 4 hr, 5 min, 6 sec, 7 ms"
+  );
+});
+
+test("Duration#toHuman works in differt languages", () => {
+  expect(dur().reconfigure({ locale: "fr" }).toHuman()).toEqual(
+    "1 an, 2 mois, 1 semaine, 3 jours, 4 heures, 5 minutes, 6 secondes, 7 millisecondes"
+  );
+});
diff --git a/test/duration/info.test.js b/test/duration/info.test.js
index 350a3a51..59500fe6 100644
--- a/test/duration/info.test.js
+++ b/test/duration/info.test.js
@@ -2,18 +2,11 @@
 
 import { Duration } from "../../src/luxon";
 
-const dur = Duration.fromObject(
-  {
-    years: 1,
-    months: 2,
-    days: 3.3,
-  },
-  {
-    locale: "fr",
-    numberingSystem: "beng",
-    conversionAccuracy: "longterm",
-  }
-);
+const dur = Duration.fromObject({
+  years: 1,
+  months: 2,
+  days: 3.3,
+});
 
 //------
 // #toObject
@@ -26,21 +19,6 @@ test("Duration#toObject returns the object", () => {
   });
 });
 
-test("Duration#toObject accepts a flag to return config", () => {
-  expect(dur.toObject({ includeConfig: true })).toEqual(
-    {
-      years: 1,
-      months: 2,
-      days: 3.3,
-    },
-    {
-      locale: "fr",
-      numberingSystem: "beng",
-      conversionAccuracy: "longterm",
-    }
-  );
-});
-
 test("Duration#toObject returns an empty object for invalid durations", () => {
   expect(Duration.invalid("because").toObject()).toEqual({});
 });
diff --git a/test/zones/IANA.test.js b/test/zones/IANA.test.js
index a38789d1..e42ac94c 100644
--- a/test/zones/IANA.test.js
+++ b/test/zones/IANA.test.js
@@ -1,6 +1,5 @@
 /* global test expect */
 import { FixedOffsetZone, IANAZone } from "../../src/luxon";
-import { withoutIntl } from "../helpers";
 
 test("IANAZone.create returns a singleton per zone name", () => {
   expect(IANAZone.create("UTC")).toBe(IANAZone.create("UTC"));
@@ -19,7 +18,8 @@ test("IANAZone.create should return IANAZone instance", () => {
 
 test("IANAZone.isValidSpecifier", () => {
   expect(IANAZone.isValidSpecifier("America/New_York")).toBe(true);
-  expect(IANAZone.isValidSpecifier("Fantasia/Castle")).toBe(true);
+  // this used to return true but now returns false, because we just defer to isValidZone
+  expect(IANAZone.isValidSpecifier("Fantasia/Castle")).toBe(false);
   expect(IANAZone.isValidSpecifier("Sport~~blorp")).toBe(false);
   expect(IANAZone.isValidSpecifier("Etc/GMT+8")).toBe(true);
   expect(IANAZone.isValidSpecifier("Etc/GMT-8")).toBe(true);