From 5578acd4c401246202e95244cf2832e928c4cd5f Mon Sep 17 00:00:00 2001 From: Clockwork Date: Thu, 30 Nov 2023 08:52:15 +0200 Subject: [PATCH] fix: Ensure RFC dates between years 0001 and 0099 are parsed correctly --- packages/encoding/src/rfc3339.spec.ts | 21 +++++++++++++++++++++ packages/encoding/src/rfc3339.ts | 7 ++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/encoding/src/rfc3339.spec.ts b/packages/encoding/src/rfc3339.spec.ts index 227f1e32..eef19fc1 100644 --- a/packages/encoding/src/rfc3339.spec.ts +++ b/packages/encoding/src/rfc3339.spec.ts @@ -58,6 +58,27 @@ describe("RFC3339", () => { expect(fromRfc3339("2002-10-02T11:12:13.999Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999))); }); + it("parses dates between years 0 and 99 with and without timezones", () => { + expect(fromRfc3339("0001-01-01T00:00:00.000Z")).toEqual( + new Date(new Date(Date.UTC(1, 0, 1, 0, 0, 0, 0)).setUTCFullYear(1)), + ); + expect(fromRfc3339("0000-01-01T00:00:00.000Z")).toEqual( + new Date(new Date(Date.UTC(0, 0, 1, 0, 0, 0, 0)).setUTCFullYear(0)), + ); + expect(fromRfc3339("1999-01-01T00:00:00.000Z")).toEqual( + new Date(new Date(Date.UTC(1999, 0, 1, 0, 0, 0, 0)).setUTCFullYear(1999)), + ); + expect(fromRfc3339("0099-01-01T00:00:00.000Z")).toEqual( + new Date(new Date(Date.UTC(99, 0, 1, 0, 0, 0, 0)).setUTCFullYear(99)), + ); + expect(fromRfc3339("0010-01-01T00:00:00+01:00")).toEqual( + new Date(new Date(Date.UTC(9, 11, 31, 23, 0, 0, 0)).setUTCFullYear(9)), + ); + expect(fromRfc3339("0100-01-01T00:00:00+01:00")).toEqual( + new Date(new Date(Date.UTC(99, 11, 31, 23, 0, 0, 0)).setUTCFullYear(99)), + ); + }); + it("parses dates with low precision fractional seconds", () => { // 1 digit expect(fromRfc3339("2002-10-02T11:12:13.0Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0))); diff --git a/packages/encoding/src/rfc3339.ts b/packages/encoding/src/rfc3339.ts index a385ca1c..a9165fcd 100644 --- a/packages/encoding/src/rfc3339.ts +++ b/packages/encoding/src/rfc3339.ts @@ -40,7 +40,12 @@ export function fromRfc3339(str: string): Date { const tzOffset = tzOffsetSign * (tzOffsetHours * 60 + tzOffsetMinutes) * 60; // seconds - const timestamp = Date.UTC(year, month - 1, day, hour, minute, second, milliSeconds) - tzOffset * 1000; + let timestamp = Date.UTC(year, month - 1, day, hour, minute, second, milliSeconds); + + // Date.UTC maps year 0-99 to 1900-1999. Ensure the correct year is set and THEN apply the offset + const date = new Date(timestamp); + timestamp = date.setUTCFullYear(year) - tzOffset * 1000; + return new Date(timestamp); }