-
Notifications
You must be signed in to change notification settings - Fork 19
fix(decoder): Auto-detect timestamp resolution in JSONL decoder #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,18 @@ | |
| } from "../../../typings/logs"; | ||
|
|
||
|
|
||
| // Timestamp resolution detection thresholds | ||
| const TIMESTAMP_THRESHOLD_SECONDS = 1e11; | ||
| const TIMESTAMP_THRESHOLD_MILLISECONDS = 1e14; | ||
| const TIMESTAMP_THRESHOLD_MICROSECONDS = 1e17; | ||
|
|
||
| // Conversion factors to milliseconds | ||
| const MILLISECONDS_PER_SECOND = 1000; | ||
| const MILLISECONDS_PER_MICROSECOND = 1000; | ||
| const MILLISECONDS_PER_NANOSECOND = 1e6; | ||
| const NANOSECONDS_PER_MILLISECOND = 1000000n; | ||
|
|
||
|
|
||
| /** | ||
| * Determines whether the given value is a `JsonObject` and applies a TypeScript narrowing | ||
| * conversion if so. | ||
|
|
@@ -69,7 +81,39 @@ | |
| field = INVALID_TIMESTAMP_VALUE; | ||
| } | ||
|
|
||
| let dayjsTimestamp: Dayjs = dayjs.utc(field); | ||
| let timestampInMs: number | string = field; | ||
|
|
||
| // Auto-detect timestamp resolution and convert to milliseconds | ||
| if ("number" === typeof field || "bigint" === typeof field) { | ||
| const numValue = "bigint" === typeof field | ||
| ? Number(field) | ||
| : field; | ||
|
|
||
| // Detection based on timestamp magnitude: | ||
| // - Seconds: < 10^11 (covers dates up to year 5138) | ||
| // - Milliseconds: 10^11 <= t < 10^14 | ||
| // - Microseconds: 10^14 <= t < 10^17 | ||
| // - Nanoseconds: >= 10^17 | ||
| if (numValue < TIMESTAMP_THRESHOLD_SECONDS && numValue >= 0) { | ||
| // Seconds -> convert to milliseconds | ||
| timestampInMs = numValue * MILLISECONDS_PER_SECOND; | ||
|
Comment on lines
+97
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Negative timestamps in seconds are incorrectly handled. The check For example, Apply this diff to handle negative timestamps correctly: - if (numValue < TIMESTAMP_THRESHOLD_SECONDS && numValue >= 0) {
+ if (Math.abs(numValue) < TIMESTAMP_THRESHOLD_SECONDS) {
// Seconds -> convert to milliseconds
timestampInMs = numValue * MILLISECONDS_PER_SECOND;🧰 Tools🪛 GitHub Check: lint-check[failure] 97-97: 🤖 Prompt for AI Agents |
||
| } else if (numValue < TIMESTAMP_THRESHOLD_MILLISECONDS) { | ||
| // Milliseconds -> use as-is | ||
| timestampInMs = numValue; | ||
| } else if (numValue < TIMESTAMP_THRESHOLD_MICROSECONDS) { | ||
| // Microseconds -> convert to milliseconds | ||
| timestampInMs = numValue / MILLISECONDS_PER_MICROSECOND; | ||
| } else { | ||
| // Nanoseconds -> convert to milliseconds | ||
| // For bigint, perform division before converting to avoid precision loss | ||
| timestampInMs = "bigint" === typeof field | ||
| ? Number(field / NANOSECONDS_PER_MILLISECOND) | ||
| : numValue / MILLISECONDS_PER_NANOSECOND; | ||
|
Comment on lines
+109
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the multiline ternary operator formatting to resolve ESLint violation. The pipeline is failing because the Apply this diff to fix the formatting: - timestampInMs = "bigint" === typeof field
- ? Number(field / NANOSECONDS_PER_MILLISECOND)
- : numValue / MILLISECONDS_PER_NANOSECOND;
+ timestampInMs = "bigint" === typeof field ?
+ Number(field / NANOSECONDS_PER_MILLISECOND) :
+ numValue / MILLISECONDS_PER_NANOSECOND;🧰 Tools🪛 GitHub Check: lint-check[failure] 111-111: [failure] 110-110: 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
| // dayjs.utc() expects millisecond input | ||
| let dayjsTimestamp: Dayjs = dayjs.utc(timestampInMs); | ||
|
|
||
| // Sanitize invalid (e.g., "deadbeef") timestamps to `INVALID_TIMESTAMP_VALUE`; otherwise | ||
| // they'll show up in UI as "Invalid Date". | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Risk of precision loss for large microsecond timestamps when converting bigint to Number.
Converting bigint to Number before the magnitude check causes precision loss for microsecond timestamps (1e14–1e17) that exceed
Number.MAX_SAFE_INTEGER(≈9.007e15). The nanoseconds case correctly handles this by performing bigint division before conversion (lines 109-111), but the microseconds case doesn't.Consider handling bigint for microseconds similarly to nanoseconds. Apply this approach:
} else if (numValue < TIMESTAMP_THRESHOLD_MICROSECONDS) { // Microseconds -> convert to milliseconds - timestampInMs = numValue / MILLISECONDS_PER_MICROSECOND; + timestampInMs = "bigint" === typeof field ? + Number(field / 1000n) : + numValue / MILLISECONDS_PER_MICROSECOND; } else {🧰 Tools
🪛 GitHub Actions: lint
[error] 89-89: ESLint: '?' should be placed at the end of the line. (operator-linebreak). Command: 'eslint . --max-warnings 0 --concurrency=auto'.
🪛 GitHub Check: lint-check
[failure] 90-90:
':' should be placed at the end of the line
[failure] 89-89:
'?' should be placed at the end of the line
🤖 Prompt for AI Agents