Repository: moment/luxon (js)

Issue:
Add support for ISO 8601 basic format to DateTime.toISO()
From [Wikipedia](https://en.wikipedia.org/wiki/ISO_8601):

> Representations can be done in one of two formats – a basic format with a minimal number of separators or an extended format with separators added to enhance human readability. The standard notes that "The basic format should be avoided in plain text." The separator used between date values (year, month, week, and day) is the hyphen, while the colon is used as the separator between time values (hours, minutes, and seconds). For example, the 6th day of the 1st month of the year 2009 may be written as "2009-01-06" in the extended format or simply as "20090106" in the basic format without ambiguity.

This could be done as a `{ format: 'basic' }` option with the default being `{ format: 'extended' }`. This format is useful when used in a URL.

Pull request description:
Add support for ISO basic format to DateTime#toISO
Closes #656.

Patch:
diff --git a/src/datetime.js b/src/datetime.js
index e5fc876e..539f7554 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -179,10 +179,10 @@ function parseDataToDateTime(parsed, parsedZone, opts, format, text) {
 
 // if you want to output a technical format (e.g. RFC 2822), this helper
 // helps handle the details
-function toTechFormat(dt, format) {
+function toTechFormat(dt, format, allowZ = true) {
   return dt.isValid
     ? Formatter.create(Locale.create("en-US"), {
-        allowZ: true,
+        allowZ,
         forceSimple: true
       }).formatDateTimeFromString(dt, format)
     : null;
@@ -197,13 +197,14 @@ function toTechTimeFormat(
     suppressMilliseconds = false,
     includeOffset,
     includeZone = false,
-    spaceZone = false
+    spaceZone = false,
+    format = "extended"
   }
 ) {
-  let fmt = "HH:mm";
+  let fmt = format === "basic" ? "HHmm" : "HH:mm";
 
   if (!suppressSeconds || dt.second !== 0 || dt.millisecond !== 0) {
-    fmt += ":ss";
+    fmt += format === "basic" ? "ss" : ":ss";
     if (!suppressMilliseconds || dt.millisecond !== 0) {
       fmt += ".SSS";
     }
@@ -216,7 +217,7 @@ function toTechTimeFormat(
   if (includeZone) {
     fmt += "z";
   } else if (includeOffset) {
-    fmt += "ZZ";
+    fmt += format === "basic" ? "ZZZ" : "ZZ";
   }
 
   return toTechFormat(dt, fmt);
@@ -1507,9 +1508,11 @@ export default class DateTime {
    * @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0
    * @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.local().toISO() //=> '2017-04-22T20:47:05.335-04:00'
    * @example DateTime.local().toISO({ includeOffset: false }) //=> '2017-04-22T20:47:05.335'
+   * @example DateTime.local().toISO({ format: 'basic' }) //=> '20170422T204705.335-0400'
    * @return {string}
    */
   toISO(opts = {}) {
@@ -1517,21 +1520,24 @@ export default class DateTime {
       return null;
     }
 
-    return `${this.toISODate()}T${this.toISOTime(opts)}`;
+    return `${this.toISODate(opts)}T${this.toISOTime(opts)}`;
   }
 
   /**
    * Returns an ISO 8601-compliant string representation of this DateTime's date component
+   * @param {Object} opts - options
+   * @param {string} [opts.format='extended'] - choose between the basic and extended format
    * @example DateTime.utc(1982, 5, 25).toISODate() //=> '1982-05-25'
+   * @example DateTime.utc(1982, 5, 25).toISODate({ format: 'basic' }) //=> '19820525'
    * @return {string}
    */
-  toISODate() {
-    let format = "yyyy-MM-dd";
+  toISODate({ format = "extended" } = {}) {
+    let fmt = format === "basic" ? "yyyyMMdd" : "yyyy-MM-dd";
     if (this.year > 9999) {
-      format = "+" + format;
+      fmt = "+" + fmt;
     }
 
-    return toTechFormat(this, format);
+    return toTechFormat(this, fmt);
   }
 
   /**
@@ -1549,15 +1555,23 @@ export default class DateTime {
    * @param {boolean} [opts.suppressMilliseconds=false] - exclude milliseconds from the format if they're 0
    * @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().hour(7).minute(34).toISOTime() //=> '07:34:19.361Z'
    * @example DateTime.utc().hour(7).minute(34).toISOTime({ suppressSeconds: true }) //=> '07:34Z'
+   * @example DateTime.utc().hour(7).minute(34).toISOTime({ format: 'basic' }) //=> '073419.361Z'
    * @return {string}
    */
-  toISOTime({ suppressMilliseconds = false, suppressSeconds = false, includeOffset = true } = {}) {
+  toISOTime({
+    suppressMilliseconds = false,
+    suppressSeconds = false,
+    includeOffset = true,
+    format = "extended"
+  } = {}) {
     return toTechTimeFormat(this, {
       suppressSeconds,
       suppressMilliseconds,
-      includeOffset
+      includeOffset,
+      format
     });
   }
 
@@ -1568,7 +1582,7 @@ export default class DateTime {
    * @return {string}
    */
   toRFC2822() {
-    return toTechFormat(this, "EEE, dd LLL yyyy HH:mm:ss ZZZ");
+    return toTechFormat(this, "EEE, dd LLL yyyy HH:mm:ss ZZZ", false);
   }
 
   /**
diff --git a/src/impl/formatter.js b/src/impl/formatter.js
index 76e44490..98872f94 100644
--- a/src/impl/formatter.js
+++ b/src/impl/formatter.js
@@ -203,7 +203,7 @@ export default class Formatter {
             return formatOffset({ format: "short", allowZ: this.opts.allowZ });
           case "ZZZ":
             // like +0600
-            return formatOffset({ format: "techie", allowZ: false });
+            return formatOffset({ format: "techie", allowZ: this.opts.allowZ });
           case "ZZZZ":
             // like EST
             return dt.zone.offsetName(dt.ts, { format: "short", locale: this.loc.locale });


Test patch:
diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js
index cafaa551..5051e02e 100644
--- a/test/datetime/format.test.js
+++ b/test/datetime/format.test.js
@@ -50,6 +50,10 @@ test("DateTime#toISODate() is local to the zone", () => {
   expect(dt.toUTC(-10 * 60).toISODate()).toBe("1982-05-24");
 });
 
+test("DateTime#toISODate() can output the basic format", () => {
+  expect(dt.toISODate({ format: "basic" })).toBe("19820525");
+});
+
 test("DateTime#toISODate() returns null for invalid DateTimes", () => {
   expect(invalid.toISODate()).toBe(null);
 });
@@ -117,6 +121,10 @@ test("DateTime#toISOTime() can omit the offset", () => {
   expect(dt.toISOTime({ includeOffset: false })).toBe("09:23:54.123");
 });
 
+test("DateTime#toISOTime() can output the basic format", () => {
+  expect(dt.toISOTime({ format: "basic" })).toBe("092354.123Z");
+});
+
 test("DateTime#toISOTime() returns null for invalid DateTimes", () => {
   expect(invalid.toISOTime()).toBe(null);
 });