@@ -17,7 +17,7 @@ mod url_mapping;
1717
1818use std:: {
1919 collections:: { HashMap , VecDeque } ,
20- env,
20+ env, fmt ,
2121 net:: SocketAddr ,
2222 sync:: Arc ,
2323 time:: Duration ,
@@ -40,13 +40,77 @@ use tokio::{
4040 task:: JoinHandle ,
4141} ;
4242use tower_http:: services:: { ServeDir , ServeFile } ;
43- use tracing_subscriber:: { EnvFilter , layer:: SubscriberExt , util:: SubscriberInitExt } ;
43+ use tracing_subscriber:: {
44+ EnvFilter ,
45+ fmt:: { format:: Writer , time:: FormatTime } ,
46+ layer:: SubscriberExt ,
47+ util:: SubscriberInitExt ,
48+ } ;
4449
4550use crate :: cache:: DirectLinkCache ;
4651use activity_log:: { ActivityKind , ActivityLevel , ActivityLogStore , PlaybackLogRecord } ;
4752
4853const MAX_PROXY_BODY_BYTES : usize = 64 * 1024 * 1024 ;
4954
55+ #[ derive( Clone ) ]
56+ struct TzTimer {
57+ offset_seconds : i32 ,
58+ }
59+
60+ impl TzTimer {
61+ fn from_env ( ) -> Self {
62+ Self {
63+ offset_seconds : tz_offset_seconds ( & env:: var ( "TZ" ) . unwrap_or_default ( ) ) ,
64+ }
65+ }
66+ }
67+
68+ impl FormatTime for TzTimer {
69+ fn format_time ( & self , writer : & mut Writer < ' _ > ) -> fmt:: Result {
70+ let now = chrono:: Utc :: now ( ) + chrono:: Duration :: seconds ( self . offset_seconds . into ( ) ) ;
71+ write ! ( writer, "{}" , now. format( "%Y-%m-%d %H:%M:%S%.3f" ) )
72+ }
73+ }
74+
75+ fn tz_offset_seconds ( tz : & str ) -> i32 {
76+ let tz = tz. trim ( ) ;
77+ if tz. eq_ignore_ascii_case ( "Asia/Shanghai" )
78+ || tz. eq_ignore_ascii_case ( "Asia/Chongqing" )
79+ || tz. eq_ignore_ascii_case ( "Asia/Harbin" )
80+ || tz. eq_ignore_ascii_case ( "Asia/Urumqi" )
81+ || tz. eq_ignore_ascii_case ( "PRC" )
82+ {
83+ return 8 * 3600 ;
84+ }
85+ if tz. eq_ignore_ascii_case ( "UTC" ) || tz. eq_ignore_ascii_case ( "Etc/UTC" ) || tz == "Z" {
86+ return 0 ;
87+ }
88+ parse_utc_offset ( tz) . unwrap_or ( 0 )
89+ }
90+
91+ fn parse_utc_offset ( value : & str ) -> Option < i32 > {
92+ let value = value
93+ . strip_prefix ( "UTC" )
94+ . or_else ( || value. strip_prefix ( "GMT" ) )
95+ . unwrap_or ( value)
96+ . trim ( ) ;
97+ let sign = match value. as_bytes ( ) . first ( ) ? {
98+ b'+' => 1 ,
99+ b'-' => -1 ,
100+ _ => return None ,
101+ } ;
102+ let value = & value[ 1 ..] ;
103+ let ( hours, minutes) = if let Some ( ( hours, minutes) ) = value. split_once ( ':' ) {
104+ ( hours. parse :: < i32 > ( ) . ok ( ) ?, minutes. parse :: < i32 > ( ) . ok ( ) ?)
105+ } else {
106+ ( value. parse :: < i32 > ( ) . ok ( ) ?, 0 )
107+ } ;
108+ if !( 0 ..=23 ) . contains ( & hours) || !( 0 ..=59 ) . contains ( & minutes) {
109+ return None ;
110+ }
111+ Some ( sign * ( hours * 3600 + minutes * 60 ) )
112+ }
113+
50114#[ derive( Clone ) ]
51115struct AppState {
52116 config : Arc < RwLock < Config > > ,
@@ -211,7 +275,7 @@ impl ProxyManager {
211275async fn main ( ) -> AppResult < ( ) > {
212276 tracing_subscriber:: registry ( )
213277 . with ( EnvFilter :: try_from_default_env ( ) . unwrap_or_else ( |_| "emby302gateway_rs=info" . into ( ) ) )
214- . with ( tracing_subscriber:: fmt:: layer ( ) )
278+ . with ( tracing_subscriber:: fmt:: layer ( ) . with_timer ( TzTimer :: from_env ( ) ) )
215279 . init ( ) ;
216280
217281 let settings_store = SettingsStore :: open_default ( ) ?;
@@ -2052,6 +2116,13 @@ mod tests {
20522116 . as_nanos ( )
20532117 }
20542118
2119+ #[ test]
2120+ fn log_timer_respects_common_tz_values ( ) {
2121+ assert_eq ! ( tz_offset_seconds( "Asia/Shanghai" ) , 8 * 3600 ) ;
2122+ assert_eq ! ( tz_offset_seconds( "UTC+08:00" ) , 8 * 3600 ) ;
2123+ assert_eq ! ( tz_offset_seconds( "UTC" ) , 0 ) ;
2124+ }
2125+
20552126 async fn spawn_mock_server < F , Fut > ( handler : F ) -> String
20562127 where
20572128 F : Fn ( Request ) -> Fut + Clone + Send + Sync + ' static ,
0 commit comments