From 5b9602b70cc1f13bdd02ef59dc66efe74216e2c4 Mon Sep 17 00:00:00 2001 From: Etienne Donneger <23462475+Krow10@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:05:38 -0400 Subject: [PATCH] Fix `timestamp` query parameter parsing (#22) * Fix `timestamp` query parameter parsing The implemented behavior treats all input dates as UTC dates to be consistent with how blockchains stores their timestamp values. By default the Clickhouse DB will store the timestamps using the `YYYY-MM-DDTHH:MM:SS` format, missing timezone information. As such the new behavior will match queries passing this format whereas previously they would have been shifted by the user's UTC difference. * Update README with `timestamp` query parameter notice * Fix types for new timestamp parsing --- README.md | 5 +++++ src/queries.ts | 4 ++-- src/schemas.ts | 21 ++++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1dee874..a9cbe05 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ | GET `/{chain}/timestamp?block_number=` | Timestamp query from a block number or array (comma-separated) | GET `/{chain}/blocknum?timestamp=` | Block number query from a timestamp or array (comma-separated) +**Important note regarding `timestamp` query parameter** + +Expects **UTC** datetime or UNIX-like timestamp for matching the data in the Clickhouse DB. Passing `timestamp` data with additional timezone information (such as `...T...Z` or `±hh`) will likely fail the query to match (unless it corresponds to UTC0). + + ## Requirements - [Clickhouse](clickhouse.com/) diff --git a/src/queries.ts b/src/queries.ts index 1a0eeb2..69942a1 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -56,10 +56,10 @@ export async function timestampQuery(chain: string, block_number: number | numbe return parseBlockTimeQueryResponse(json); } -export async function blocknumQuery(chain: string, timestamp: Date | Date[]): Promise { +export async function blocknumQuery(chain: string, timestamp: string | string[]): Promise { timestamp = Array.isArray(timestamp) ? timestamp : [timestamp]; const query = `SELECT (chain, block_number, timestamp) FROM ${config.name} WHERE (chain == '${chain}') AND (timestamp IN (${ - timestamp.map((t) => '\'' + t.toISOString().replace('T', ' ').substring(0, 19) + '\'').toString() // Format dates to find them in DB (mock data) + timestamp.map((t) => `'${t}'`).toString() }))`; // TODO: Find closest instead of matching timestamp or another route ? const json = await makeQuery(query); diff --git a/src/schemas.ts b/src/schemas.ts index a453869..5b36990 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -3,11 +3,22 @@ import { z } from '@hono/zod-openapi'; import config from './config'; import { supportedChainsQuery } from './queries'; +// Removes milliseconds and 'Z' from ISO string to match Clickhouse DB insert +function toTimestampDBFormat(d: Date) { + return d.toISOString().split('.')[0]; +} + const supportedChains = await supportedChainsQuery(); // Base types const z_blocknum = z.coerce.number().positive(); -const z_timestamp = z.coerce.date(); +const z_timestamp = z.coerce.date().transform((t: Date) => { + const toUTC = new Date( + Date.UTC(t.getFullYear(), t.getMonth(), t.getDate(), t.getHours(), t.getMinutes(), t.getSeconds()) + ); + + return toTimestampDBFormat(toUTC); +}); // Adapted from https://stackoverflow.com/a/75212079 // Enforces parsing capability from an array of blocknum strings returned by Clickhouse DB @@ -25,7 +36,7 @@ const blocknumsFromStringArray = >>>(schema: T) => { +const timestampsFromStringArray = (schema: T) => { return z.preprocess((obj) => { if (Array.isArray(obj)) { return obj; @@ -80,7 +91,7 @@ export const TimestampSchema = z.object({ name: 'timestamp', in: 'query', }, - example: new Date().toISOString() + example: Date.now().toString() }) }); @@ -90,8 +101,8 @@ export const BlocktimeQueryResponseSchema = z.object({ chain: z.enum(supportedChains).openapi({ example: 'EOS' }), block_number: z_blocknum.openapi({ example: 1337 }), timestamp: z.union([ - z_timestamp.openapi({ example: new Date().toISOString() }), - z_timestamp.array().openapi({ example: [new Date(), new Date(0)] }), + z_timestamp.openapi({ example: Date.now().toString() }), + z_timestamp.array().openapi({ example: [Date.now().toString(), toTimestampDBFormat(new Date(0))] }), ]) }).openapi('BlocktimeQueryResponse');