agent: Add date separators to Thread History (#29961)
Adds time-bucket separators to the thread history list: https://github.com/user-attachments/assets/c9ac3ec4-b632-4ea5-8234-382b48de2bd6 Note: I'm simulating that Today is next Thursday so that I can show the "This Week" bucket. Release Notes: - agent: Add date separators to Thread History
This commit is contained in:
parent
4fdd14c3d8
commit
de554589a8
3 changed files with 766 additions and 175 deletions
|
@ -42,6 +42,82 @@ pub fn format_local_timestamp(
|
|||
}
|
||||
}
|
||||
|
||||
/// Formats the date component of a timestamp
|
||||
pub fn format_date(
|
||||
timestamp: OffsetDateTime,
|
||||
reference: OffsetDateTime,
|
||||
enhanced_formatting: bool,
|
||||
) -> String {
|
||||
format_absolute_date(timestamp, reference, enhanced_formatting)
|
||||
}
|
||||
|
||||
/// Formats the time component of a timestamp
|
||||
pub fn format_time(timestamp: OffsetDateTime) -> String {
|
||||
format_absolute_time(timestamp)
|
||||
}
|
||||
|
||||
/// Formats the date component of a timestamp in medium style
|
||||
pub fn format_date_medium(
|
||||
timestamp: OffsetDateTime,
|
||||
reference: OffsetDateTime,
|
||||
enhanced_formatting: bool,
|
||||
) -> String {
|
||||
format_absolute_date_medium(timestamp, reference, enhanced_formatting)
|
||||
}
|
||||
|
||||
fn format_absolute_date(
|
||||
timestamp: OffsetDateTime,
|
||||
reference: OffsetDateTime,
|
||||
#[allow(unused_variables)] enhanced_date_formatting: bool,
|
||||
) -> String {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if !enhanced_date_formatting {
|
||||
return macos::format_date(×tamp);
|
||||
}
|
||||
|
||||
let timestamp_date = timestamp.date();
|
||||
let reference_date = reference.date();
|
||||
if timestamp_date == reference_date {
|
||||
"Today".to_string()
|
||||
} else if reference_date.previous_day() == Some(timestamp_date) {
|
||||
"Yesterday".to_string()
|
||||
} else {
|
||||
macos::format_date(×tamp)
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// todo(linux) respect user's date/time preferences
|
||||
// todo(windows) respect user's date/time preferences
|
||||
let current_locale = CURRENT_LOCALE
|
||||
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
|
||||
format_timestamp_naive_date(
|
||||
timestamp,
|
||||
reference,
|
||||
is_12_hour_time_by_locale(current_locale.as_str()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_absolute_time(timestamp: OffsetDateTime) -> String {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
macos::format_time(×tamp)
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// todo(linux) respect user's date/time preferences
|
||||
// todo(windows) respect user's date/time preferences
|
||||
let current_locale = CURRENT_LOCALE
|
||||
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
|
||||
format_timestamp_naive_time(
|
||||
timestamp,
|
||||
is_12_hour_time_by_locale(current_locale.as_str()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_absolute_timestamp(
|
||||
timestamp: OffsetDateTime,
|
||||
reference: OffsetDateTime,
|
||||
|
@ -52,22 +128,22 @@ fn format_absolute_timestamp(
|
|||
if !enhanced_date_formatting {
|
||||
return format!(
|
||||
"{} {}",
|
||||
macos::format_date(×tamp),
|
||||
macos::format_time(×tamp)
|
||||
format_absolute_date(timestamp, reference, enhanced_date_formatting),
|
||||
format_absolute_time(timestamp)
|
||||
);
|
||||
}
|
||||
|
||||
let timestamp_date = timestamp.date();
|
||||
let reference_date = reference.date();
|
||||
if timestamp_date == reference_date {
|
||||
format!("Today at {}", macos::format_time(×tamp))
|
||||
format!("Today at {}", format_absolute_time(timestamp))
|
||||
} else if reference_date.previous_day() == Some(timestamp_date) {
|
||||
format!("Yesterday at {}", macos::format_time(×tamp))
|
||||
format!("Yesterday at {}", format_absolute_time(timestamp))
|
||||
} else {
|
||||
format!(
|
||||
"{} {}",
|
||||
macos::format_date(×tamp),
|
||||
macos::format_time(×tamp)
|
||||
format_absolute_date(timestamp, reference, enhanced_date_formatting),
|
||||
format_absolute_time(timestamp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -79,13 +155,62 @@ fn format_absolute_timestamp(
|
|||
}
|
||||
}
|
||||
|
||||
fn format_absolute_timestamp_medium(
|
||||
fn format_absolute_date_medium(
|
||||
timestamp: OffsetDateTime,
|
||||
#[allow(unused_variables)] reference: OffsetDateTime,
|
||||
reference: OffsetDateTime,
|
||||
enhanced_formatting: bool,
|
||||
) -> String {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
macos::format_date_medium(×tamp)
|
||||
if !enhanced_formatting {
|
||||
return macos::format_date_medium(×tamp);
|
||||
}
|
||||
|
||||
let timestamp_date = timestamp.date();
|
||||
let reference_date = reference.date();
|
||||
if timestamp_date == reference_date {
|
||||
"Today".to_string()
|
||||
} else if reference_date.previous_day() == Some(timestamp_date) {
|
||||
"Yesterday".to_string()
|
||||
} else {
|
||||
macos::format_date_medium(×tamp)
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// todo(linux) respect user's date/time preferences
|
||||
// todo(windows) respect user's date/time preferences
|
||||
let current_locale = CURRENT_LOCALE
|
||||
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
|
||||
if !enhanced_formatting {
|
||||
return format_timestamp_naive_date_medium(
|
||||
timestamp,
|
||||
is_12_hour_time_by_locale(current_locale.as_str()),
|
||||
);
|
||||
}
|
||||
|
||||
let timestamp_date = timestamp.date();
|
||||
let reference_date = reference.date();
|
||||
if timestamp_date == reference_date {
|
||||
"Today".to_string()
|
||||
} else if reference_date.previous_day() == Some(timestamp_date) {
|
||||
"Yesterday".to_string()
|
||||
} else {
|
||||
format_timestamp_naive_date_medium(
|
||||
timestamp,
|
||||
is_12_hour_time_by_locale(current_locale.as_str()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_absolute_timestamp_medium(
|
||||
timestamp: OffsetDateTime,
|
||||
reference: OffsetDateTime,
|
||||
) -> String {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
format_absolute_date_medium(timestamp, reference, false)
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
|
@ -178,15 +303,9 @@ fn calculate_month_difference(timestamp: OffsetDateTime, reference: OffsetDateTi
|
|||
/// Note:
|
||||
/// This function does not respect the user's date and time preferences.
|
||||
/// This should only be used as a fallback mechanism when the OS time formatting fails.
|
||||
pub fn format_timestamp_naive(
|
||||
timestamp_local: OffsetDateTime,
|
||||
reference_local: OffsetDateTime,
|
||||
is_12_hour_time: bool,
|
||||
) -> String {
|
||||
fn format_timestamp_naive_time(timestamp_local: OffsetDateTime, is_12_hour_time: bool) -> String {
|
||||
let timestamp_local_hour = timestamp_local.hour();
|
||||
let timestamp_local_minute = timestamp_local.minute();
|
||||
let reference_local_date = reference_local.date();
|
||||
let timestamp_local_date = timestamp_local.date();
|
||||
|
||||
let (hour, meridiem) = if is_12_hour_time {
|
||||
let meridiem = if timestamp_local_hour >= 12 {
|
||||
|
@ -206,38 +325,103 @@ pub fn format_timestamp_naive(
|
|||
(timestamp_local_hour, None)
|
||||
};
|
||||
|
||||
let formatted_time = match meridiem {
|
||||
match meridiem {
|
||||
Some(meridiem) => format!("{}:{:02} {}", hour, timestamp_local_minute, meridiem),
|
||||
None => format!("{:02}:{:02}", hour, timestamp_local_minute),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let formatted_date = match meridiem {
|
||||
Some(_) => format!(
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn format_timestamp_naive_date(
|
||||
timestamp_local: OffsetDateTime,
|
||||
reference_local: OffsetDateTime,
|
||||
is_12_hour_time: bool,
|
||||
) -> String {
|
||||
let reference_local_date = reference_local.date();
|
||||
let timestamp_local_date = timestamp_local.date();
|
||||
|
||||
if timestamp_local_date == reference_local_date {
|
||||
"Today".to_string()
|
||||
} else if reference_local_date.previous_day() == Some(timestamp_local_date) {
|
||||
"Yesterday".to_string()
|
||||
} else {
|
||||
match is_12_hour_time {
|
||||
true => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
false => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn format_timestamp_naive_date_medium(
|
||||
timestamp_local: OffsetDateTime,
|
||||
is_12_hour_time: bool,
|
||||
) -> String {
|
||||
let timestamp_local_date = timestamp_local.date();
|
||||
|
||||
match is_12_hour_time {
|
||||
true => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
None => format!(
|
||||
false => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_timestamp_naive(
|
||||
timestamp_local: OffsetDateTime,
|
||||
reference_local: OffsetDateTime,
|
||||
is_12_hour_time: bool,
|
||||
) -> String {
|
||||
let formatted_time = format_timestamp_naive_time(timestamp_local, is_12_hour_time);
|
||||
let reference_local_date = reference_local.date();
|
||||
let timestamp_local_date = timestamp_local.date();
|
||||
|
||||
if timestamp_local_date == reference_local_date {
|
||||
format!("Today at {}", formatted_time)
|
||||
} else if reference_local_date.previous_day() == Some(timestamp_local_date) {
|
||||
format!("Yesterday at {}", formatted_time)
|
||||
} else {
|
||||
let formatted_date = match is_12_hour_time {
|
||||
true => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
false => format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.year()
|
||||
),
|
||||
};
|
||||
format!("{} {}", formatted_date, formatted_time)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
static CURRENT_LOCALE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn format_timestamp_fallback(timestamp: OffsetDateTime, reference: OffsetDateTime) -> String {
|
||||
static CURRENT_LOCALE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
||||
let current_locale = CURRENT_LOCALE
|
||||
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
|
||||
|
||||
|
@ -245,8 +429,8 @@ fn format_timestamp_fallback(timestamp: OffsetDateTime, reference: OffsetDateTim
|
|||
format_timestamp_naive(timestamp, reference, is_12_hour_time)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
/// Returns `true` if the locale is recognized as a 12-hour time locale.
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn is_12_hour_time_by_locale(locale: &str) -> bool {
|
||||
[
|
||||
"es-MX", "es-CO", "es-SV", "es-NI",
|
||||
|
@ -344,6 +528,131 @@ mod macos {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_format_date() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
|
||||
// Test with same date (today)
|
||||
let timestamp_today = create_offset_datetime(1990, 4, 12, 9, 30, 0);
|
||||
assert_eq!(format_date(timestamp_today, reference, true), "Today");
|
||||
|
||||
// Test with previous day (yesterday)
|
||||
let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
|
||||
assert_eq!(
|
||||
format_date(timestamp_yesterday, reference, true),
|
||||
"Yesterday"
|
||||
);
|
||||
|
||||
// Test with other date
|
||||
let timestamp_other = create_offset_datetime(1990, 4, 10, 9, 30, 0);
|
||||
let result = format_date(timestamp_other, reference, true);
|
||||
assert!(!result.is_empty());
|
||||
assert_ne!(result, "Today");
|
||||
assert_ne!(result, "Yesterday");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_time() {
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
|
||||
|
||||
// We can't assert the exact output as it depends on the platform and locale
|
||||
// But we can at least confirm it doesn't panic and returns a non-empty string
|
||||
let result = format_time(timestamp);
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_date_medium() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
|
||||
|
||||
// Test with enhanced formatting (today)
|
||||
let result_enhanced = format_date_medium(timestamp, reference, true);
|
||||
assert_eq!(result_enhanced, "Today");
|
||||
|
||||
// Test with standard formatting
|
||||
let result_standard = format_date_medium(timestamp, reference, false);
|
||||
assert!(!result_standard.is_empty());
|
||||
|
||||
// Test yesterday with enhanced formatting
|
||||
let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
|
||||
let result_yesterday = format_date_medium(timestamp_yesterday, reference, true);
|
||||
assert_eq!(result_yesterday, "Yesterday");
|
||||
|
||||
// Test other date with enhanced formatting
|
||||
let timestamp_other = create_offset_datetime(1990, 4, 10, 9, 30, 0);
|
||||
let result_other = format_date_medium(timestamp_other, reference, true);
|
||||
assert!(!result_other.is_empty());
|
||||
assert_ne!(result_other, "Today");
|
||||
assert_ne!(result_other, "Yesterday");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_absolute_time() {
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
|
||||
|
||||
// We can't assert the exact output as it depends on the platform and locale
|
||||
// But we can at least confirm it doesn't panic and returns a non-empty string
|
||||
let result = format_absolute_time(timestamp);
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_absolute_date() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
|
||||
// Test with same date (today)
|
||||
let timestamp_today = create_offset_datetime(1990, 4, 12, 9, 30, 0);
|
||||
assert_eq!(
|
||||
format_absolute_date(timestamp_today, reference, true),
|
||||
"Today"
|
||||
);
|
||||
|
||||
// Test with previous day (yesterday)
|
||||
let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
|
||||
assert_eq!(
|
||||
format_absolute_date(timestamp_yesterday, reference, true),
|
||||
"Yesterday"
|
||||
);
|
||||
|
||||
// Test with other date
|
||||
let timestamp_other = create_offset_datetime(1990, 4, 10, 9, 30, 0);
|
||||
let result = format_absolute_date(timestamp_other, reference, true);
|
||||
assert!(!result.is_empty());
|
||||
assert_ne!(result, "Today");
|
||||
assert_ne!(result, "Yesterday");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_absolute_date_medium() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
|
||||
|
||||
// Test with enhanced formatting (today)
|
||||
let result_enhanced = format_absolute_date_medium(timestamp, reference, true);
|
||||
assert_eq!(result_enhanced, "Today");
|
||||
|
||||
// Test with standard formatting
|
||||
let result_standard = format_absolute_date_medium(timestamp, reference, false);
|
||||
assert!(!result_standard.is_empty());
|
||||
|
||||
// Test yesterday with enhanced formatting
|
||||
let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
|
||||
let result_yesterday = format_absolute_date_medium(timestamp_yesterday, reference, true);
|
||||
assert_eq!(result_yesterday, "Yesterday");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_timestamp_naive_time() {
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
|
||||
assert_eq!(format_timestamp_naive_time(timestamp, true), "9:30 AM");
|
||||
assert_eq!(format_timestamp_naive_time(timestamp, false), "09:30");
|
||||
|
||||
let timestamp_pm = create_offset_datetime(1990, 4, 12, 15, 45, 0);
|
||||
assert_eq!(format_timestamp_naive_time(timestamp_pm, true), "3:45 PM");
|
||||
assert_eq!(format_timestamp_naive_time(timestamp_pm, false), "15:45");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_24_hour_time() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue