leenfetch_core/modules/linux/info/
os_age.rs1use std::fmt::Write;
2use std::fs;
3use std::process::Command;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::modules::enums::OsAgeShorthand;
7
8pub fn get_os_age(shorthand: OsAgeShorthand) -> Option<String> {
11 let install_epoch = read_install_epoch_seconds()?;
12 let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
13
14 let seconds = now.saturating_sub(install_epoch);
16
17 Some(format_age(seconds, shorthand))
18}
19
20fn read_install_epoch_seconds() -> Option<u64> {
25 if let Ok(md) = fs::metadata("/") {
27 if let Ok(created) = md.created() {
28 if let Ok(dur) = created.duration_since(UNIX_EPOCH) {
29 let secs = dur.as_secs();
30 if secs > 0 {
31 return Some(secs);
32 }
33 }
34 }
35 }
36
37 let out = Command::new("stat").args(["-c", "%W", "/"]).output().ok()?;
39
40 if !out.status.success() {
41 return None;
42 }
43
44 let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
45 if s == "0" || s == "-1" {
47 return None;
48 }
49
50 if let Ok(v) = s.parse::<i64>() {
52 if v > 0 {
53 return Some(v as u64);
54 }
55 }
56
57 None
58}
59
60fn format_age(seconds: u64, shorthand: OsAgeShorthand) -> String {
61 let days = seconds / 86_400;
62 let hours = (seconds / 3_600) % 24;
63 let minutes = (seconds / 60) % 60;
64
65 let mut buf = String::with_capacity(32);
66
67 match shorthand {
68 OsAgeShorthand::Full => {
69 if days > 0 {
70 let _ = write!(buf, "{} day{}, ", days, if days != 1 { "s" } else { "" });
71 }
72 if hours > 0 {
73 let _ = write!(buf, "{} hour{}, ", hours, if hours != 1 { "s" } else { "" });
74 }
75 if minutes > 0 {
76 let _ = write!(
77 buf,
78 "{} minute{}",
79 minutes,
80 if minutes != 1 { "s" } else { "" }
81 );
82 }
83 if buf.is_empty() {
84 let _ = write!(buf, "{} seconds", seconds);
85 }
86 }
87 OsAgeShorthand::Tiny => {
88 if days > 0 {
89 let _ = write!(buf, "{} days", days);
90 }
91 }
92 OsAgeShorthand::Seconds => {
93 let _ = write!(buf, "{}s", seconds);
94 }
95 }
96
97 buf.trim_end_matches([' ', ','].as_ref()).to_string()
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn formats_full_age() {
106 let result = format_age(86_400 * 2 + 3_600 + 120, OsAgeShorthand::Full);
107 assert!(
108 result.contains("2 days") && result.contains("1 hour") && result.contains("2 minutes"),
109 "unexpected format: {result}"
110 );
111 }
112
113 #[test]
114 fn formats_tiny_age() {
115 let result = format_age(86_400 * 5 + 300, OsAgeShorthand::Tiny);
116 assert_eq!(result, "5 days");
117 }
118
119 #[test]
120 fn formats_seconds_age() {
121 let result = format_age(42, OsAgeShorthand::Seconds);
122 assert_eq!(result, "42s");
123 }
124}