Skip to main content

grit_lib/git_date/
approx.rs

1//! Git `approxidate` (ported from `date.c`).
2
3use super::parse::{match_multi_number, MONTH_NAMES, WEEKDAY_NAMES};
4use super::tm::{get_time_sec, match_string, parse_timestamp_prefix};
5use libc::{time_t, tm};
6use std::mem::MaybeUninit;
7
8fn update_tm(tm: &mut tm, now: &tm, sec: i64) -> time_t {
9    if tm.tm_mday < 0 {
10        tm.tm_mday = now.tm_mday;
11    }
12    if tm.tm_mon < 0 {
13        tm.tm_mon = now.tm_mon;
14    }
15    if tm.tm_year < 0 {
16        tm.tm_year = now.tm_year;
17        if tm.tm_mon > now.tm_mon {
18            tm.tm_year -= 1;
19        }
20    }
21    unsafe {
22        let t = libc::mktime(tm);
23        if t == -1 {
24            return -1;
25        }
26        let n = t - sec;
27        let mut out = MaybeUninit::<tm>::uninit();
28        let p = libc::localtime_r(&n, out.as_mut_ptr());
29        if p.is_null() {
30            return -1;
31        }
32        *tm = *p;
33        n
34    }
35}
36
37fn pending_number(tm: &mut tm, num: &mut i32) {
38    let number = *num;
39    if number != 0 {
40        *num = 0;
41        if tm.tm_mday < 0 && number < 32 {
42            tm.tm_mday = number;
43        } else if tm.tm_mon < 0 && number < 13 {
44            tm.tm_mon = number - 1;
45        } else if tm.tm_year < 0 {
46            if (1969..2100).contains(&number) {
47                tm.tm_year = number - 1900;
48            } else if (69..100).contains(&number) {
49                tm.tm_year = number;
50            } else if number < 38 {
51                tm.tm_year = number + 100;
52            }
53        }
54    }
55}
56
57fn date_now(tm: &mut tm, now: &tm, num: &mut i32) {
58    *num = 0;
59    let _ = update_tm(tm, now, 0);
60}
61
62fn date_yesterday(tm: &mut tm, now: &tm, num: &mut i32) {
63    *num = 0;
64    let _ = update_tm(tm, now, 24 * 60 * 60);
65}
66
67fn date_time(tm: &mut tm, now: &tm, hour: i32) {
68    if tm.tm_hour < hour {
69        let _ = update_tm(tm, now, 24 * 60 * 60);
70    }
71    tm.tm_hour = hour;
72    tm.tm_min = 0;
73    tm.tm_sec = 0;
74}
75
76fn date_midnight(tm: &mut tm, now: &tm, num: &mut i32) {
77    pending_number(tm, num);
78    date_time(tm, now, 0);
79}
80
81fn date_noon(tm: &mut tm, now: &tm, num: &mut i32) {
82    pending_number(tm, num);
83    date_time(tm, now, 12);
84}
85
86fn date_tea(tm: &mut tm, now: &tm, num: &mut i32) {
87    pending_number(tm, num);
88    date_time(tm, now, 17);
89}
90
91fn date_pm(tm: &mut tm, _now: &tm, num: &mut i32) {
92    let n = *num;
93    *num = 0;
94    let mut hour = tm.tm_hour;
95    if n != 0 {
96        hour = n;
97        tm.tm_min = 0;
98        tm.tm_sec = 0;
99    }
100    tm.tm_hour = (hour % 12) + 12;
101}
102
103fn date_am(tm: &mut tm, _now: &tm, num: &mut i32) {
104    let n = *num;
105    *num = 0;
106    let mut hour = tm.tm_hour;
107    if n != 0 {
108        hour = n;
109        tm.tm_min = 0;
110        tm.tm_sec = 0;
111    }
112    tm.tm_hour = hour % 12;
113}
114
115fn date_never(tm: &mut tm, _now: &tm, num: &mut i32) {
116    let n: time_t = 0;
117    unsafe {
118        let mut out = MaybeUninit::<tm>::uninit();
119        let p = libc::localtime_r(&n, out.as_mut_ptr());
120        if !p.is_null() {
121            *tm = *p;
122        }
123    }
124    *num = 0;
125}
126
127const NUMBER_NAME: [&str; 11] = [
128    "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
129];
130
131const TYPELEN: [(&str, i64); 5] = [
132    ("seconds", 1),
133    ("minutes", 60),
134    ("hours", 60 * 60),
135    ("days", 24 * 60 * 60),
136    ("weeks", 7 * 24 * 60 * 60),
137];
138
139fn alpha_word_len(date: &[u8]) -> usize {
140    let mut i = 0usize;
141    while i < date.len() && date[i].is_ascii_alphabetic() {
142        i += 1;
143    }
144    i
145}
146
147fn approxidate_alpha(
148    date: &[u8],
149    tm: &mut tm,
150    now: &tm,
151    num: &mut i32,
152    touched: &mut i32,
153) -> usize {
154    let end = alpha_word_len(date);
155
156    for (i, name) in MONTH_NAMES.iter().enumerate() {
157        let m = match_string(date, name);
158        if m >= 3 {
159            tm.tm_mon = i as i32;
160            *touched = 1;
161            return end;
162        }
163    }
164
165    let specials: [(&str, fn(&mut tm, &tm, &mut i32)); 8] = [
166        ("yesterday", date_yesterday),
167        ("noon", date_noon),
168        ("midnight", date_midnight),
169        ("tea", date_tea),
170        ("PM", date_pm),
171        ("AM", date_am),
172        ("never", date_never),
173        ("now", date_now),
174    ];
175    for (name, f) in specials {
176        if match_string(date, name) == name.len() {
177            f(tm, now, num);
178            *touched = 1;
179            return end;
180        }
181    }
182
183    if *num == 0 {
184        for i in 1..11 {
185            let len = NUMBER_NAME[i].len();
186            if match_string(date, NUMBER_NAME[i]) == len {
187                *num = i as i32;
188                *touched = 1;
189                return end;
190            }
191        }
192        if match_string(date, "last") == 4 {
193            *num = 1;
194            *touched = 1;
195        }
196        return end;
197    }
198
199    for (typ, len_secs) in TYPELEN {
200        let tlen = typ.len();
201        if match_string(date, typ) >= tlen.saturating_sub(1) {
202            let _ = update_tm(tm, now, len_secs * (*num as i64));
203            *num = 0;
204            *touched = 1;
205            return end;
206        }
207    }
208
209    for (i, wname) in WEEKDAY_NAMES.iter().enumerate() {
210        let m = match_string(date, wname);
211        if m >= 3 {
212            let mut n = *num - 1;
213            *num = 0;
214            let mut diff = tm.tm_wday - (i as i32);
215            if diff <= 0 {
216                n += 1;
217            }
218            diff += 7 * n;
219            let _ = update_tm(tm, now, (diff as i64) * 24 * 60 * 60);
220            *touched = 1;
221            return end;
222        }
223    }
224
225    if match_string(date, "months") >= 5 {
226        let _ = update_tm(tm, now, 0);
227        let mut n = tm.tm_mon - *num;
228        *num = 0;
229        while n < 0 {
230            n += 12;
231            tm.tm_year -= 1;
232        }
233        tm.tm_mon = n;
234        *touched = 1;
235        return end;
236    }
237
238    if match_string(date, "years") >= 4 {
239        let _ = update_tm(tm, now, 0);
240        tm.tm_year -= *num;
241        *num = 0;
242        *touched = 1;
243        return end;
244    }
245
246    end
247}
248
249fn approxidate_digit(date: &[u8], tm: &mut tm, num: &mut i32, now_sec: i64) -> usize {
250    let (number, n) = parse_timestamp_prefix(date);
251    if n == 0 {
252        return 0;
253    }
254    let end = n;
255
256    if let Some(&sep) = date.get(end) {
257        if matches!(sep, b':' | b'.' | b'/' | b'-')
258            && date.get(end + 1).is_some_and(|b| b.is_ascii_digit())
259        {
260            let m = match_multi_number(number, date, end, tm, now_sec);
261            if m != 0 {
262                return m;
263            }
264        }
265    }
266
267    if date[0] != b'0' || end <= 2 {
268        *num = number as i32;
269    }
270    end
271}
272
273/// Git `approxidate_careful` — returns Unix timestamp; on parse failure uses fuzzy parser.
274pub fn approxidate_careful(date: &str, error_ret: Option<&mut i32>) -> u64 {
275    let mut dummy = 0;
276    let er: &mut i32 = match error_ret {
277        Some(p) => p,
278        None => &mut dummy,
279    };
280    if let Ok((ts, _)) = super::parse::parse_date_basic(date) {
281        *er = 0;
282        return ts;
283    }
284    let tv_sec = get_time_sec();
285    approxidate_str(date, tv_sec, er)
286}
287
288fn approxidate_str(date: &str, time_sec: i64, error_ret: &mut i32) -> u64 {
289    let mut tm_buf = MaybeUninit::<tm>::uninit();
290    let mut now_buf = MaybeUninit::<tm>::uninit();
291    unsafe {
292        let tt = time_sec as time_t;
293        let p = libc::localtime_r(&tt, tm_buf.as_mut_ptr());
294        if p.is_null() {
295            *error_ret = 1;
296            return 0;
297        }
298        let p2 = libc::localtime_r(&tt, now_buf.as_mut_ptr());
299        if p2.is_null() {
300            *error_ret = 1;
301            return 0;
302        }
303        let mut tm = *tm_buf.as_ptr();
304        let now = *now_buf.as_ptr();
305        tm.tm_year = -1;
306        tm.tm_mon = -1;
307        tm.tm_mday = -1;
308
309        let mut number = 0i32;
310        let mut touched = 0i32;
311        let bytes = date.as_bytes();
312        let mut i = 0usize;
313        while i < bytes.len() {
314            let c = bytes[i];
315            if c == 0 || c == b'\n' {
316                break;
317            }
318            if c.is_ascii_digit() {
319                pending_number(&mut tm, &mut number);
320                let adv = approxidate_digit(&bytes[i..], &mut tm, &mut number, time_sec);
321                if adv == 0 {
322                    break;
323                }
324                i += adv;
325                touched = 1;
326                continue;
327            }
328            if c.is_ascii_alphabetic() {
329                let adv = approxidate_alpha(&bytes[i..], &mut tm, &now, &mut number, &mut touched);
330                i += adv;
331                continue;
332            }
333            i += 1;
334        }
335        pending_number(&mut tm, &mut number);
336        if touched == 0 {
337            *error_ret = 1;
338        }
339        let n = update_tm(&mut tm, &now, 0);
340        if n < 0 {
341            *error_ret = 1;
342            return 0;
343        }
344        n as u64
345    }
346}