1use chrono::DateTime;
11
12#[cfg(target_arch = "wasm32")]
20pub async fn sleep_ms(ms: u32) {
21 use wasm_bindgen::JsCast;
22 use wasm_bindgen_futures::JsFuture;
23 let promise = js_sys::Promise::new(&mut |resolve, _| {
24 let delay = ms.min(i32::MAX as u32) as i32;
26 let window = web_sys::window().expect("sleep_ms 必须在浏览器上下文中调用:无 window");
27 window
28 .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve.unchecked_into(), delay)
29 .expect("setTimeout with a number delay cannot fail per WebIDL");
30 });
31 let _ = JsFuture::from(promise).await;
32}
33
34#[cfg(all(feature = "server", not(target_arch = "wasm32")))]
41pub async fn sleep_ms(ms: u32) {
42 tokio::time::sleep(std::time::Duration::from_millis(ms as u64)).await;
43}
44
45#[cfg(all(not(feature = "server"), not(target_arch = "wasm32")))]
51pub async fn sleep_ms(_ms: u32) {
52 panic!("sleep_ms 在非 wasm32 且非 server 的无效构建组合下被调用:请检查 feature 配置");
53}
54
55pub fn now_millis() -> i64 {
59 #[cfg(target_arch = "wasm32")]
60 {
61 js_sys::Date::now() as i64
62 }
63 #[cfg(not(target_arch = "wasm32"))]
64 {
65 chrono::Utc::now().timestamp_millis()
66 }
67}
68
69pub fn relative_label_from_millis(delta_millis: i64, created_iso: &str) -> (String, String) {
77 let seconds = delta_millis / 1000;
78
79 let label = if seconds < 60 {
80 "刚刚".to_string()
81 } else {
82 let minutes = seconds / 60;
83 if minutes < 60 {
84 format!("{} 分钟前", minutes)
85 } else {
86 let hours = minutes / 60;
87 if hours < 24 {
88 format!("{} 小时前", hours)
89 } else {
90 let days = hours / 24;
91 if days < 30 {
92 format!("{} 天前", days)
93 } else {
94 String::new()
96 }
97 }
98 }
99 };
100
101 let absolute = DateTime::parse_from_rfc3339(created_iso)
103 .map(|dt| dt.format("%Y-%m-%d").to_string())
104 .unwrap_or_default();
105
106 let label = if label.is_empty() {
107 absolute.clone()
108 } else {
109 label
110 };
111 (label, absolute)
112}
113
114pub fn format_relative_time_iso(created_iso: &str) -> String {
118 let Ok(dt) = DateTime::parse_from_rfc3339(created_iso) else {
120 return "刚刚".to_string();
121 };
122 let delta_millis = now_millis() - dt.timestamp_millis();
123 relative_label_from_millis(delta_millis, created_iso).0
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 const ISO: &str = "2026-06-22T05:43:57.565+00:00";
131
132 #[test]
133 fn relative_label_just_now_under_60s() {
134 let (label, _) = relative_label_from_millis(0, ISO);
135 assert_eq!(label, "刚刚");
136 let (label, _) = relative_label_from_millis(59_999, ISO);
137 assert_eq!(label, "刚刚");
138 }
139
140 #[test]
141 fn relative_label_minutes() {
142 let (label, _) = relative_label_from_millis(60_000, ISO);
143 assert_eq!(label, "1 分钟前");
144 let (label, _) = relative_label_from_millis(5 * 60_000, ISO);
145 assert_eq!(label, "5 分钟前");
146 let (label, _) = relative_label_from_millis(59 * 60_000, ISO);
147 assert_eq!(label, "59 分钟前");
148 }
149
150 #[test]
151 fn relative_label_hours() {
152 let (label, _) = relative_label_from_millis(60 * 60_000, ISO);
153 assert_eq!(label, "1 小时前");
154 let (label, _) = relative_label_from_millis(3 * 3_600_000, ISO);
155 assert_eq!(label, "3 小时前");
156 let (label, _) = relative_label_from_millis(23 * 3_600_000, ISO);
157 assert_eq!(label, "23 小时前");
158 }
159
160 #[test]
161 fn relative_label_days() {
162 let (label, _) = relative_label_from_millis(24 * 3_600_000, ISO);
163 assert_eq!(label, "1 天前");
164 let (label, _) = relative_label_from_millis(7 * 24 * 3_600_000, ISO);
165 assert_eq!(label, "7 天前");
166 let (label, _) = relative_label_from_millis(29 * 24 * 3_600_000, ISO);
167 assert_eq!(label, "29 天前");
168 }
169
170 #[test]
171 fn relative_label_falls_back_to_date_over_30_days() {
172 let (label, absolute) = relative_label_from_millis(60 * 24 * 3_600_000, ISO);
173 assert_eq!(label, "2026-06-22");
174 assert_eq!(absolute, "2026-06-22");
175 }
176
177 #[test]
178 fn relative_label_future_falls_back_to_just_now() {
179 let (label, _) = relative_label_from_millis(-5_000, ISO);
181 assert_eq!(label, "刚刚");
182 }
183
184 #[test]
185 fn relative_label_invalid_iso_still_returns_absolute_empty() {
186 let (label, absolute) = relative_label_from_millis(0, "not-a-date");
188 assert_eq!(label, "刚刚");
189 assert_eq!(absolute, "");
190 }
191
192 #[test]
193 fn format_relative_time_iso_invalid_iso_falls_back() {
194 assert_eq!(format_relative_time_iso("not-a-date"), "刚刚");
196 }
197}