From 4fa9f93f4603005948b235ef7f0880d09327832d Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sun, 10 Aug 2025 17:00:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=B6=E5=8C=BA=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands/usage.rs | 98 ++++++++++++++++++++------- src/lib/date-utils.ts | 113 +++++++++++++++++++++----------- 2 files changed, 150 insertions(+), 61 deletions(-) diff --git a/src-tauri/src/commands/usage.rs b/src-tauri/src/commands/usage.rs index 62a16b8..5472c06 100644 --- a/src-tauri/src/commands/usage.rs +++ b/src-tauri/src/commands/usage.rs @@ -286,8 +286,17 @@ pub fn parse_jsonl_file( .clone() .unwrap_or_else(|| encoded_project_name.to_string()); + // 转换时间戳为本地时间格式 + let local_timestamp = if let Ok(dt) = DateTime::parse_from_rfc3339(&entry.timestamp) { + // 转换为本地时区并格式化为 ISO 格式 + dt.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S%.3f").to_string() + } else { + // 如果解析失败,保留原始时间戳 + entry.timestamp.clone() + }; + entries.push(UsageEntry { - timestamp: entry.timestamp, + timestamp: local_timestamp, model: message .model .clone() @@ -398,17 +407,23 @@ pub fn get_usage_stats(days: Option) -> Result { // Filter by days if specified let filtered_entries = if let Some(days) = days { // Convert 'now' to local date for consistent comparison - let cutoff = Local::now().with_timezone(&Local).date_naive() - chrono::Duration::days(days as i64); + let cutoff = Local::now().date_naive() - chrono::Duration::days(days as i64); all_entries .into_iter() .filter(|e| { - if let Ok(dt) = DateTime::parse_from_rfc3339(&e.timestamp) { - // Convert each entry timestamp to local time, then compare dates - let local_date = dt.with_timezone(&Local).date_naive(); - local_date >= cutoff + // 处理新的本地时间格式 "YYYY-MM-DD HH:MM:SS.sss" + let date = if e.timestamp.contains(' ') { + // 新格式:直接解析日期部分 + e.timestamp.split(' ').next() + .and_then(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").ok()) + } else if let Ok(dt) = DateTime::parse_from_rfc3339(&e.timestamp) { + // 旧格式:RFC3339 格式 + Some(dt.with_timezone(&Local).date_naive()) } else { - false - } + None + }; + + date.map_or(false, |d| d >= cutoff) }) .collect() } else { @@ -469,7 +484,12 @@ pub fn get_usage_stats(days: Option) -> Result { .insert(entry.session_id.clone()); // Update daily stats (use local timezone date) - let date = if let Ok(dt) = DateTime::parse_from_rfc3339(&entry.timestamp) { + // 处理新的本地时间格式 "YYYY-MM-DD HH:MM:SS.sss" + let date = if entry.timestamp.contains(' ') { + // 新格式:直接提取日期部分 + entry.timestamp.split(' ').next().unwrap_or(&entry.timestamp).to_string() + } else if let Ok(dt) = DateTime::parse_from_rfc3339(&entry.timestamp) { + // 旧格式:RFC3339 格式 dt.with_timezone(&Local).date_naive().to_string() } else { // Fallback to raw prefix if parse fails @@ -608,12 +628,19 @@ pub fn get_usage_by_date_range(start_date: String, end_date: String) -> Result = all_entries .into_iter() .filter(|e| { - if let Ok(dt) = DateTime::parse_from_rfc3339(&e.timestamp) { - let date = dt.with_timezone(&Local).date_naive(); - date >= start && date <= end + // 处理新的本地时间格式 "YYYY-MM-DD HH:MM:SS.sss" + let date = if e.timestamp.contains(' ') { + // 新格式:直接解析日期部分 + e.timestamp.split(' ').next() + .and_then(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").ok()) + } else if let Ok(dt) = DateTime::parse_from_rfc3339(&e.timestamp) { + // 旧格式:RFC3339 格式 + Some(dt.with_timezone(&Local).date_naive()) } else { - false - } + None + }; + + date.map_or(false, |d| d >= start && d <= end) }) .collect(); @@ -686,9 +713,15 @@ pub fn get_usage_by_date_range(start_date: String, end_date: String) -> Result = all_entries .into_iter() .filter(|e| { - if let Ok(dt) = DateTime::parse_from_rfc3339(&e.timestamp) { - let date = dt.with_timezone(&Local).date_naive(); - let is_after_since = since_date.map_or(true, |s| date >= s); - let is_before_until = until_date.map_or(true, |u| date <= u); + // 处理新的本地时间格式 "YYYY-MM-DD HH:MM:SS.sss" + let date = if e.timestamp.contains(' ') { + // 新格式:直接解析日期部分 + e.timestamp.split(' ').next() + .and_then(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").ok()) + } else if let Ok(dt) = DateTime::parse_from_rfc3339(&e.timestamp) { + // 旧格式:RFC3339 格式 + Some(dt.with_timezone(&Local).date_naive()) + } else { + None + }; + + if let Some(d) = date { + let is_after_since = since_date.map_or(true, |s| d >= s); + let is_before_until = until_date.map_or(true, |u| d <= u); is_after_since && is_before_until } else { false diff --git a/src/lib/date-utils.ts b/src/lib/date-utils.ts index 70e6b86..ca75e5a 100644 --- a/src/lib/date-utils.ts +++ b/src/lib/date-utils.ts @@ -1,40 +1,45 @@ /** * Formats a Unix timestamp to a human-readable date string * @param timestamp - Unix timestamp in seconds + * @param locale - Optional locale string (e.g., 'en-US', 'zh-CN') * @returns Formatted date string * * @example * formatUnixTimestamp(1735555200) // "Dec 30, 2024" + * formatUnixTimestamp(1735555200, "zh-CN") // "12月30日, 2024年" */ -export function formatUnixTimestamp(timestamp: number): string { +export function formatUnixTimestamp(timestamp: number, locale?: string): string { const date = new Date(timestamp * 1000); const now = new Date(); + const effectiveLocale = locale || navigator.language || 'en-US'; + const isZhCN = effectiveLocale.startsWith('zh'); // If it's today, show time if (isToday(date)) { - return formatTime(date); + return formatTime(date, effectiveLocale); } // If it's yesterday if (isYesterday(date)) { - return `Yesterday, ${formatTime(date)}`; + const yesterdayLabel = isZhCN ? '昨天' : 'Yesterday'; + return `${yesterdayLabel}, ${formatTime(date, effectiveLocale)}`; } // If it's within the last week, show day of week if (isWithinWeek(date)) { - return `${getDayName(date)}, ${formatTime(date)}`; + return `${getDayName(date, effectiveLocale)}, ${formatTime(date, effectiveLocale)}`; } // If it's this year, don't show year if (date.getFullYear() === now.getFullYear()) { - return date.toLocaleDateString('en-US', { + return date.toLocaleDateString(effectiveLocale, { month: 'short', day: 'numeric' }); } // Otherwise show full date - return date.toLocaleDateString('en-US', { + return date.toLocaleDateString(effectiveLocale, { month: 'short', day: 'numeric', year: 'numeric' @@ -44,14 +49,17 @@ export function formatUnixTimestamp(timestamp: number): string { /** * Formats an ISO timestamp string to a human-readable date * @param isoString - ISO timestamp string + * @param locale - Optional locale string (e.g., 'en-US', 'zh-CN') * @returns Formatted date string * * @example * formatISOTimestamp("2025-01-04T10:13:29.000Z") // "Jan 4, 2025" + * formatISOTimestamp("2025-01-04T10:13:29.000Z", "zh-CN") // "1月4日, 2025" */ -export function formatISOTimestamp(isoString: string): string { +export function formatISOTimestamp(isoString: string, locale?: string): string { const date = new Date(isoString); - return formatUnixTimestamp(Math.floor(date.getTime() / 1000)); + const effectiveLocale = locale || navigator.language || 'en-US'; + return formatUnixTimestamp(Math.floor(date.getTime() / 1000), effectiveLocale); } /** @@ -76,11 +84,14 @@ export function getFirstLine(text: string): string { } // Helper functions -function formatTime(date: Date): string { - return date.toLocaleTimeString('en-US', { +function formatTime(date: Date, locale?: string): string { + const effectiveLocale = locale || navigator.language || 'en-US'; + const isZhCN = effectiveLocale.startsWith('zh'); + + return date.toLocaleTimeString(effectiveLocale, { hour: 'numeric', minute: '2-digit', - hour12: true + hour12: !isZhCN // Chinese typically uses 24-hour format }); } @@ -101,22 +112,26 @@ function isWithinWeek(date: Date): boolean { return date > weekAgo; } -function getDayName(date: Date): string { - return date.toLocaleDateString('en-US', { weekday: 'long' }); +function getDayName(date: Date, locale?: string): string { + const effectiveLocale = locale || navigator.language || 'en-US'; + return date.toLocaleDateString(effectiveLocale, { weekday: 'long' }); } /** * Formats a timestamp to a relative time string (e.g., "2 hours ago", "3 days ago") * @param timestamp - Unix timestamp in milliseconds + * @param locale - Optional locale string (e.g., 'en-US', 'zh-CN') * @returns Relative time string * * @example * formatTimeAgo(Date.now() - 3600000) // "1 hour ago" - * formatTimeAgo(Date.now() - 86400000) // "1 day ago" + * formatTimeAgo(Date.now() - 86400000, "zh-CN") // "1小时前" */ -export function formatTimeAgo(timestamp: number): string { +export function formatTimeAgo(timestamp: number, locale?: string): string { const now = Date.now(); const diff = now - timestamp; + const effectiveLocale = locale || navigator.language || 'en-US'; + const isZhCN = effectiveLocale.startsWith('zh'); const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); @@ -126,27 +141,51 @@ export function formatTimeAgo(timestamp: number): string { const months = Math.floor(days / 30); const years = Math.floor(days / 365); - if (years > 0) { - return years === 1 ? '1 year ago' : `${years} years ago`; + if (isZhCN) { + if (years > 0) { + return `${years}年前`; + } + if (months > 0) { + return `${months}个月前`; + } + if (weeks > 0) { + return `${weeks}周前`; + } + if (days > 0) { + return `${days}天前`; + } + if (hours > 0) { + return `${hours}小时前`; + } + if (minutes > 0) { + return `${minutes}分钟前`; + } + if (seconds > 0) { + return `${seconds}秒前`; + } + return '刚刚'; + } else { + if (years > 0) { + return years === 1 ? '1 year ago' : `${years} years ago`; + } + if (months > 0) { + return months === 1 ? '1 month ago' : `${months} months ago`; + } + if (weeks > 0) { + return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`; + } + if (days > 0) { + return days === 1 ? '1 day ago' : `${days} days ago`; + } + if (hours > 0) { + return hours === 1 ? '1 hour ago' : `${hours} hours ago`; + } + if (minutes > 0) { + return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`; + } + if (seconds > 0) { + return seconds === 1 ? '1 second ago' : `${seconds} seconds ago`; + } + return 'just now'; } - if (months > 0) { - return months === 1 ? '1 month ago' : `${months} months ago`; - } - if (weeks > 0) { - return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`; - } - if (days > 0) { - return days === 1 ? '1 day ago' : `${days} days ago`; - } - if (hours > 0) { - return hours === 1 ? '1 hour ago' : `${hours} hours ago`; - } - if (minutes > 0) { - return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`; - } - if (seconds > 0) { - return seconds === 1 ? '1 second ago' : `${seconds} seconds ago`; - } - - return 'just now'; }