Repository: denoland/deno
Pull request: fix(unstable/temporal): respect locale in `Duration.prototype.toLocaleString`


Changed files:
filename: runtime/js/99_main.js
patch: @@ -37,6 +37,7 @@ const {
   ObjectHasOwn,
   ObjectKeys,
   ObjectGetOwnPropertyDescriptor,
+  ObjectGetOwnPropertyDescriptors,
   ObjectPrototypeIsPrototypeOf,
   ObjectSetPrototypeOf,
   PromisePrototypeThen,
@@ -45,6 +46,7 @@ const {
   Symbol,
   SymbolIterator,
   TypeError,
+  uncurryThis,
 } = primordials;
 const {
   isNativeError,
@@ -459,6 +461,51 @@ function exposeUnstableFeaturesForWindowOrWorkerGlobalScope(unstableFeatures) {
   }
 }
 
+function shimTemporalDurationToLocaleString() {
+  const DurationFormat = Intl.DurationFormat;
+  if (!DurationFormat) {
+    // Intl.DurationFormat can be disabled with --v8-flags=--no-harmony-intl-duration-format
+    return;
+  }
+  const DurationFormatPrototype = DurationFormat.prototype;
+  const formatDuration = uncurryThis(DurationFormatPrototype.format);
+
+  const Duration = Temporal.Duration;
+  const DurationPrototype = Duration.prototype;
+  const desc = ObjectGetOwnPropertyDescriptors(DurationPrototype);
+  const assertDuration = uncurryThis(desc.toLocaleString.value);
+  const getYears = uncurryThis(desc.years.get);
+  const getMonths = uncurryThis(desc.months.get);
+  const getWeeks = uncurryThis(desc.weeks.get);
+  const getDays = uncurryThis(desc.days.get);
+  const getHours = uncurryThis(desc.hours.get);
+  const getMinutes = uncurryThis(desc.minutes.get);
+  const getSeconds = uncurryThis(desc.seconds.get);
+  const getMilliseconds = uncurryThis(desc.milliseconds.get);
+  const getMicroseconds = uncurryThis(desc.microseconds.get);
+  const getNanoseconds = uncurryThis(desc.nanoseconds.get);
+
+  ObjectAssign(DurationPrototype, {
+    toLocaleString(locales = undefined, options) {
+      assertDuration(this);
+      const durationFormat = new DurationFormat(locales, options);
+      const duration = {
+        years: getYears(this),
+        months: getMonths(this),
+        weeks: getWeeks(this),
+        days: getDays(this),
+        hours: getHours(this),
+        minutes: getMinutes(this),
+        seconds: getSeconds(this),
+        milliseconds: getMilliseconds(this),
+        microseconds: getMicroseconds(this),
+        nanoseconds: getNanoseconds(this),
+      };
+      return formatDuration(durationFormat, duration);
+    },
+  });
+}
+
 // NOTE(bartlomieju): remove all the ops that have already been imported using
 // "virtual op module" (`ext:core/ops`).
 const NOT_IMPORTED_OPS = [
@@ -821,6 +868,12 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
         });
         delete globalThis.Temporal.Now.timeZone;
       }
+
+      // deno-lint-ignore prefer-primordials
+      if (new Temporal.Duration().toLocaleString("en-US") !== "PT0S") {
+        throw "V8 supports Temporal.Duration.prototype.toLocaleString now, no need to shim it";
+      }
+      shimTemporalDurationToLocaleString();
     }
 
     // Setup `Deno` global - we're actually overriding already existing global
@@ -1024,6 +1077,8 @@ function bootstrapWorkerRuntime(
         });
         delete globalThis.Temporal.Now.timeZone;
       }
+
+      shimTemporalDurationToLocaleString();
     }
 
     // Setup `Deno` global - we're actually overriding already existing global

filename: tests/specs/run/unstable_temporal_api_patch/main.out
patch: @@ -5,3 +5,4 @@ iso8601
 UTC
 undefined
 undefined
+1 day, 6 hr, 30 min

filename: tests/specs/run/unstable_temporal_api_patch/main.ts
patch: @@ -9,3 +9,6 @@ console.log(zoned.timeZoneId);
 console.log(zoned.calendar);
 // @ts-expect-error: undefined check
 console.log(zoned.timeZone);
+
+const duration = Temporal.Duration.from("P1DT6H30M");
+console.log(duration.toLocaleString("en-US"));