Repository: moment/luxon
Pull request #593 (2019-10-08 19:55:15)

Use "numeric" instead of "2-digit" in formatting presets


Patch:
diff --git a/changelog.md b/changelog.md
index 3574d7cf..7cba7a16 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,15 +1,15 @@
 # Changelog
 
-# 1.19.3
+## 1.19.3
 
  * Cache offset values
  * Fix handling of negative sub 1-hour offsets
 
-# 1.19.2
+## 1.19.2
 
  * Speculative fix for Node 6
 
-# 1.19.1
+## 1.19.1
 
  * Fix Intl.DateTimeFormat usage for polyfills
 
diff --git a/src/duration.js b/src/duration.js
index da3a7531..fbef70f3 100644
--- a/src/duration.js
+++ b/src/duration.js
@@ -1,4 +1,4 @@
-import { isUndefined, isNumber, normalizeObject, hasOwnProperty } from "./impl/util.js";
+import { isUndefined, isNumber, normalizeObject, hasOwnProperty, asNumber } from "./impl/util.js";
 import Locale from "./impl/locale.js";
 import Formatter from "./impl/formatter.js";
 import { parseISODuration } from "./impl/regexParser.js";
@@ -477,6 +477,22 @@ export default class Duration {
     return this.plus(dur.negate());
   }
 
+  /**
+   * Scale this Duration by the specified amount. Return a newly-constructed Duration.
+   * @param {function} fn - The function to apply to each unit. Arity is 1 or 2: the value of the unit and, optionally, the unit name. Must return a number.
+   * @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnit(x => x * 2) //=> { hours: 2, minutes: 60 }
+   * @example Duration.fromObject({ hours: 1, minutes: 30 }).mapUnit((x, u) => u === "hour" ? x * 2 : x) //=> { hours: 2, minutes: 30 }
+   * @return {Duration}
+   */
+  mapUnits(fn) {
+    if (!this.isValid) return this;
+    const result = {};
+    for (const k of Object.keys(this.values)) {
+      result[k] = asNumber(fn(this.values[k], k));
+    }
+    return clone(this, { values: result }, true);
+  }
+
   /**
    * Get the value of unit.
    * @param {string} unit - a unit such as 'minute' or 'day'
diff --git a/src/impl/formats.js b/src/impl/formats.js
index 1b17bbfc..8c66b9a3 100644
--- a/src/impl/formats.js
+++ b/src/impl/formats.js
@@ -4,8 +4,7 @@
 
 const n = "numeric",
   s = "short",
-  l = "long",
-  d2 = "2-digit";
+  l = "long";
 
 export const DATE_SHORT = {
   year: n,
@@ -34,32 +33,32 @@ export const DATE_HUGE = {
 
 export const TIME_SIMPLE = {
   hour: n,
-  minute: d2
+  minute: n
 };
 
 export const TIME_WITH_SECONDS = {
   hour: n,
-  minute: d2,
-  second: d2
+  minute: n,
+  second: n
 };
 
 export const TIME_WITH_SHORT_OFFSET = {
   hour: n,
-  minute: d2,
-  second: d2,
+  minute: n,
+  second: n,
   timeZoneName: s
 };
 
 export const TIME_WITH_LONG_OFFSET = {
   hour: n,
-  minute: d2,
-  second: d2,
+  minute: n,
+  second: n,
   timeZoneName: l
 };
 
 export const TIME_24_SIMPLE = {
   hour: n,
-  minute: d2,
+  minute: n,
   hour12: false
 };
 
@@ -68,8 +67,8 @@ export const TIME_24_SIMPLE = {
  */
 export const TIME_24_WITH_SECONDS = {
   hour: n,
-  minute: d2,
-  second: d2,
+  minute: n,
+  second: n,
   hour12: false
 };
 
