44
55use crate :: database:: { lock_conn, Database } ;
66use crate :: error:: AppError ;
7- use chrono:: { Duration , Utc } ;
7+ use chrono:: { Duration , Local , TimeZone } ;
88use rusqlite:: { params, Connection , OptionalExtension } ;
99use serde:: { Deserialize , Serialize } ;
1010use serde_json:: Value ;
@@ -186,8 +186,17 @@ impl Database {
186186 let conn = lock_conn ! ( self . conn) ;
187187
188188 if days <= 1 {
189+ let today = Local :: now ( ) . date_naive ( ) ;
190+ let start_of_today = today. and_hms_opt ( 0 , 0 , 0 ) . unwrap ( ) ;
191+ // 使用 earliest() 处理 DST 切换时的歧义时间,fallback 到当前时间减一天
192+ let start_ts = Local
193+ . from_local_datetime ( & start_of_today)
194+ . earliest ( )
195+ . unwrap_or_else ( || Local :: now ( ) - Duration :: days ( 1 ) )
196+ . timestamp ( ) ;
197+
189198 let sql = "SELECT
190- strftime('%Y-%m-%dT%H:00:00Z ', datetime(created_at, 'unixepoch')) as bucket,
199+ strftime('%Y-%m-%dT%H:00:00 ', datetime(created_at, 'unixepoch', 'localtime ')) as bucket,
191200 COUNT(*) as request_count,
192201 COALESCE(SUM(CAST(total_cost_usd AS REAL)), 0) as total_cost,
193202 COALESCE(SUM(input_tokens + output_tokens), 0) as total_tokens,
@@ -196,12 +205,12 @@ impl Database {
196205 COALESCE(SUM(cache_creation_tokens), 0) as total_cache_creation_tokens,
197206 COALESCE(SUM(cache_read_tokens), 0) as total_cache_read_tokens
198207 FROM proxy_request_logs
199- WHERE created_at >= strftime('%s', 'now', '-1 day')
208+ WHERE created_at >= ?
200209 GROUP BY bucket
201210 ORDER BY bucket ASC" ;
202211
203212 let mut stmt = conn. prepare ( sql) ?;
204- let rows = stmt. query_map ( [ ] , |row| {
213+ let rows = stmt. query_map ( [ start_ts ] , |row| {
205214 Ok ( DailyStats {
206215 date : row. get ( 0 ) ?,
207216 request_count : row. get :: < _ , i64 > ( 1 ) ? as u64 ,
@@ -221,12 +230,11 @@ impl Database {
221230 }
222231
223232 let mut stats = Vec :: new ( ) ;
224- let today = Utc :: now ( ) . date_naive ( ) ;
225233 for hour in 0 ..24 {
226234 let bucket = today
227235 . and_hms_opt ( hour, 0 , 0 )
228236 . unwrap ( )
229- . format ( "%Y-%m-%dT%H:00:00Z " )
237+ . format ( "%Y-%m-%dT%H:00:00 " )
230238 . to_string ( ) ;
231239
232240 if let Some ( stat) = buckets. remove ( & bucket) {
@@ -246,8 +254,19 @@ impl Database {
246254 }
247255 Ok ( stats)
248256 } else {
257+ let today = Local :: now ( ) . date_naive ( ) ;
258+ let start_day =
259+ today - Duration :: days ( ( days. saturating_sub ( 1 ) ) as i64 ) ;
260+ let start_of_window = start_day. and_hms_opt ( 0 , 0 , 0 ) . unwrap ( ) ;
261+ // 使用 earliest() 处理 DST 切换时的歧义时间,fallback 到当前时间减 days 天
262+ let start_ts = Local
263+ . from_local_datetime ( & start_of_window)
264+ . earliest ( )
265+ . unwrap_or_else ( || Local :: now ( ) - Duration :: days ( days as i64 ) )
266+ . timestamp ( ) ;
267+
249268 let sql = "SELECT
250- date( created_at, 'unixepoch') as bucket,
269+ strftime('%Y-%m-%dT00:00:00', datetime( created_at, 'unixepoch', 'localtime') ) as bucket,
251270 COUNT(*) as request_count,
252271 COALESCE(SUM(CAST(total_cost_usd AS REAL)), 0) as total_cost,
253272 COALESCE(SUM(input_tokens + output_tokens), 0) as total_tokens,
@@ -256,12 +275,12 @@ impl Database {
256275 COALESCE(SUM(cache_creation_tokens), 0) as total_cache_creation_tokens,
257276 COALESCE(SUM(cache_read_tokens), 0) as total_cache_read_tokens
258277 FROM proxy_request_logs
259- WHERE created_at >= strftime('%s', 'now', ?)
278+ WHERE created_at >= ?
260279 GROUP BY bucket
261280 ORDER BY bucket ASC" ;
262281
263282 let mut stmt = conn. prepare ( sql) ?;
264- let rows = stmt. query_map ( [ format ! ( "-{days} days" ) ] , |row| {
283+ let rows = stmt. query_map ( [ start_ts ] , |row| {
265284 Ok ( DailyStats {
266285 date : row. get ( 0 ) ?,
267286 request_count : row. get :: < _ , i64 > ( 1 ) ? as u64 ,
@@ -281,12 +300,10 @@ impl Database {
281300 }
282301
283302 let mut stats = Vec :: new ( ) ;
284- let start_day =
285- Utc :: now ( ) . date_naive ( ) - Duration :: days ( ( days. saturating_sub ( 1 ) ) as i64 ) ;
286303
287304 for i in 0 ..days {
288305 let day = start_day + Duration :: days ( i as i64 ) ;
289- let key = day. format ( "%Y-%m-%d " ) . to_string ( ) ;
306+ let key = day. format ( "%Y-%m-%dT00:00:00 " ) . to_string ( ) ;
290307 if let Some ( stat) = map. remove ( & key) {
291308 stats. push ( stat) ;
292309 } else {
@@ -617,7 +634,7 @@ impl Database {
617634 "SELECT COALESCE(SUM(CAST(total_cost_usd AS REAL)), 0)
618635 FROM proxy_request_logs
619636 WHERE provider_id = ? AND app_type = ?
620- AND date(created_at, 'unixepoch') = date('now')" ,
637+ AND date(datetime( created_at, 'unixepoch', 'localtime')) = date('now', 'localtime ')" ,
621638 params ! [ provider_id, app_type] ,
622639 |row| row. get ( 0 ) ,
623640 )
@@ -629,7 +646,7 @@ impl Database {
629646 "SELECT COALESCE(SUM(CAST(total_cost_usd AS REAL)), 0)
630647 FROM proxy_request_logs
631648 WHERE provider_id = ? AND app_type = ?
632- AND strftime('%Y-%m', created_at, 'unixepoch') = strftime('%Y-%m', 'now')" ,
649+ AND strftime('%Y-%m', datetime( created_at, 'unixepoch', 'localtime')) = strftime('%Y-%m', 'now', 'localtime ')" ,
633650 params ! [ provider_id, app_type] ,
634651 |row| row. get ( 0 ) ,
635652 )
0 commit comments