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};
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            let code = if size_of::<u64>() == 8 { 0 } else { 1 };
41            Ok(TestToolDateResult::Exit(code))
42        }
43        "time_t-is64bit" => {
44            let code = if size_of::<libc::time_t>() == 8 { 0 } else { 1 };
45            Ok(TestToolDateResult::Exit(code))
46        }
47        "relative" => {
48            let mut lines = Vec::new();
49            for a in rest {
50                let t: u64 = a
51                    .parse()
52                    .map_err(|_| format!("test-tool date relative: bad integer {a}"))?;
53                let s = show::show_date_relative(t, tm::get_time_sec());
54                lines.push(format!("{a} -> {s}"));
55            }
56            Ok(TestToolDateResult::Output(lines))
57        }
58        "human" => {
59            let mut lines = Vec::new();
60            for a in rest {
61                let t: u64 = a
62                    .parse()
63                    .map_err(|_| format!("test-tool date human: bad integer {a}"))?;
64                let mut mode = DateMode::from_type(DateModeType::Human);
65                let s = show::show_date(t, 0, &mut mode);
66                show::date_mode_release(&mut mode);
67                lines.push(format!("{a} -> {s}"));
68            }
69            Ok(TestToolDateResult::Output(lines))
70        }
71        "parse" => {
72            let mut lines = Vec::new();
73            for a in rest {
74                match parse::parse_date(a) {
75                    Ok(ds) => {
76                        let parts: Vec<&str> = ds.split_whitespace().collect();
77                        if parts.len() >= 2 {
78                            let t: u64 = parts[0].parse().map_err(|_| "bad parse output")?;
79                            let tz = atoi_bytes(parts[1].as_bytes());
80                            let mut mode = DateMode::from_type(DateModeType::Iso8601);
81                            let out = show::show_date(t, tz, &mut mode);
82                            show::date_mode_release(&mut mode);
83                            lines.push(format!("{a} -> {out}"));
84                        } else {
85                            lines.push(format!("{a} -> bad"));
86                        }
87                    }
88                    Err(()) => lines.push(format!("{a} -> bad")),
89                }
90            }
91            Ok(TestToolDateResult::Output(lines))
92        }
93        "approxidate" => {
94            let mut lines = Vec::new();
95            for a in rest {
96                let mut err = 0;
97                let t = approx::approxidate_careful(a, Some(&mut err));
98                let mut mode = DateMode::from_type(DateModeType::Iso8601);
99                let out = show::show_date(t, 0, &mut mode);
100                show::date_mode_release(&mut mode);
101                lines.push(format!("{a} -> {out}"));
102            }
103            Ok(TestToolDateResult::Output(lines))
104        }
105        s if s.starts_with("show:") => {
106            let format = s.strip_prefix("show:").unwrap_or("");
107            let mut mode = parse_date_format(format).map_err(|e| e.to_string())?;
108            let mut lines = Vec::new();
109            for a in rest {
110                let b = a.as_bytes();
111                let (t, n) = parse_timestamp_prefix(b);
112                let mut rest_b = &b[n..];
113                while rest_b.first() == Some(&b' ') {
114                    rest_b = &rest_b[1..];
115                }
116                let tz = atoi_bytes(rest_b);
117                let s = show::show_date(t, tz, &mut mode);
118                lines.push(format!("{a} -> {s}"));
119            }
120            show::date_mode_release(&mut mode);
121            Ok(TestToolDateResult::Output(lines))
122        }
123        _ => Err(format!("test-tool date: unknown subcommand '{sub}'")),
124    }
125}