fix(x): show chat timestamps in the user's local timezone (#779)

* fix(x): show chat timestamps in the user's local timezone

The per-message "Current date and time" context was built with toISOString(), giving the model a UTC time frame with no zone. It then quoted email Date: headers (sent in +0000) in UTC — an email shown as 1:06 PM IST in the inbox was described as 7:36 AM in chat.

Use local wall-clock via toLocaleString with an explicit timeZoneName and the IANA zone (matching what the legacy engine already did) so the model converts offset-bearing timestamps to local time.

* fix(x): convert email timestamps to local time for the assistant

The "now" fix gave the model a local time frame, but email dates still reached it as raw RFC 2822 Date: headers (marketing mail is sent in +0000), so it kept quoting them in UTC — a 1:06 PM IST email described as 7:36 AM in chat.

Add formatEmailDateLocal() and apply it at the three model-facing serialization points: the read-view email tool, the thread-summary line, and the gmail-sync knowledge markdown (**Date:**). The structured date field on snapshots/messages is left raw, since the renderer parses it with new Date() and formats it client-side.

* fix(x): generalize local-time formatting for model-facing timestamps

Move formatEmailDateLocal out of sync_gmail into a shared
formatTimestampForModel() util — email is just the first source of
external timestamps; Slack/calendar serialization can now reuse the
same one-line call instead of growing per-source formatters. Also add
a system-prompt instruction to always express times in the user's
local timezone, as a fallback for raw dates inside email bodies, web
content, and third-party tool output that pre-conversion can't reach.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Harshvardhan Vatsa 2026-07-24 16:26:04 +05:30 committed by GitHub
parent f8f5c8614f
commit 15d484980d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 67 additions and 17 deletions

View file

@ -22,5 +22,6 @@ export * as notificationSettings from './notification-settings.js';
export * as turnLimits from './turn-limits.js';
export * as codeSessions from './code-sessions.js';
export * as channels from './channels.js';
export * as time from './time.js';
export * as rowboatApp from './rowboat-app.js';
export { PrefixLogger };

View file

@ -0,0 +1,34 @@
/**
* Format a timestamp into the user's local timezone for any text shown to the
* LLM (tool output, knowledge markdown, thread summaries). External sources
* carry their own offsets email `Date:` headers are typically `+0000`, Slack
* uses epoch seconds, calendar APIs return UTC ISO strings. Handed to the model
* raw, it quotes them verbatim instead of converting e.g. a 1:06 PM IST email
* described as "7:36 AM" in chat. Call this at every model-facing serialization
* boundary; the model's tendency to quote timestamps verbatim then produces
* correct output. The weekday is included because models are unreliable at
* day-of-week arithmetic. Uses the system timezone of the process it runs in.
*
* NOT for structured `date` fields that code later parses with `new Date(...)`
* those must stay raw parseable values.
*
* Accepts a raw date string, epoch milliseconds, or a Date. Unparseable
* strings (e.g. 'Unknown') are returned unchanged; null/undefined and invalid
* Date/number inputs return ''.
*/
export function formatTimestampForModel(raw: string | number | Date | undefined | null): string {
if (raw === undefined || raw === null || raw === '') return '';
const parsed = raw instanceof Date ? raw : new Date(raw);
if (Number.isNaN(parsed.getTime())) {
return typeof raw === 'string' ? raw : '';
}
return parsed.toLocaleString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
});
}