Skip to content

fix: read correct UNIX timestamps#273

Merged
erickzhao merged 4 commits into
mainfrom
tz-fixes
Oct 24, 2025
Merged

fix: read correct UNIX timestamps#273
erickzhao merged 4 commits into
mainfrom
tz-fixes

Conversation

@erickzhao
Copy link
Copy Markdown
Member

@erickzhao erickzhao commented Oct 23, 2025

#270 added a timezone switcher for log tables.

Bug

We noticed that timestamps displayed in the log table were being shifted to the Sleuth client's timezone. For example, a log bundle containing a line at 9:00 AM ET would show up as 9:00 AM PT when displayed in Sleuth.

image

Root cause

The root cause of this issue is that the momentValue (i.e. milliseconds since the UNIX Epoch) assigned to each log line was being saved in the Sleuth client's own system timezone.

For a concrete example, say a log bundle is generated in America/Toronto or UTC-5:

const dateString = `10/17/25, 09:16:02:509`;
const date = new Date(dateString); // If you open Sleuth in Pacific time, this will be 2025-10-17T16:16:02.509Z
const momentValue = date.valueOf(); // 1760717762509 --> this is wrong!

Solution

The initial solution for this PR was to convert IANA timezones to a fixed UTC offset and add it to the new Date constructor. However, this doesn't work globally due to changes in Daylight Savings Time—for instance, Vancouver is either at UTC-7 or UTC-8 depending on the time of year.

As a new solution, we parse the date string to its components with regex, then use the new TZDate constructor from @date-fns/tz that handles all timezone-related logic.

import { TZDate } from '@date-fns/tz';

const dateString = `10/17/25, 09:16:02:509`;
const DATE_FORMAT_RGX =
  /^(\d{2})\/(\d{2})\/(\d{2}), (\d{2}):(\d{2}):(\d{2}):(\d{3})$/;
const parsedDate = DATE_FORMAT_RGX.exec(dateString);
const [, month, day, year, hour, minute, second, millisecond] = parsedDate;
const momentValue = new TZDate(
  parseInt(`20${year}`, 10),
  parseInt(month, 10),
  parseInt(day, 10),
  parseInt(hour, 10),
  parseInt(minute, 10),
  parseInt(second, 10),
  parseInt(millisecond, 10),
  userTZ, // `America/Toronto`
).valueOf(); // 1763388962509 --> this is correct!

@erickzhao erickzhao marked this pull request as draft October 23, 2025 19:00
@erickzhao erickzhao marked this pull request as ready for review October 23, 2025 23:03
@erickzhao erickzhao requested review from a team and nocturnll October 23, 2025 23:03
results[1].replace(', 24:', ', 00:'),
// Expected format: MM/DD/YY, HH:mm:ss:SSS'
const DATE_FORMAT_RGX =
/^(\d{2})\/(\d{2})\/(\d{2}), (\d{2}):(\d{2}):(\d{2}):(\d{3})$/;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😭 Whew, I bet this was not fun to figure out

throw new Error(`Failed to parse Chromium timestamp: ${timestamp}`);
}
const [, month, day, hour, minute, second, millisecond] = parsedTimestamp;
const logDate = new TZDate(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (non-blocking): it looks like we're doing similar logic at 3 points in this file, is it worth DRYing this up into a shared utility method? Or is it just different enough that it's not worth it (looks like this one needs month when another needs year, etc).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The webapp and desktop formats are the same, but having a different format for the Chromium one makes the refactoring a bit cumbersome. It's really only 4 added lines each time so I think it's okay to keep it as-is for now.

@erickzhao erickzhao merged commit 9dca786 into main Oct 24, 2025
6 checks passed
@erickzhao erickzhao deleted the tz-fixes branch October 24, 2025 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants