1use libc::{time_t, tm};
4
5pub type Timestamp = u64;
7
8pub type TzHhmm = i32;
10
11pub const TIMESTAMP_MAX: u64 = (((2100u64 - 1970) * 365 + 32) * 24 * 60 * 60).saturating_sub(1);
12
13pub fn tm_to_time_t(tm: &tm) -> time_t {
15 const MDAYS: [i32; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
16 let year = tm.tm_year - 70;
17 if !(0..=129).contains(&year) {
18 return -1;
19 }
20 let month = tm.tm_mon;
21 if !(0..=11).contains(&month) {
22 return -1;
23 }
24 let mut day = tm.tm_mday;
25 if month < 2 || (year + 2) % 4 != 0 {
26 day -= 1;
27 }
28 if tm.tm_hour < 0 || tm.tm_min < 0 || tm.tm_sec < 0 {
29 return -1;
30 }
31 let secs =
32 (year as i64 * 365 + (year as i64 + 1) / 4 + MDAYS[month as usize] as i64 + day as i64)
33 * 24
34 * 60
35 * 60
36 + tm.tm_hour as i64 * 60 * 60
37 + tm.tm_min as i64 * 60
38 + tm.tm_sec as i64;
39 secs as time_t
40}
41
42pub fn date_overflows(t: u64) -> bool {
43 if t >= u64::MAX {
44 return true;
45 }
46 let sys: time_t = t as time_t;
47 (t as i128) != (sys as i128) || ((t < 1) != (sys < 1))
48}
49
50pub fn gm_time_t(mut time: u64, tz: TzHhmm) -> Option<u64> {
52 let mut minutes = if tz < 0 { -tz } else { tz };
53 minutes = (minutes / 100) * 60 + (minutes % 100);
54 minutes = if tz < 0 { -minutes } else { minutes };
55 let adj = (minutes as i64) * 60;
56 if adj > 0 {
57 time = time.checked_add(adj as u64)?;
58 } else if adj < 0 {
59 let a = (-adj) as u64;
60 if time < a {
61 return None;
62 }
63 time -= a;
64 }
65 if date_overflows(time) {
66 return None;
67 }
68 Some(time)
69}
70
71pub unsafe fn time_to_tm(time: u64, tz: TzHhmm, out: *mut tm) -> Option<*mut tm> {
73 let t = gm_time_t(time, tz)?;
74 let tt = t as time_t;
75 let p = libc::gmtime_r(&tt, out);
76 if p.is_null() {
77 None
78 } else {
79 Some(p)
80 }
81}
82
83pub unsafe fn time_to_tm_local(time: u64, out: *mut tm) -> Option<*mut tm> {
85 let tt = time as time_t;
86 let p = libc::localtime_r(&tt, out);
87 if p.is_null() {
88 None
89 } else {
90 Some(p)
91 }
92}
93
94pub unsafe fn local_time_tzoffset(t: time_t, tm_out: *mut tm) -> TzHhmm {
96 let p = libc::localtime_r(&t, tm_out);
97 if p.is_null() {
98 return 0;
99 }
100 let t_local = tm_to_time_t(&*tm_out);
101 if t_local == -1 {
102 return 0;
103 }
104 let (eastwest, offset) = if (t_local as i128) < (t as i128) {
105 (-1, (t as i128) - (t_local as i128))
106 } else {
107 (1, (t_local as i128) - (t as i128))
108 };
109 let mut offset_min = (offset / 60) as i32;
110 offset_min = (offset_min % 60) + ((offset_min / 60) * 100);
111 offset_min * eastwest
112}
113
114pub fn local_tzoffset(time: u64) -> TzHhmm {
116 if date_overflows(time) {
117 return 0;
118 }
119 let t = time as time_t;
120 let mut buf = std::mem::MaybeUninit::<tm>::uninit();
121 unsafe {
122 let tm_out = buf.as_mut_ptr();
123 local_time_tzoffset(t, tm_out)
124 }
125}
126
127pub fn get_time_sec() -> i64 {
129 if let Ok(s) = std::env::var("GIT_TEST_DATE_NOW") {
130 if let Ok(v) = s.parse::<i64>() {
131 return v;
132 }
133 }
134 std::time::SystemTime::now()
135 .duration_since(std::time::UNIX_EPOCH)
136 .map(|d| d.as_secs() as i64)
137 .unwrap_or(0)
138}
139
140pub fn parse_timestamp_prefix(s: &[u8]) -> (u64, usize) {
142 let mut i = 0usize;
143 while i < s.len() && s[i].is_ascii_digit() {
144 i += 1;
145 }
146 if i == 0 {
147 return (0, 0);
148 }
149 let n = std::str::from_utf8(&s[..i])
150 .ok()
151 .and_then(|x| x.parse::<u64>().ok())
152 .unwrap_or(0);
153 (n, i)
154}
155
156pub fn atoi_bytes(s: &[u8]) -> i32 {
158 let s = trim_ascii_ws(s);
159 if s.is_empty() {
160 return 0;
161 }
162 let neg = s[0] == b'-';
163 let start = if s[0] == b'+' || s[0] == b'-' { 1 } else { 0 };
164 let mut v: i32 = 0;
165 let mut i = start;
166 while i < s.len() && s[i].is_ascii_digit() {
167 v = v.saturating_mul(10).saturating_add((s[i] - b'0') as i32);
168 i += 1;
169 }
170 if neg {
171 -v
172 } else {
173 v
174 }
175}
176
177fn trim_ascii_ws(s: &[u8]) -> &[u8] {
178 let mut a = 0;
179 let mut b = s.len();
180 while a < b && (s[a] == b' ' || s[a] == b'\t') {
181 a += 1;
182 }
183 while b > a && (s[b - 1] == b' ' || s[b - 1] == b'\t') {
184 b -= 1;
185 }
186 &s[a..b]
187}
188
189pub fn empty_tm() -> tm {
190 unsafe { std::mem::zeroed() }
191}
192
193pub fn init_tm_unknown() -> tm {
194 let mut t = unsafe { std::mem::zeroed::<tm>() };
195 t.tm_sec = -1;
196 t.tm_min = -1;
197 t.tm_hour = -1;
198 t.tm_mday = -1;
199 t.tm_mon = -1;
200 t.tm_year = -1;
201 t.tm_wday = -1;
202 t.tm_yday = -1;
203 t.tm_isdst = -1;
204 t
205}
206
207pub fn nodate(tm: &tm) -> bool {
208 (tm.tm_year & tm.tm_mon & tm.tm_mday & tm.tm_hour & tm.tm_min & tm.tm_sec) < 0
209}
210
211pub fn maybeiso8601(tm: &tm) -> bool {
212 tm.tm_hour == -1 && tm.tm_min == 0 && tm.tm_sec == 0
213}
214
215pub fn is_date_known(tm: &tm) -> bool {
216 tm.tm_year != -1 && tm.tm_mon != -1 && tm.tm_mday != -1
217}
218
219pub fn match_string(date: &[u8], pat: &str) -> usize {
220 let pb = pat.as_bytes();
221 let mut i = 0usize;
222 while i < date.len() && i < pb.len() {
223 let d = date[i];
224 let p = pb[i];
225 if d == p {
226 i += 1;
227 continue;
228 }
229 if d.eq_ignore_ascii_case(&p) {
230 i += 1;
231 continue;
232 }
233 if !d.is_ascii_alphanumeric() {
234 break;
235 }
236 return 0;
237 }
238 i
239}
240
241pub fn skip_alpha(date: &[u8]) -> usize {
242 let mut i = 1usize;
243 while i < date.len() && date[i].is_ascii_alphabetic() {
244 i += 1;
245 }
246 i
247}