diff --git a/packages/tendermint-rpc/src/dates.spec.ts b/packages/tendermint-rpc/src/dates.spec.ts index dfdf0b94..25862012 100644 --- a/packages/tendermint-rpc/src/dates.spec.ts +++ b/packages/tendermint-rpc/src/dates.spec.ts @@ -1,10 +1,20 @@ import { ReadonlyDate } from "readonly-date"; -import { DateTime, DateWithNanoseconds, fromRfc3339WithNanoseconds, toRfc3339WithNanoseconds } from "./dates"; +import { + DateTime, + DateWithNanoseconds, + fromRfc3339WithNanoseconds, + fromSeconds, + toRfc3339WithNanoseconds, + toSeconds, +} from "./dates"; describe("dates", () => { describe("fromRfc3339WithNanoseconds", () => { it("works", () => { + expect(fromRfc3339WithNanoseconds("2020-12-15T10:57:26Z").nanoseconds).toEqual(0); + expect(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.7Z").nanoseconds).toEqual(0); + expect(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.77Z").nanoseconds).toEqual(0); expect(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.778Z").nanoseconds).toEqual(0); expect(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.7789Z").nanoseconds).toEqual(900000); expect(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.77809Z").nanoseconds).toEqual(90000); @@ -52,6 +62,107 @@ describe("dates", () => { }); }); + describe("fromSeconds", () => { + it("works", () => { + { + const date = fromSeconds(1608029846); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26Z")); + } + { + const date = fromSeconds(1608029846, 0); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26Z")); + } + { + const date = fromSeconds(1608029846, 1); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.000000001Z")); + } + { + const date = fromSeconds(1608029846, 10); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.000000010Z")); + } + { + const date = fromSeconds(1608029846, 100); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.000000100Z")); + } + { + const date = fromSeconds(1608029846, 1000); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.000001000Z")); + } + { + const date = fromSeconds(1608029846, 10000); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.000010000Z")); + } + { + const date = fromSeconds(1608029846, 100000); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.000100000Z")); + } + { + const date = fromSeconds(1608029846, 1000000); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.001000000Z")); + } + { + const date = fromSeconds(1608029846, 10000000); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.010000000Z")); + } + { + const date = fromSeconds(1608029846, 100000000); + expect(date).toEqual(fromRfc3339WithNanoseconds("2020-12-15T10:57:26.100000000Z")); + } + }); + + it("throws for nanos out of range", () => { + expect(() => fromSeconds(1608029846, 1000000000)).toThrow(); + expect(() => fromSeconds(1608029846, -1)).toThrow(); + expect(() => fromSeconds(1608029846, 1.2)).toThrow(); + expect(() => fromSeconds(1608029846, NaN)).toThrow(); + }); + }); + + describe("toSeconds", () => { + it("works", () => { + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 0 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.7Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 700000000 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.77Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 770000000 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.778Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 778000000 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.7789Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 778900000 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.77809Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 778090000 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.778009Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 778009000 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.7780009Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 778000900 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.77800009Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 778000090 }); + } + { + const date = fromRfc3339WithNanoseconds("2020-12-15T10:57:26.778000009Z"); + expect(toSeconds(date)).toEqual({ seconds: 1608029846, nanos: 778000009 }); + } + }); + }); + describe("DateTime", () => { it("decodes a string", () => { expect(DateTime.decode("2020-12-15T10:57:26.778Z").nanoseconds).toEqual(0); diff --git a/packages/tendermint-rpc/src/dates.ts b/packages/tendermint-rpc/src/dates.ts index 4a15ccfc..fd75d7ea 100644 --- a/packages/tendermint-rpc/src/dates.ts +++ b/packages/tendermint-rpc/src/dates.ts @@ -1,4 +1,5 @@ import { fromRfc3339 } from "@cosmjs/encoding"; +import { Uint32 } from "@cosmjs/math"; import { ReadonlyDate } from "readonly-date"; export interface ReadonlyDateWithNanoseconds extends ReadonlyDate { @@ -25,6 +26,30 @@ export function toRfc3339WithNanoseconds(dateTime: ReadonlyDateWithNanoseconds): return `${millisecondIso.slice(0, -1)}${nanoseconds.padStart(6, "0")}Z`; } +export function fromSeconds(seconds: number, nanos = 0): DateWithNanoseconds { + const checkedNanos = new Uint32(nanos).toNumber(); + if (checkedNanos > 999_999_999) { + throw new Error("Nano seconds must not exceed 999999999"); + } + const out: DateWithNanoseconds = new Date(seconds * 1000 + Math.floor(checkedNanos / 1000000)); + out.nanoseconds = checkedNanos % 1000000; + return out; +} + +/** + * Calculates the UNIX timestamp in seconds as well as the nanoseconds after the given second. + * + * This is useful when dealing with external systems like the protobuf type + * [.google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) + * or any other system that does not use millisecond precision. + */ +export function toSeconds(date: ReadonlyDateWithNanoseconds): { seconds: number; nanos: number } { + return { + seconds: Math.floor(date.getTime() / 1000), + nanos: (date.getTime() % 1000) * 1000000 + (date.nanoseconds ?? 0), + }; +} + /** @deprecated Use fromRfc3339WithNanoseconds/toRfc3339WithNanoseconds instead */ export class DateTime { /** @deprecated Use fromRfc3339WithNanoseconds instead */ diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts index c6584a41..1d762ea2 100644 --- a/packages/tendermint-rpc/src/index.ts +++ b/packages/tendermint-rpc/src/index.ts @@ -5,7 +5,9 @@ export { DateTime, ReadonlyDateWithNanoseconds, fromRfc3339WithNanoseconds, + fromSeconds, toRfc3339WithNanoseconds, + toSeconds, } from "./dates"; export { AbciInfoRequest,