Files
Unlucky-Launcher/crates/core/src/launch/time.rs
T
AskaEth bc41dfcf7d
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Unlucky Launcher v0.0.1-alpha
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
2026-10-31 21:54:10 +08:00

119 lines
4.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 时间工具:RFC3339(毫秒,UTC)格式化与会话时钟。
//!
//! 会话事件流的 `ts` / `t_ms` 字段由这里产出(schema 见
//! `DevDocs/SubAgentsTalk/launch-接口约定.md` §2)。
//! 只用 `std::time`,不引入 chrono/time 依赖。
use std::time::{Instant, SystemTime, UNIX_EPOCH};
/// 把 `SystemTime` 格式化成 RFC3339 UTC 毫秒精度串,例 `2026-10-29T12:45:01.123Z`。
///
/// 早于 Unix 纪元的时间按纪元处理(不会 panic:事件流宁可有 1970 的时间戳也不能丢事件)。
pub fn format_rfc3339_millis(time: SystemTime) -> String {
let since_epoch = time.duration_since(UNIX_EPOCH).unwrap_or_default();
let secs = since_epoch.as_secs();
let millis = since_epoch.subsec_millis();
let days = i64::try_from(secs / 86_400).unwrap_or(i64::MAX);
let secs_of_day = secs % 86_400;
let (year, month, day) = civil_from_days(days);
let hour = secs_of_day / 3_600;
let minute = (secs_of_day % 3_600) / 60;
let second = secs_of_day % 60;
format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{millis:03}Z")
}
/// 当前墙上时间(便于测试替换的单一入口)。
pub fn now() -> SystemTime {
SystemTime::now()
}
/// 由“自 1970-01-01 起的天数”还原公历年月日(Howard Hinnant 的 civil_from_days)。
pub fn civil_from_days(days: i64) -> (i64, u32, u32) {
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let year = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if month <= 2 { year + 1 } else { year };
(
year,
u32::try_from(month).unwrap_or(1),
u32::try_from(day).unwrap_or(1),
)
}
/// 会话时钟:同时记录单调时刻(`t_ms`)与墙上时刻(`ts`)。
#[derive(Debug, Clone)]
pub struct SessionClock {
start_instant: Instant,
started_at: SystemTime,
}
impl SessionClock {
/// 以“现在”为起点创建时钟。
pub fn start_new() -> Self {
Self {
start_instant: Instant::now(),
started_at: now(),
}
}
/// 用给定的墙上时间与单调时刻创建(测试可注入)。
pub fn started_at(start_instant: Instant, started_at: SystemTime) -> Self {
Self {
start_instant,
started_at,
}
}
/// 会话开始的墙上时间。
pub fn started_at_wall(&self) -> SystemTime {
self.started_at
}
/// 会话开始时刻的 RFC3339 串。
pub fn started_at_iso(&self) -> String {
format_rfc3339_millis(self.started_at)
}
/// 距会话开始的毫秒数(`t_ms`)。
pub fn elapsed_ms(&self) -> u64 {
u64::try_from(self.start_instant.elapsed().as_millis()).unwrap_or(u64::MAX)
}
/// 当前墙上时间的 RFC3339 串(`ts`)。
pub fn now_iso(&self) -> String {
format_rfc3339_millis(now())
}
}
impl Default for SessionClock {
fn default() -> Self {
Self::start_new()
}
}
/// 生成会话 id:`YYYYMMDDTHHMMSS-<6 位十六进制>`。
///
/// 十六进制段由墙上时间与 pid 混合而来——不引入 rand 依赖,同一秒同一进程内也能区分
/// (调用方可在冲突时追加序号)。
pub fn make_session_id(wall: SystemTime, pid: u32) -> String {
let since_epoch = wall.duration_since(UNIX_EPOCH).unwrap_or_default();
let secs = since_epoch.as_secs();
let days = i64::try_from(secs / 86_400).unwrap_or(i64::MAX);
let (year, month, day) = civil_from_days(days);
let secs_of_day = secs % 86_400;
let hour = secs_of_day / 3_600;
let minute = (secs_of_day % 3_600) / 60;
let second = secs_of_day % 60;
let mut hash: u64 = u64::from(since_epoch.subsec_nanos()).wrapping_add(u64::from(pid));
hash ^= secs.rotate_left(17);
hash = hash.wrapping_mul(0x9E37_79B9_7F4A_7C15);
let suffix = (hash >> 40) & 0xFF_FFFF;
format!("{year:04}{month:02}{day:02}T{hour:02}{minute:02}{second:02}-{suffix:06x}")
}