Skip to main content

uucore/features/
time.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5
6// spell-checker:ignore (ToDO) strtime
7
8//! Set of functions related to time handling
9
10use jiff::Zoned;
11use jiff::fmt::StdIoWrite;
12use jiff::fmt::strtime::{BrokenDownTime, Config};
13use std::io::Write;
14use std::time::{SystemTime, UNIX_EPOCH};
15
16use crate::error::{UResult, USimpleError};
17use crate::show_error;
18
19/// Format the given date according to this time format style.
20fn format_zoned<W: Write>(out: &mut W, zoned: Zoned, fmt: &str) -> UResult<()> {
21    let tm = BrokenDownTime::from(&zoned);
22    let mut out = StdIoWrite(out);
23    let config = Config::new().lenient(true);
24    tm.format_with_config(&config, fmt, &mut out)
25        .map_err(|x| USimpleError::new(1, x.to_string()))
26}
27
28/// Convert a SystemTime` to a number of seconds since UNIX_EPOCH
29pub fn system_time_to_sec(time: SystemTime) -> (i64, u32) {
30    if time > UNIX_EPOCH {
31        let d = time.duration_since(UNIX_EPOCH).unwrap();
32        (d.as_secs() as i64, d.subsec_nanos())
33    } else {
34        let d = UNIX_EPOCH.duration_since(time).unwrap();
35        (-(d.as_secs() as i64), d.subsec_nanos())
36    }
37}
38
39pub mod format {
40    pub static FULL_ISO: &str = "%Y-%m-%d %H:%M:%S.%N %z";
41    pub static LONG_ISO: &str = "%Y-%m-%d %H:%M";
42    pub static ISO: &str = "%Y-%m-%d";
43}
44
45/// Sets how `format_system_time` behaves if the time cannot be converted.
46pub enum FormatSystemTimeFallback {
47    Integer,      // Just print seconds since epoch (`ls`)
48    IntegerError, // The above, and print an error (`du``)
49    Float,        // Just print seconds+nanoseconds since epoch (`stat`)
50}
51
52/// Format a `SystemTime` according to given fmt, and append to vector out.
53pub fn format_system_time<W: Write>(
54    out: &mut W,
55    time: SystemTime,
56    fmt: &str,
57    mode: FormatSystemTimeFallback,
58) -> UResult<()> {
59    let zoned: Result<Zoned, _> = time.try_into();
60    if let Ok(zoned) = zoned {
61        format_zoned(out, zoned, fmt)
62    } else {
63        // Assume that if we cannot build a Zoned element, the timestamp is
64        // out of reasonable range, just print it then.
65        // TODO: The range allowed by jiff is different from what GNU accepts,
66        // but it still far enough in the future/past to be unlikely to matter:
67        //  jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200]
68        //  GNU: Year fits in signed 32 bits (timezone dependent)
69        let (mut secs, mut nsecs) = system_time_to_sec(time);
70        match mode {
71            FormatSystemTimeFallback::Integer => out.write_all(secs.to_string().as_bytes())?,
72            FormatSystemTimeFallback::IntegerError => {
73                let str = secs.to_string();
74                show_error!("time '{str}' is out of range");
75                out.write_all(str.as_bytes())?;
76            }
77            FormatSystemTimeFallback::Float => {
78                if secs < 0 && nsecs != 0 {
79                    secs -= 1;
80                    nsecs = 1_000_000_000 - nsecs;
81                }
82                out.write_fmt(format_args!("{secs}.{nsecs:09}"))?;
83            }
84        }
85        Ok(())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::time::{FormatSystemTimeFallback, format_system_time};
92    use std::time::{Duration, UNIX_EPOCH};
93
94    // Test epoch SystemTime get printed correctly at UTC0, with 2 simple formats.
95    #[test]
96    fn test_simple_system_time() {
97        unsafe { std::env::set_var("TZ", "UTC0") };
98
99        let time = UNIX_EPOCH;
100        let mut out = Vec::new();
101        format_system_time(
102            &mut out,
103            time,
104            "%Y-%m-%d %H:%M",
105            FormatSystemTimeFallback::Integer,
106        )
107        .expect("Formatting error.");
108        assert_eq!(String::from_utf8(out).unwrap(), "1970-01-01 00:00");
109
110        let mut out = Vec::new();
111        format_system_time(
112            &mut out,
113            time,
114            "%Y-%m-%d %H:%M:%S.%N %z",
115            FormatSystemTimeFallback::Integer,
116        )
117        .expect("Formatting error.");
118        assert_eq!(
119            String::from_utf8(out).unwrap(),
120            "1970-01-01 00:00:00.000000000 +0000"
121        );
122    }
123
124    // Test that very large (positive or negative) lead to just the timestamp being printed.
125    #[test]
126    fn test_large_system_time() {
127        let time = UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_200);
128        let mut out = Vec::new();
129        format_system_time(
130            &mut out,
131            time,
132            "%Y-%m-%d %H:%M",
133            FormatSystemTimeFallback::Integer,
134        )
135        .expect("Formatting error.");
136        assert_eq!(String::from_utf8(out).unwrap(), "67768036191763200");
137
138        let time = UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_800);
139        let mut out = Vec::new();
140        format_system_time(
141            &mut out,
142            time,
143            "%Y-%m-%d %H:%M",
144            FormatSystemTimeFallback::Integer,
145        )
146        .expect("Formatting error.");
147        assert_eq!(String::from_utf8(out).unwrap(), "-67768040922076800");
148    }
149
150    // Test that very large (positive or negative) lead to just the timestamp being printed.
151    #[test]
152    fn test_large_system_time_float() {
153        let time =
154            UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_000) + Duration::from_nanos(123);
155        let mut out = Vec::new();
156        format_system_time(
157            &mut out,
158            time,
159            "%Y-%m-%d %H:%M",
160            FormatSystemTimeFallback::Float,
161        )
162        .expect("Formatting error.");
163        assert_eq!(
164            String::from_utf8(out).unwrap(),
165            "67768036191763000.000000123"
166        );
167
168        let time =
169            UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_000) + Duration::from_nanos(123);
170        let mut out = Vec::new();
171        format_system_time(
172            &mut out,
173            time,
174            "%Y-%m-%d %H:%M",
175            FormatSystemTimeFallback::Float,
176        )
177        .expect("Formatting error.");
178        assert_eq!(
179            String::from_utf8(out).unwrap(),
180            "-67768040922076000.000000123"
181        );
182    }
183}