Skip to content

Commit 764d150

Browse files
authored
Fix DateTime.makeUnsafe incorrectly appending "Z" to date strings containing "GMT" (#1781)
1 parent c667dad commit 764d150

File tree

4 files changed

+36
-4
lines changed

4 files changed

+36
-4
lines changed

.changeset/fix-datetime-gmt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
Fix `DateTime.makeUnsafe` incorrectly appending "Z" to date strings containing "GMT"

packages/effect/src/internal/dateTime.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,14 @@ export const makeUnsafe = <A extends DateTime.DateTime.Input>(input: A): DateTim
232232
return fromDateUnsafe(new Date(input)) as DateTime.DateTime.PreserveZone<A>
233233
}
234234

235-
const hasZone = (input: string): boolean => /Z|[+-]\d{2}$|[+-]\d{2}:?\d{2}$|\]$/.test(input)
235+
/**
236+
* Detects whether a date string already contains timezone info.
237+
* Without a zone, `new Date("2024-01-01T12:00:00")` is parsed as local time,
238+
* so `makeUnsafe` appends "Z" to force UTC interpretation.
239+
* This check prevents appending "Z" to strings that already have a zone
240+
* (e.g. "2024-01-01T12:00:00Z", "...+05:30", "...GMT"), which would produce invalid dates.
241+
*/
242+
const hasZone = (input: string): boolean => /Z|GMT|[+-]\d{2}$|[+-]\d{2}:?\d{2}$|\]$/.test(input)
236243

237244
const minEpochMillis = -8640000000000000 + (12 * 60 * 60 * 1000)
238245
const maxEpochMillis = 8640000000000000 - (14 * 60 * 60 * 1000)

packages/effect/test/DateTime.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,12 +457,24 @@ describe("DateTime", () => {
457457

458458
describe("makeUnsafe", () => {
459459
it("treats strings without zone info as UTC", () => {
460-
let dt = DateTime.makeUnsafe("2024-01-01 01:00:00")
460+
const dt = DateTime.makeUnsafe("2024-01-01 01:00:00")
461461
strictEqual(dt.toJSON(), "2024-01-01T01:00:00.000Z")
462+
})
463+
464+
it("does not append Z to strings ending with Z", () => {
465+
const dt = DateTime.makeUnsafe("2026-01-27T17:14:06.000Z")
466+
strictEqual(dt.toJSON(), "2026-01-27T17:14:06.000Z")
467+
})
462468

463-
dt = DateTime.makeUnsafe("2020-02-01T11:17:00+1100")
469+
it("does not append Z to strings with explicit offset", () => {
470+
const dt = DateTime.makeUnsafe("2020-02-01T11:17:00+1100")
464471
strictEqual(dt.toJSON(), "2020-02-01T00:17:00.000Z")
465472
})
473+
474+
it("does not append Z to strings containing GMT", () => {
475+
const dt = DateTime.makeUnsafe("Tue, 27 Jan 2026 17:14:06 GMT")
476+
strictEqual(dt.toJSON(), "2026-01-27T17:14:06.000Z")
477+
})
466478
})
467479

468480
describe("Disambiguation", () => {

packages/effect/test/schema/SchemaGetter.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Effect, Option, Result, SchemaGetter } from "effect"
1+
import { DateTime, Effect, Option, Result, SchemaGetter } from "effect"
22
import { describe, it } from "vitest"
33
import { assertSome, deepStrictEqual } from "../utils/assert.ts"
44

@@ -21,6 +21,14 @@ describe("SchemaGetter", () => {
2121
assertSome(result, 2)
2222
})
2323

24+
it("dateTimeUtcFromInput", async () => {
25+
const decoding = makeAsserts(SchemaGetter.dateTimeUtcFromInput<string>())
26+
await decoding("2024-01-01 01:00:00", DateTime.makeUnsafe("2024-01-01T01:00:00.000Z"))
27+
await decoding("2020-02-01T11:17:00+1100", DateTime.makeUnsafe("2020-02-01T00:17:00.000Z"))
28+
// should support strings with explicit GMT zone
29+
await decoding("Tue, 27 Jan 2026 17:14:06 GMT", DateTime.makeUnsafe("2026-01-27T17:14:06.000Z"))
30+
})
31+
2432
describe("decodeFormData / encodeFormData", () => {
2533
const decoding = makeAsserts(SchemaGetter.decodeFormData())
2634
const encoding = makeAsserts(SchemaGetter.encodeFormData())

0 commit comments

Comments
 (0)