Skip to content

Commit ea38376

Browse files
authored
fix: do formatting in utc to avoid dst gaps (#31)
Fixes #30
1 parent 6f53354 commit ea38376

File tree

4 files changed

+33
-12
lines changed

4 files changed

+33
-12
lines changed

Diff for: src/__tests__/format.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,14 @@ describe("format with a timezone", () => {
212212
})
213213
).toBe("2024-02-16T12:00:00+0100")
214214
})
215+
216+
it("can format times during device DST gaps", () => {
217+
expect(
218+
format({
219+
date: new Date("2024-03-10T02:30:00Z"),
220+
format: "HH:mm:ssZ",
221+
tz: "UTC",
222+
})
223+
).toBe("02:30:00+0000")
224+
})
215225
})

Diff for: src/common.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export function fill(
169169
return `0${value}`
170170
}
171171
if (partName === "dayPeriod") {
172-
const p = ap(d.getHours() < 12 ? "am" : "pm", locale)
172+
const p = ap(d.getUTCHours() < 12 ? "am" : "pm", locale)
173173
return token === "A" ? p.toUpperCase() : p.toLowerCase()
174174
}
175175
if (partName === "timeZoneName") {
@@ -210,14 +210,17 @@ function createPartMap(
210210
valueParts.push(
211211
...new Intl.DateTimeFormat(
212212
preciseLocale,
213-
requestedParts.reduce((options, part) => {
214-
if (part.partName === "literal") return options
215-
// Side effect! Genitive parts get shoved into a separate array.
216-
if (genitive && genitiveTokens.includes(part.token)) {
217-
genitiveParts.push(part)
218-
}
219-
return Object.assign(options, part.option)
220-
}, {} as Intl.DateTimeFormatOptions),
213+
requestedParts.reduce(
214+
(options, part) => {
215+
if (part.partName === "literal") return options
216+
// Side effect! Genitive parts get shoved into a separate array.
217+
if (genitive && genitiveTokens.includes(part.token)) {
218+
genitiveParts.push(part)
219+
}
220+
return Object.assign(options, part.option)
221+
},
222+
{ timeZone: "UTC" } as Intl.DateTimeFormatOptions
223+
)
221224
)
222225
.formatToParts(d)
223226
.map(normStr),
@@ -229,13 +232,15 @@ function createPartMap(
229232
case "MMMM":
230233
formattedParts = new Intl.DateTimeFormat(preciseLocale, {
231234
dateStyle: "long",
235+
timeZone: "UTC",
232236
})
233237
.formatToParts(d)
234238
.map(normStr)
235239
break
236240
case "MMM":
237241
formattedParts = new Intl.DateTimeFormat(preciseLocale, {
238242
dateStyle: "medium",
243+
timeZone: "UTC",
239244
})
240245
.formatToParts(d)
241246
.map(normStr)

Diff for: src/format.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { DateInput, Format, FormatOptions, Part } from "./types"
55
import { offset } from "./offset"
66
import { removeOffset } from "./removeOffset"
77
import { deviceLocale } from "./deviceLocale"
8+
import { deviceTZ } from "./deviceTZ"
89

910
/**
1011
* Produce a formatted string. Available strings:
@@ -71,12 +72,16 @@ export function format(
7172
if (format === "ISO8601") return date(inputDateOrOptions).toISOString()
7273

7374
if (tz) {
74-
// If a timezone is provided, we need to apply the offset to the date.
75+
forceOffset = offset(inputDateOrOptions, "utc", tz)
76+
}
77+
78+
// We need to apply an offset to the date so that it can be formatted as UTC.
79+
tz ??= deviceTZ()
80+
if (tz.toLowerCase() !== "utc") {
7581
inputDateOrOptions = removeOffset(
7682
inputDateOrOptions,
77-
offset(inputDateOrOptions, tz)
83+
offset(inputDateOrOptions, tz, "utc")
7884
)
79-
forceOffset = offset(inputDateOrOptions, "utc", tz)
8085
}
8186

8287
if (!locale || locale === "device") {

Diff for: src/parts.ts

+1
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ function partStyle(
257257
if (style === "long" || style === "short") {
258258
const genitiveFormattedParts = new Intl.DateTimeFormat(locale, {
259259
dateStyle: style === "short" ? "medium" : "long",
260+
timeZone: "UTC",
260261
})
261262
.formatToParts(date)
262263
.map(normStr)

0 commit comments

Comments
 (0)