Skip to content

Commit 1432845

Browse files
fix: update per-prompt timestamp to include local time zone information (#2654)
1 parent a8afb47 commit 1432845

File tree

2 files changed

+49
-26
lines changed

2 files changed

+49
-26
lines changed

crates/chat-cli/src/cli/chat/conversation.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::collections::{
66
use std::io::Write;
77
use std::sync::atomic::Ordering;
88

9-
use chrono::Utc;
9+
use chrono::Local;
1010
use crossterm::style::Color;
1111
use crossterm::{
1212
execute,
@@ -328,7 +328,7 @@ impl ConversationState {
328328
input
329329
};
330330

331-
let msg = UserMessage::new_prompt(input, Some(Utc::now()));
331+
let msg = UserMessage::new_prompt(input, Some(Local::now().fixed_offset()));
332332
self.next_message = Some(msg);
333333
}
334334

@@ -420,7 +420,7 @@ impl ConversationState {
420420
self.next_message = Some(UserMessage::new_tool_use_results_with_images(
421421
tool_results,
422422
images,
423-
Some(Utc::now()),
423+
Some(Local::now().fixed_offset()),
424424
));
425425
}
426426

@@ -429,7 +429,7 @@ impl ConversationState {
429429
self.next_message = Some(UserMessage::new_cancelled_tool_uses(
430430
Some(deny_input),
431431
tools_to_be_abandoned.iter().map(|t| t.id.as_str()),
432-
Some(Utc::now()),
432+
Some(Local::now().fixed_offset()),
433433
));
434434
}
435435

crates/chat-cli/src/cli/chat/message.rs

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use std::env;
33

44
use chrono::{
55
DateTime,
6-
Utc,
6+
Datelike,
7+
FixedOffset,
78
};
89
use serde::{
910
Deserialize,
@@ -54,7 +55,7 @@ pub struct UserMessage {
5455
pub additional_context: String,
5556
pub env_context: UserEnvContext,
5657
pub content: UserMessageContent,
57-
pub timestamp: Option<DateTime<Utc>>,
58+
pub timestamp: Option<DateTime<FixedOffset>>,
5859
pub images: Option<Vec<ImageBlock>>,
5960
}
6061

@@ -107,7 +108,7 @@ impl UserMessageContent {
107108
impl UserMessage {
108109
/// Creates a new [UserMessage::Prompt], automatically detecting and adding the user's
109110
/// environment [UserEnvContext].
110-
pub fn new_prompt(prompt: String, timestamp: Option<DateTime<Utc>>) -> Self {
111+
pub fn new_prompt(prompt: String, timestamp: Option<DateTime<FixedOffset>>) -> Self {
111112
Self {
112113
images: None,
113114
timestamp,
@@ -120,7 +121,7 @@ impl UserMessage {
120121
pub fn new_cancelled_tool_uses<'a>(
121122
prompt: Option<String>,
122123
tool_use_ids: impl Iterator<Item = &'a str>,
123-
timestamp: Option<DateTime<Utc>>,
124+
timestamp: Option<DateTime<FixedOffset>>,
124125
) -> Self {
125126
Self {
126127
images: None,
@@ -157,7 +158,7 @@ impl UserMessage {
157158
pub fn new_tool_use_results_with_images(
158159
results: Vec<ToolUseResult>,
159160
images: Vec<ImageBlock>,
160-
timestamp: Option<DateTime<Utc>>,
161+
timestamp: Option<DateTime<FixedOffset>>,
161162
) -> Self {
162163
Self {
163164
additional_context: String::new(),
@@ -292,12 +293,20 @@ impl UserMessage {
292293
let mut content = String::new();
293294

294295
if let Some(ts) = self.timestamp {
296+
let weekday = match ts.weekday() {
297+
chrono::Weekday::Mon => "Monday",
298+
chrono::Weekday::Tue => "Tuesday",
299+
chrono::Weekday::Wed => "Wednesday",
300+
chrono::Weekday::Thu => "Thursday",
301+
chrono::Weekday::Fri => "Friday",
302+
chrono::Weekday::Sat => "Saturday",
303+
chrono::Weekday::Sun => "Sunday",
304+
};
305+
// Format the time with iso8601 format using a timezone offset.
306+
let timestamp = ts.to_rfc3339_opts(chrono::SecondsFormat::Millis, false);
295307
content.push_str(&format!(
296-
"{}Current UTC time: {}{}\n",
297-
CONTEXT_ENTRY_START_HEADER,
298-
// Format the time with iso8601 format using Z, e.g. 2025-08-08T17:43:28.672Z
299-
ts.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
300-
CONTEXT_ENTRY_END_HEADER,
308+
"{}Current time: {}, {}\n{}",
309+
CONTEXT_ENTRY_START_HEADER, weekday, timestamp, CONTEXT_ENTRY_END_HEADER,
301310
));
302311
}
303312

@@ -565,21 +574,35 @@ mod tests {
565574
fn test_user_input_message_timestamp_formatting() {
566575
const USER_PROMPT: &str = "hello world";
567576

568-
let msg = UserMessage::new_prompt(USER_PROMPT.to_string(), Some(Utc::now()));
577+
// Friday, Jan 26, 2018
578+
let timestamp = DateTime::parse_from_rfc3339("2018-01-26T12:30:09.453-07:00").unwrap();
569579

570-
let msgs = [
571-
msg.clone().into_user_input_message(None, &HashMap::new()),
572-
msg.clone().into_history_entry(),
580+
let msgs = {
581+
let msg = UserMessage::new_prompt(USER_PROMPT.to_string(), Some(timestamp));
582+
[
583+
msg.clone().into_user_input_message(None, &HashMap::new()),
584+
msg.clone().into_history_entry(),
585+
]
586+
};
587+
let expected = [
588+
CONTEXT_ENTRY_START_HEADER,
589+
"Current time",
590+
"Friday",
591+
CONTEXT_ENTRY_END_HEADER,
592+
USER_ENTRY_START_HEADER,
593+
USER_PROMPT,
594+
USER_ENTRY_END_HEADER.trim(), /* user message content is trimmed, so remove any
595+
* trailing newlines for the end header. */
573596
];
574-
575597
for m in msgs {
576-
println!("checking {:?}", m);
577-
assert!(m.content.contains(CONTEXT_ENTRY_START_HEADER));
578-
assert!(m.content.contains("Current UTC time"));
579-
assert!(m.content.contains(CONTEXT_ENTRY_END_HEADER));
580-
assert!(m.content.contains(USER_ENTRY_START_HEADER));
581-
assert!(m.content.contains(USER_PROMPT));
582-
assert!(m.content.contains(USER_ENTRY_END_HEADER.trim()));
598+
for assertion in expected {
599+
assert!(
600+
m.content.contains(assertion),
601+
"expected message: {} to contain: {}",
602+
m.content,
603+
assertion
604+
);
605+
}
583606
}
584607
}
585608

0 commit comments

Comments
 (0)