@@ -78,8 +77,8 @@ export const TIME_24_WITH_SECONDS = {
  */
 export const TIME_24_WITH_SHORT_OFFSET = {
   hour: n,
-  minute: d2,
-  second: d2,
+  minute: n,
+  second: n,
   hour12: false,
   timeZoneName: s
 };
@@ -89,8 +88,8 @@ export const TIME_24_WITH_SHORT_OFFSET = {
  */
 export const TIME_24_WITH_LONG_OFFSET = {
   hour: n,
-  minute: d2,
-  second: d2,
+  minute: n,
+  second: n,
   hour12: false,
   timeZoneName: l
 };
@@ -103,7 +102,7 @@ export const DATETIME_SHORT = {
   month: n,
   day: n,
   hour: n,
-  minute: d2
+  minute: n
 };
 
 /**
@@ -114,8 +113,8 @@ export const DATETIME_SHORT_WITH_SECONDS = {
   month: n,
   day: n,
   hour: n,
-  minute: d2,
-  second: d2
+  minute: n,
+  second: n
 };
 
 export const DATETIME_MED = {
@@ -123,7 +122,7 @@ export const DATETIME_MED = {
   month: s,
   day: n,
   hour: n,
-  minute: d2
+  minute: n
 };
 
 export const DATETIME_MED_WITH_SECONDS = {
@@ -131,8 +130,8 @@ export const DATETIME_MED_WITH_SECONDS = {
   month: s,
   day: n,
   hour: n,
-  minute: d2,
-  second: d2
+  minute: n,
+  second: n
 };
 
 export const DATETIME_MED_WITH_WEEKDAY = {
@@ -141,7 +140,7 @@ export const DATETIME_MED_WITH_WEEKDAY = {
   day: n,
   weekday: s,
   hour: n,
-  minute: d2
+  minute: n
 };
 
 export const DATETIME_FULL = {
@@ -149,7 +148,7 @@ export const DATETIME_FULL = {
   month: l,
   day: n,
   hour: n,
-  minute: d2,
+  minute: n,
   timeZoneName: s
 };
 
@@ -158,8 +157,8 @@ export const DATETIME_FULL_WITH_SECONDS = {
   month: l,
   day: n,
   hour: n,
-  minute: d2,
-  second: d2,
+  minute: n,
+  second: n,
   timeZoneName: s
 };
 
@@ -169,7 +168,7 @@ export const DATETIME_HUGE = {
   day: n,
   weekday: l,
   hour: n,
-  minute: d2,
+  minute: n,
   timeZoneName: l
 };
 
@@ -179,7 +178,7 @@ export const DATETIME_HUGE_WITH_SECONDS = {
   day: n,
   weekday: l,
   hour: n,
-  minute: d2,
-  second: d2,
+  minute: n,
+  second: n,
   timeZoneName: l
 };
diff --git a/src/impl/util.js b/src/impl/util.js
index 824718c4..19d0c1e7 100644
--- a/src/impl/util.js
+++ b/src/impl/util.js
@@ -242,7 +242,7 @@ export function signedOffset(offHourStr, offMinuteStr) {
 
 // COERCION
 
-function asNumber(value) {
+export function asNumber(value) {
   const numericValue = Number(value);
   if (typeof value === "boolean" || value === "" || Number.isNaN(numericValue))
     throw new InvalidArgumentError(`Invalid unit value ${value}`);


Test patch:
diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js
index f1c448a7..cafaa551 100644
--- a/test/datetime/format.test.js
+++ b/test/datetime/format.test.js
@@ -288,6 +288,19 @@ test("DateTime#toLocaleString() does the best it can with a fixed-offset zone wh
   );
 });
 
+test("DateTime#toLocaleString uses locale-appropriate time formats", () => {
+  expect(dt.reconfigure({ locale: "en-US" }).toLocaleString(DateTime.TIME_SIMPLE)).toBe("9:23 AM");
+  expect(dt.reconfigure({ locale: "en-US" }).toLocaleString(DateTime.TIME_24_SIMPLE)).toBe("09:23");
+
+  // France has 24-hour time by default
+  expect(dt.reconfigure({ locale: "fr" }).toLocaleString(DateTime.TIME_SIMPLE)).toBe("09:23");
+  expect(dt.reconfigure({ locale: "fr" }).toLocaleString(DateTime.TIME_24_SIMPLE)).toBe("09:23");
+
+  // For whatever reason, Spain doesn't prefix with "0"
+  expect(dt.reconfigure({ locale: "es" }).toLocaleString(DateTime.TIME_SIMPLE)).toBe("9:23");
+  expect(dt.reconfigure({ locale: "es" }).toLocaleString(DateTime.TIME_24_SIMPLE)).toBe("9:23");
+});
+
 //------
 // #resolvedLocaleOpts()
 //------
diff --git a/test/duration/math.test.js b/test/duration/math.test.js
index 496898f8..457a6237 100644
--- a/test/duration/math.test.js
+++ b/test/duration/math.test.js
@@ -137,3 +137,38 @@ test("Duration#negate preserves conversionAccuracy", () => {
     result = dur.negate();
   expect(result.conversionAccuracy).toBe("longterm");
 });
+
+//------
+// #mapUnits
+//------
+
+test("Duration#units can multiply durations", () => {
+  const dur = Duration.fromObject({ hours: 1, minutes: 2, seconds: -3, milliseconds: -4 }),
+    result = dur.mapUnits(x => x * 5);
+
+  expect(result.hours).toBe(5);
+  expect(result.minutes).toBe(10);
+  expect(result.seconds).toBe(-15);
+  expect(result.milliseconds).toBe(-20);
+});
+
+test("Duration#units can take the unit into account", () => {
+  const dur = Duration.fromObject({ hours: 1, minutes: 2, seconds: -3, milliseconds: -4 }),
+    result = dur.mapUnits((x, u) => x * (u === "milliseconds" ? 2 : 5));
+
+  expect(result.hours).toBe(5);
+  expect(result.minutes).toBe(10);
+  expect(result.seconds).toBe(-15);
+  expect(result.milliseconds).toBe(-8);
+});
+
+test("Duration#mapUnits maintains invalidity", () => {
+  const dur = Duration.invalid("because").mapUnits(x => x * 5);
+  expect(dur.isValid).toBe(false);
+  expect(dur.invalidReason).toBe("because");
+});
+
+test("Duration#mapUnits requires that fn return a number", () => {
+  const dur = Duration.fromObject({ hours: 1, minutes: 2, seconds: -3, milliseconds: -4 });
+  expect(() => dur.mapUnits(() => "hello?")).toThrow();
+});