1use std::time::{SystemTime, UNIX_EPOCH};
17
18pub const NANOS_PER_SEC: i64 = 1_000_000_000;
20pub const NANOS_PER_DAY: i64 = 86_400 * NANOS_PER_SEC;
22
23pub const DTNS_MIN: DtNs = i64::MIN;
26pub const DTNS_MAX: DtNs = i64::MAX;
28
29pub type DtNs = i64;
31
32pub type Day = i32;
34
35pub fn days_from_civil(y: i32, m: u32, d: u32) -> Day {
39 let y = if m <= 2 { y - 1 } else { y } as i64;
40 let era = if y >= 0 { y } else { y - 399 } / 400;
41 let yoe = y - era * 400; let mp: i64 = ((m as i64) + 9) % 12; let doy = (153 * mp + 2) / 5 + (d as i64) - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; (era * 146097 + doe - 719468) as Day
46}
47
48pub fn civil_from_days(z: Day) -> (i32, u32, u32) {
50 let z = z as i64 + 719468;
51 let era = if z >= 0 { z } else { z - 146096 } / 146097;
52 let doe = z - era * 146097; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe + era * 400;
55 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = (doy - (153 * mp + 2) / 5 + 1) as u32; let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32; ((y + if m <= 2 { 1 } else { 0 }) as i32, m, d)
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct Civil {
65 pub year: i32,
67 pub month: u32,
69 pub day: u32,
71 pub hour: u32,
73 pub minute: u32,
75 pub second: u32,
77 pub nanos: u32,
79}
80
81pub fn civil_to_nanos(c: Civil) -> DtNs {
87 match try_civil_to_nanos(c) {
88 Some(t) => t,
89 None => panic!("civil 越界或超出 DtNs 可表达范围: {c:?}"),
90 }
91}
92
93pub fn try_civil_to_nanos(c: Civil) -> Option<DtNs> {
95 if !is_valid_civil(&c) {
96 return None;
97 }
98 let days = days_from_civil(c.year, c.month, c.day) as i64;
99 let secs_of_day = (c.hour as i64) * 3600 + (c.minute as i64) * 60 + c.second as i64;
100 days.checked_mul(NANOS_PER_DAY)?
101 .checked_add(secs_of_day.checked_mul(NANOS_PER_SEC)?)?
102 .checked_add(c.nanos as i64)
103}
104
105pub fn is_leap_year(year: i32) -> bool {
107 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
108}
109
110pub fn days_in_month(year: i32, month: u32) -> u32 {
112 match month {
113 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
114 4 | 6 | 9 | 11 => 30,
115 2 => {
116 if is_leap_year(year) {
117 29
118 } else {
119 28
120 }
121 }
122 _ => 0,
123 }
124}
125
126pub fn is_valid_civil(c: &Civil) -> bool {
128 (1..=12).contains(&c.month)
129 && c.day >= 1
130 && c.day <= days_in_month(c.year, c.month)
131 && c.hour < 24
132 && c.minute < 60
133 && c.second < 60
134 && c.nanos < 1_000_000_000
135}
136
137pub fn parse_civil(s: &str) -> Option<DtNs> {
142 let s = s.trim();
143 let (date_str, time_str) = match s.split_once(['T', ' ']) {
144 Some((d, t)) => (d, Some(t)),
145 None => (s, None),
146 };
147 let mut date_parts = date_str.split('-');
148 let year: i32 = date_parts.next()?.parse().ok()?;
149 let month: u32 = date_parts.next()?.parse().ok()?;
150 let day: u32 = date_parts.next()?.parse().ok()?;
151 if date_parts.next().is_some() {
152 return None;
153 }
154
155 let (hour, minute, second, nanos) = match time_str {
156 None => (0, 0, 0, 0),
157 Some(t) => {
158 let (hms, frac) = match t.split_once('.') {
159 Some((a, b)) => (a, Some(b)),
160 None => (t, None),
161 };
162 let mut it = hms.split(':');
163 let hour: u32 = it.next()?.parse().ok()?;
164 let minute: u32 = it.next().unwrap_or("0").parse().ok()?;
165 let second: u32 = it.next().unwrap_or("0").parse().ok()?;
166 if it.next().is_some() {
167 return None;
168 }
169 let nanos = match frac {
170 None => 0,
171 Some(f) => {
172 if f.is_empty() || f.len() > 9 || !f.bytes().all(|b| b.is_ascii_digit()) {
173 return None;
174 }
175 let v: u32 = f.parse().ok()?;
176 v * 10u32.pow(9 - f.len() as u32)
177 }
178 };
179 (hour, minute, second, nanos)
180 }
181 };
182
183 try_civil_to_nanos(Civil {
184 year,
185 month,
186 day,
187 hour,
188 minute,
189 second,
190 nanos,
191 })
192}
193
194pub fn civil_from_nanos(t: DtNs) -> Civil {
196 let days = t.div_euclid(NANOS_PER_DAY);
197 let secs_of_day = t.rem_euclid(NANOS_PER_DAY);
198 let (year, month, day) = civil_from_days(days as Day);
199 let total_sec = secs_of_day / NANOS_PER_SEC;
200 let nanos = (secs_of_day % NANOS_PER_SEC) as u32;
201 Civil {
202 year,
203 month,
204 day,
205 hour: (total_sec / 3600) as u32,
206 minute: (total_sec % 3600 / 60) as u32,
207 second: (total_sec % 60) as u32,
208 nanos,
209 }
210}
211
212pub fn now_nanos() -> DtNs {
214 from_system_time(SystemTime::now())
215}
216
217pub fn trading_day(t: DtNs) -> Day {
219 t.div_euclid(NANOS_PER_DAY) as Day
220}
221
222pub fn from_system_time(t: SystemTime) -> DtNs {
225 match t.duration_since(UNIX_EPOCH) {
226 Ok(d) => d.as_nanos() as DtNs,
227 Err(e) => -(e.duration().as_nanos() as DtNs),
228 }
229}
230
231pub fn format_civil(t: DtNs) -> String {
235 let c = civil_from_nanos(t);
236 let base = format!(
237 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
238 c.year, c.month, c.day, c.hour, c.minute, c.second
239 );
240 if c.nanos == 0 {
241 base
242 } else {
243 format!("{base}.{:09}", c.nanos)
244 }
245}