From 5578acd4c401246202e95244cf2832e928c4cd5f Mon Sep 17 00:00:00 2001 From: Clockwork Date: Thu, 30 Nov 2023 08:52:15 +0200 Subject: [PATCH 1/5] 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 227f1e328e..eef19fc180 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 a385ca1c0a..a9165fcd90 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); } From b0d5e0c2c828339a5a406946494cf256c968211d Mon Sep 17 00:00:00 2001 From: Clockwork Date: Thu, 30 Nov 2023 13:44:27 +0200 Subject: [PATCH 2/5] fix: no special treatment for tendermint/comet zero time --- .../tendermint-rpc/src/comet38/adaptor/responses.ts | 13 +------------ .../src/tendermint34/adaptor/responses.ts | 13 +------------ .../src/tendermint37/adaptor/responses.ts | 13 +------------ 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/packages/tendermint-rpc/src/comet38/adaptor/responses.ts b/packages/tendermint-rpc/src/comet38/adaptor/responses.ts index 6bec2343b7..0f6e0ed951 100644 --- a/packages/tendermint-rpc/src/comet38/adaptor/responses.ts +++ b/packages/tendermint-rpc/src/comet38/adaptor/responses.ts @@ -467,22 +467,11 @@ type RpcSignature = { readonly signature: string | null; }; -/** - * In some cases a timestamp is optional and set to the value 0 in Go. - * This can lead to strings like "0001-01-01T00:00:00Z" (see https://github.com/cosmos/cosmjs/issues/704#issuecomment-797122415). - * This decoder tries to clean up such encoding from the API and turn them - * into undefined values. - */ -function decodeOptionalTime(timestamp: string): DateWithNanoseconds | undefined { - const nonZeroTime = timestamp && !timestamp.startsWith("0001-01-01"); - return nonZeroTime ? fromRfc3339WithNanoseconds(timestamp) : undefined; -} - function decodeCommitSignature(data: RpcSignature): CommitSignature { return { blockIdFlag: decodeBlockIdFlag(data.block_id_flag), validatorAddress: data.validator_address ? fromHex(data.validator_address) : undefined, - timestamp: decodeOptionalTime(data.timestamp), + timestamp: data.timestamp ? fromRfc3339WithNanoseconds(data.timestamp) : undefined, signature: data.signature ? fromBase64(data.signature) : undefined, }; } diff --git a/packages/tendermint-rpc/src/tendermint34/adaptor/responses.ts b/packages/tendermint-rpc/src/tendermint34/adaptor/responses.ts index b3546dd914..3809b5011a 100644 --- a/packages/tendermint-rpc/src/tendermint34/adaptor/responses.ts +++ b/packages/tendermint-rpc/src/tendermint34/adaptor/responses.ts @@ -464,22 +464,11 @@ type RpcSignature = { readonly signature: string | null; }; -/** - * In some cases a timestamp is optional and set to the value 0 in Go. - * This can lead to strings like "0001-01-01T00:00:00Z" (see https://github.com/cosmos/cosmjs/issues/704#issuecomment-797122415). - * This decoder tries to clean up such encoding from the API and turn them - * into undefined values. - */ -function decodeOptionalTime(timestamp: string): DateWithNanoseconds | undefined { - const nonZeroTime = timestamp && !timestamp.startsWith("0001-01-01"); - return nonZeroTime ? fromRfc3339WithNanoseconds(timestamp) : undefined; -} - function decodeCommitSignature(data: RpcSignature): CommitSignature { return { blockIdFlag: decodeBlockIdFlag(data.block_id_flag), validatorAddress: data.validator_address ? fromHex(data.validator_address) : undefined, - timestamp: decodeOptionalTime(data.timestamp), + timestamp: data.timestamp ? fromRfc3339WithNanoseconds(data.timestamp) : undefined, signature: data.signature ? fromBase64(data.signature) : undefined, }; } diff --git a/packages/tendermint-rpc/src/tendermint37/adaptor/responses.ts b/packages/tendermint-rpc/src/tendermint37/adaptor/responses.ts index f0614ed49f..7287aacfc3 100644 --- a/packages/tendermint-rpc/src/tendermint37/adaptor/responses.ts +++ b/packages/tendermint-rpc/src/tendermint37/adaptor/responses.ts @@ -465,22 +465,11 @@ type RpcSignature = { readonly signature: string | null; }; -/** - * In some cases a timestamp is optional and set to the value 0 in Go. - * This can lead to strings like "0001-01-01T00:00:00Z" (see https://github.com/cosmos/cosmjs/issues/704#issuecomment-797122415). - * This decoder tries to clean up such encoding from the API and turn them - * into undefined values. - */ -function decodeOptionalTime(timestamp: string): DateWithNanoseconds | undefined { - const nonZeroTime = timestamp && !timestamp.startsWith("0001-01-01"); - return nonZeroTime ? fromRfc3339WithNanoseconds(timestamp) : undefined; -} - function decodeCommitSignature(data: RpcSignature): CommitSignature { return { blockIdFlag: decodeBlockIdFlag(data.block_id_flag), validatorAddress: data.validator_address ? fromHex(data.validator_address) : undefined, - timestamp: decodeOptionalTime(data.timestamp), + timestamp: data.timestamp ? fromRfc3339WithNanoseconds(data.timestamp) : undefined, signature: data.signature ? fromBase64(data.signature) : undefined, }; } From 3814d4386592568c7f1279a771cebcfa6fc64906 Mon Sep 17 00:00:00 2001 From: Clockwork Date: Thu, 30 Nov 2023 19:57:17 +0200 Subject: [PATCH 3/5] fix: clean up rfc3339 function --- packages/encoding/src/rfc3339.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/encoding/src/rfc3339.ts b/packages/encoding/src/rfc3339.ts index a9165fcd90..e3f0abab5c 100644 --- a/packages/encoding/src/rfc3339.ts +++ b/packages/encoding/src/rfc3339.ts @@ -40,13 +40,13 @@ export function fromRfc3339(str: string): Date { const tzOffset = tzOffsetSign * (tzOffsetHours * 60 + tzOffsetMinutes) * 60; // seconds - let timestamp = Date.UTC(year, month - 1, day, hour, minute, second, milliSeconds); + const date = new Date(); + date.setUTCFullYear(year); + date.setUTCMonth(month - 1); + date.setUTCDate(day); + date.setUTCHours(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); + return new Date(date.getTime() - tzOffset * 1000); } export function toRfc3339(date: Date | ReadonlyDate): string { From 8f4216be680f6973be482c9a0bcde4b2258f6c16 Mon Sep 17 00:00:00 2001 From: Clockwork Date: Thu, 30 Nov 2023 20:32:20 +0200 Subject: [PATCH 4/5] fix: simplify rfc339 function --- packages/encoding/src/rfc3339.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/encoding/src/rfc3339.ts b/packages/encoding/src/rfc3339.ts index e3f0abab5c..21a3050246 100644 --- a/packages/encoding/src/rfc3339.ts +++ b/packages/encoding/src/rfc3339.ts @@ -41,9 +41,7 @@ export function fromRfc3339(str: string): Date { const tzOffset = tzOffsetSign * (tzOffsetHours * 60 + tzOffsetMinutes) * 60; // seconds const date = new Date(); - date.setUTCFullYear(year); - date.setUTCMonth(month - 1); - date.setUTCDate(day); + date.setUTCFullYear(year, month - 1, day); date.setUTCHours(hour, minute, second, milliSeconds); return new Date(date.getTime() - tzOffset * 1000); From bc79f8eb6d146188de915d2815388edc89d636f1 Mon Sep 17 00:00:00 2001 From: Clockwork Date: Thu, 30 Nov 2023 20:37:28 +0200 Subject: [PATCH 5/5] chore: Add changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 391bc5dcf1..2a5d87f6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to ## [Unreleased] +### Fixed + +- @cosmjs/encoding: Ensure RFC dates between years 0001 and 0099 are parsed correctly. ([#1516]) +- @cosmjs/tendermint-rpc: Remove hacky dewcodeOptionalTime() from adaptros now that `time.Time` dates are parsed correctly. ([#1516]) + +[#1516]: https://github.com/cosmos/cosmjs/pull/1516 + ## [0.32.0] - 2023-11-23 ### Added