Skip to main content

grit_lib/git_date/
mod.rs

1//! Git-compatible date parsing and display (ported from Git `date.c`).
2
3pub mod approx;
4pub mod parse;
5pub mod show;
6pub mod tm;
7
8use show::{parse_date_format, DateMode, DateModeType};
9use std::mem::size_of;
10use tm::{atoi_bytes, parse_timestamp_prefix, Timestamp};
11
12/// Result of `test-tool date` — either lines for stdout or a process exit code (no output).
13pub enum TestToolDateResult {
14    Output(Vec<String>),
15    Exit(i32),
16}
17
18/// Run `test-tool date` (see `git/t/helper/test-date.c`).
19pub fn test_tool_date(args: &[String]) -> Result<TestToolDateResult, String> {
20    // Match Git's `test-lib.sh` (`TZ=UTC`) when harness sets `GIT_TEST_DATE_NOW` but leaves `TZ`
21    // unset (direct `sh t0006-date.sh` runs). Do not override an explicit `TZ` (e.g. `EST5`).
22    if std::env::var_os("GIT_TEST_DATE_NOW").is_some() && std::env::var_os("TZ").is_none() {
23        std::env::set_var("TZ", "UTC");
24        // POSIX: refresh libc timezone cache after changing TZ (not in all `libc` bindings).
25        unsafe extern "C" {
26            fn tzset();
27        }
28        unsafe {
29            tzset();
30        }
31    }
32    if args.is_empty() {
33        return Err("test-tool date: missing subcommand".to_string());
34    }
35    let sub = args[0].as_str();
36    let rest = &args[1..];
37
38    match sub {
39        "is64bit" => {
40            // Match Git's `test-tool date is64bit` (`test-date.c`): `sizeof(timestamp_t) == 8`.
41            let code = if size_of::<Timestamp>() == 8 { 0 } else { 1 };
42            Ok(TestToolDateResult::Exit(code))
43        }
44        "time_t-is64bit" => {
45            let code = if size_of::<libc::time_t>() == 8 { 0 } else { 1 };
46            Ok(TestToolDateResult::Exit(code))
47        }
48        "relative" => {
49            let mut lines = Vec::new();
50            for a in rest {
51                let t: u64 = a
52                    .parse()
53                    .map_err(|_| format!("test-tool date relative: bad integer {a}"))?;
54                let s = show::show_date_relative(t, tm::get_time_sec());
55                lines.push(format!("{a} -> {s}"));
56            }
57            Ok(TestToolDateResult::Output(lines))
58        }
59        "human" => {
60            let mut lines = Vec::new();
61            for a in rest {
62                let t: u64 = a
63                    .parse()
64                    .map_err(|_| format!("test-tool date human: bad integer {a}"))?;
65                let mut mode = DateMode::from_type(DateModeType::Human);
66                let s = show::show_date(t, 0, &mut mode);
67                show::date_mode_release(&mut mode);
68                lines.push(format!("{a} -> {s}"));
69            }
70            Ok(TestToolDateResult::Output(lines))
71        }
72        "parse" => {
73            let mut lines = Vec::new();
74            for a in rest {
75                match parse::parse_date(a) {
76                    Ok(ds) => {
77                        let parts: Vec<&str> = ds.split_whitespace().collect();
78                        if parts.len() >= 2 {
79                            let t: u64 = parts[0].parse().map_err(|_| "bad parse output")?;
80                            let tz = atoi_bytes(parts[1].as_bytes());
81                            let mut mode = DateMode::from_type(DateModeType::Iso8601);
82                            let out = show::show_date(t, tz, &mut mode);
83                            show::date_mode_release(&mut mode);
84                            lines.push(format!("{a} -> {out}"));
85                        } else {
86                            lines.push(format!("{a} -> bad"));
87                        }
88                    }
89                    Err(()) => lines.push(format!("{a} -> bad")),
90                }
91            }
92            Ok(TestToolDateResult::Output(lines))
93        }
94        "approxidate" => {
95            let mut lines = Vec::new();
96            for a in rest {
97                let mut err = 0;
98                let t = approx::approxidate_careful(a, Some(&mut err));
99                let mut mode = DateMode::from_type(DateModeType::Iso8601);
100                let out = show::show_date(t, 0, &mut mode);
101                show::date_mode_release(&mut mode);
102                lines.push(format!("{a} -> {out}"));
103            }
104            Ok(TestToolDateResult::Output(lines))
105        }
106        s if s.starts_with("show:") => {
107            let format = s.strip_prefix("show:").unwrap_or("");
108            let mut mode = parse_date_format(format).map_err(|e| e.to_string())?;
109            let mut lines = Vec::new();
110            for a in rest {
111                let b = a.as_bytes();
112                let (t, n) = parse_timestamp_prefix(b);
113                let mut rest_b = &b[n..];
114                while rest_b.first() == Some(&b' ') {
115                    rest_b = &rest_b[1..];
116                }
117                let tz = atoi_bytes(rest_b);
118                let s = show::show_date(t, tz, &mut mode);
119                lines.push(format!("{a} -> {s}"));
120            }
121            show::date_mode_release(&mut mode);
122            Ok(TestToolDateResult::Output(lines))
123        }
124        _ => Err(format!("test-tool date: unknown subcommand '{sub}'")),
125    }
126}