futu_gateway_core/bridge/clock.rs
1// v1.4.110 P0-5 prep (T12 P1): server-clock abstraction 从
2// `handlers/mod.rs` 移到 `bridge::clock`. 移因 bridge / handlers 拆 3 crate 后
3// `ServerClock` 是 cross-domain abstraction (trd + qot 都需要 svr-time), 应
4// 在 gateway-core 同时被 trd/qot 子 crate cross-import. 原 `handlers/mod.rs`
5// 保留 `pub use` re-export 不破 sub-module use site (P3 move 后 T15 wire 一次性
6// sed).
7//
8// codex 21st-round Finding 2 (P2, v1.4.102, 2026-04-28 11:57): server-time
9// provider trait — handlers 用 server-now 替代 `Utc::now()` 本机时钟, 跟 C++
10// `INNBiz_SvrTime::GetSvrTimeStamp()` 对齐. tests 用 FixedServerClock 注入
11// 固定时间避免 wall-clock dependent (codex F3 配套).
12//
13// **Use case**: option-chain default begin/end + time-only `HH:MM:SS` 解析 +
14// 30d+1h check 都需要 "服务端今天" 而非 "本机今天" — 跨日窗口 / 时钟漂移
15// 场景能差 1 天.
16
17use std::sync::Arc;
18use std::sync::atomic::{AtomicI64, Ordering};
19use std::time::{Duration, SystemTime, UNIX_EPOCH};
20
21use parking_lot::RwLock;
22
23/// codex 21st F2/F3: server clock abstraction. production 用 `OffsetServerClock`
24/// 包 svr_time_offset_secs (从 AuthResult 派生), 测试用 `FixedServerClock`.
25pub trait ServerClock: Send + Sync + std::fmt::Debug {
26 /// 返当前 "服务端 unix timestamp (seconds)".
27 fn now_secs(&self) -> i64;
28}
29
30/// Convert a server unix timestamp into UTC DateTime.
31///
32/// `DateTime::from_timestamp` is fallible only for values outside chrono's
33/// representable range. Keep the existing epoch fallback centralized so
34/// handlers do not duplicate an infallible-looking `expect`.
35pub fn utc_datetime_from_unix_secs_or_epoch(secs: i64) -> chrono::DateTime<chrono::Utc> {
36 chrono::DateTime::<chrono::Utc>::from_timestamp(secs, 0).unwrap_or(chrono::DateTime::UNIX_EPOCH)
37}
38
39/// Return daemon wall-clock elapsed time since Unix epoch.
40///
41/// These timestamps are used for local ops/metrics bookkeeping, not
42/// backend-time-aligned business semantics. If the host clock is anomalously
43/// before Unix epoch, preserve the historical zero fallback but make it visible
44/// in logs.
45pub fn wall_clock_since_unix_epoch_or_zero(context: &'static str) -> Duration {
46 match SystemTime::now().duration_since(UNIX_EPOCH) {
47 Ok(elapsed) => elapsed,
48 Err(err) => {
49 tracing::warn!(
50 context,
51 error = %err,
52 "system wall clock is before UNIX_EPOCH; using zero duration fallback"
53 );
54 Duration::ZERO
55 }
56 }
57}
58
59/// Gateway-level server-clock runtime boundary.
60///
61/// This owns both the login-derived offset and the injectable clock provider.
62/// Startup and handler registration paths should use this bundle instead of
63/// reaching into `GatewayBridge` for raw offset/clock fields.
64#[derive(Debug)]
65pub struct ServerClockRuntime {
66 offset_secs: Arc<AtomicI64>,
67 clock: RwLock<Arc<dyn ServerClock>>,
68}
69
70impl ServerClockRuntime {
71 pub fn new() -> Self {
72 let offset_secs = Arc::new(AtomicI64::new(0));
73 let clock: Arc<dyn ServerClock> =
74 Arc::new(OffsetServerClock::new(Arc::clone(&offset_secs)));
75 Self {
76 offset_secs,
77 clock: RwLock::new(clock),
78 }
79 }
80
81 pub fn offset_secs(&self) -> Arc<AtomicI64> {
82 Arc::clone(&self.offset_secs)
83 }
84
85 pub fn store_offset_secs(&self, offset_secs: i64) {
86 self.offset_secs.store(offset_secs, Ordering::Relaxed);
87 }
88
89 pub fn clock(&self) -> Arc<dyn ServerClock> {
90 self.clock.read().clone()
91 }
92
93 pub fn set_clock_for_tests(&self, clock: Arc<dyn ServerClock>) {
94 *self.clock.write() = clock;
95 }
96}
97
98impl Default for ServerClockRuntime {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104/// Production server clock: 本机 `Utc::now()` + `svr_time_offset_secs` (从
105/// `AuthResult.svr_time_offset` 派生, AtomicI64 lock-free 跨线程读).
106#[derive(Debug, Clone)]
107pub struct OffsetServerClock {
108 offset: Arc<AtomicI64>,
109}
110
111impl OffsetServerClock {
112 pub fn new(offset: Arc<AtomicI64>) -> Self {
113 Self { offset }
114 }
115}
116
117impl ServerClock for OffsetServerClock {
118 fn now_secs(&self) -> i64 {
119 chrono::Utc::now().timestamp() + self.offset.load(Ordering::Relaxed)
120 }
121}
122
123/// Test server clock: 固定 unix ts. 用于 deterministic test assertions
124/// (e.g. "HKT 与 NY 跨日时 default 是 NY 日历日, 不是 HKT").
125#[derive(Debug, Clone)]
126pub struct FixedServerClock(pub i64);
127
128impl ServerClock for FixedServerClock {
129 fn now_secs(&self) -> i64 {
130 self.0
131 }
132}
133
134#[cfg(test)]
135mod tests;