diff --git a/src/impl/locale.js b/src/impl/locale.js index eb8c2bea8..06bdaeb28 100644 --- a/src/impl/locale.js +++ b/src/impl/locale.js @@ -197,9 +197,13 @@ class PolyNumberFormatter { class PolyDateFormatter { constructor(dt, intl, opts) { this.opts = opts; + this.originalZone = undefined; let z = undefined; - if (dt.zone.isUniversal) { + if (this.opts.timeZone) { + // Don't apply any workarounds if a timeZone is explicitly provided in opts + this.dt = dt; + } else if (dt.zone.isUniversal) { // UTC-8 or Etc/UTC-8 are not part of tzdata, only Etc/GMT+8 and the like. // That is why fixed-offset TZ is set to that unless it is: // 1. Representing offset 0 when UTC is used to maintain previous behavior and does not become GMT. @@ -212,25 +216,23 @@ class PolyDateFormatter { z = offsetZ; this.dt = dt; } else { - // Not all fixed-offset zones like Etc/+4:30 are present in tzdata. - // So we have to make do. Two cases: - // 1. The format options tell us to show the zone. We can't do that, so the best - // we can do is format the date in UTC. - // 2. The format options don't tell us to show the zone. Then we can adjust them - // the time and tell the formatter to show it to us in UTC, so that the time is right - // and the bad zone doesn't show up. + // Not all fixed-offset zones like Etc/+4:30 are present in tzdata so + // we manually apply the offset and substitute the zone as needed. z = "UTC"; - if (opts.timeZoneName) { - this.dt = dt; - } else { - this.dt = dt.offset === 0 ? dt : DateTime.fromMillis(dt.ts + dt.offset * 60 * 1000); - } + this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset }); + this.originalZone = dt.zone; } } else if (dt.zone.type === "system") { this.dt = dt; - } else { + } else if (dt.zone.type === "iana") { this.dt = dt; z = dt.zone.name; + } else { + // Custom zones can have any offset / offsetName so we just manually + // apply the offset and substitute the offsetName as needed. + z = "UTC"; + this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset }); + this.originalZone = dt.zone; } const intlOpts = { ...this.opts }; @@ -239,11 +241,35 @@ class PolyDateFormatter { } format() { + if (this.originalZone) { + // If we have to substitute in the actual zone name, we have to use + // formatToParts so that the timezone can be replaced. + return this.formatToParts() + .map(({ value }) => value) + .join(""); + } return this.dtf.format(this.dt.toJSDate()); } formatToParts() { - return this.dtf.formatToParts(this.dt.toJSDate()); + const parts = this.dtf.formatToParts(this.dt.toJSDate()); + if (this.originalZone) { + return parts.map((part) => { + if (part.type === "timeZoneName") { + const offsetName = this.originalZone.offsetName(this.dt.ts, { + locale: this.dt.locale, + format: this.opts.timeZoneName, + }); + return { + ...part, + value: offsetName, + }; + } else { + return part; + } + }); + } + return parts; } resolvedOptions() { diff --git a/test/datetime/format.test.js b/test/datetime/format.test.js index 23dcfd77a..6ea3361e8 100644 --- a/test/datetime/format.test.js +++ b/test/datetime/format.test.js @@ -398,7 +398,13 @@ test("DateTime#toLocaleString() shows things with UTC if fixed-offset zone with test("DateTime#toLocaleString() does the best it can with unsupported fixed-offset zone when showing the zone", () => { expect(dt.setZone("UTC+4:30").toLocaleString(DateTime.DATETIME_FULL)).toBe( - "May 25, 1982 at 9:23 AM UTC" + "May 25, 1982 at 1:53 PM UTC+4:30" + ); +}); + +test("DateTime#toLocaleString() does the best it can with unsupported fixed-offset zone with timeStyle full", () => { + expect(dt.setZone("UTC+4:30").toLocaleString({ timeStyle: "full" })).toBe( + "1:53:54 PM UTC+4:30" ); });