]> git.proxmox.com Git - pmg-log-tracker.git/blame - src/time.rs
cleanup: remove unused strftime function
[pmg-log-tracker.git] / src / time.rs
CommitLineData
8f1719ee
WB
1//! Time support library.
2//!
3//! Contains wrappers for `strftime`, `strptime`, `libc::localtime_r`.
4
5use std::ffi::{CStr, CString};
6use std::fmt;
7use std::mem::MaybeUninit;
8
9use anyhow::{bail, format_err, Error};
10
11/// Calender month index to *non-leap-year* day-of-the-year.
12pub const CAL_MTOD: [i64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
13
14/// Shortcut for generating an `&'static CStr`.
15#[macro_export]
16macro_rules! c_str {
17 ($data:expr) => {{
18 let bytes = concat!($data, "\0");
19 unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(bytes.as_bytes()) }
20 }};
21}
22
23// Wrapper around `libc::tm` providing `Debug` and some helper methods.
24pub struct Tm(libc::tm);
25
26impl std::ops::Deref for Tm {
27 type Target = libc::tm;
28
29 fn deref(&self) -> &libc::tm {
30 &self.0
31 }
32}
33
34impl std::ops::DerefMut for Tm {
35 fn deref_mut(&mut self) -> &mut libc::tm {
36 &mut self.0
37 }
38}
39
40// (or add libc feature 'extra-traits' but that's the only struct we need it for...)
41impl fmt::Debug for Tm {
42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 f.debug_struct("Tm")
44 .field("tm_sec", &self.tm_sec)
45 .field("tm_min", &self.tm_min)
46 .field("tm_hour", &self.tm_hour)
47 .field("tm_mday", &self.tm_mday)
48 .field("tm_mon", &self.tm_mon)
49 .field("tm_year", &self.tm_year)
50 .field("tm_wday", &self.tm_wday)
51 .field("tm_yday", &self.tm_yday)
52 .field("tm_isdst", &self.tm_isdst)
53 .field("tm_gmtoff", &self.tm_gmtoff)
54 .field("tm_zone", &self.tm_zone)
55 .finish()
56 }
57}
58
59/// These are now exposed via `libc`, but they're part of glibc.
60mod imports {
61 extern "C" {
8f1719ee
WB
62 pub fn strptime(
63 s: *const libc::c_char,
64 format: *const libc::c_char,
65 tm: *mut libc::tm,
66 ) -> *const libc::c_char;
67 }
68}
69
70impl Tm {
71 /// A zero-initialized time struct.
72 #[inline(always)]
73 pub fn zero() -> Self {
74 unsafe { std::mem::zeroed() }
75 }
76
77 /// Get the current time in the local timezone.
78 pub fn now_local() -> Result<Self, Error> {
79 Self::at_local(unsafe { libc::time(std::ptr::null_mut()) })
80 }
81
82 /// Get a local time from a unix time stamp.
83 pub fn at_local(time: libc::time_t) -> Result<Self, Error> {
84 let mut out = MaybeUninit::<libc::tm>::uninit();
85 Ok(Self(unsafe {
86 if libc::localtime_r(&time, out.as_mut_ptr()).is_null() {
87 bail!("failed to convert timestamp to local time");
88 }
89 out.assume_init()
90 }))
91 }
92
93 /// Assume this is an UTC time and convert it to a unix time stamp.
94 ///
95 /// Equivalent to `timegm(3)`, the gmt equivalent of `mktime(3)`.
96 pub fn as_utc_to_epoch(&self) -> libc::time_t {
97 let mut year = self.0.tm_year as i64 + 1900;
98 let mon = self.0.tm_mon;
99
100 let mut res: libc::time_t = (year - 1970) * 365 + CAL_MTOD[mon as usize];
101
102 if mon <= 1 {
103 year -= 1;
104 }
105
106 res += (year - 1968) / 4;
107 res -= (year - 1900) / 100;
108 res += (year - 1600) / 400;
109
110 res += (self.0.tm_mday - 1) as i64;
111 res = res * 24 + self.0.tm_hour as i64;
112 res = res * 60 + self.0.tm_min as i64;
113 res = res * 60 + self.0.tm_sec as i64;
114
115 res
116 }
117}
118
8f1719ee
WB
119/// Wrapper around `strptime(3)` to parse time strings.
120pub fn strptime(time: &str, format: &CStr) -> Result<Tm, Error> {
8a5b28ff
ML
121 // zero memory because strptime does not necessarily initialize tm_isdst, tm_gmtoff and tm_zone
122 let mut out = MaybeUninit::<libc::tm>::zeroed();
8f1719ee
WB
123
124 let time = CString::new(time).map_err(|_| format_err!("time string contains nul bytes"))?;
125
126 let end = unsafe {
127 imports::strptime(
128 time.as_ptr() as *const libc::c_char,
129 format.as_ptr(),
130 out.as_mut_ptr(),
131 )
132 };
133
134 if end.is_null() {
135 bail!("failed to parse time string {:?}", time);
136 }
137
138 Ok(Tm(unsafe { out.assume_init() }))
139}
8a5b28ff
ML
140
141pub fn date_to_rfc3339(date: &str) -> Result<String, Error> {
142 // parse the YYYY-MM-DD HH:MM:SS format for the timezone info
143 let ltime = strptime(date, c_str!("%F %T"))?;
144
145 // strptime assume it is in UTC, but we want to interpret it as local time and get the timezone
146 // offset (tm_gmtoff) which we can then append to be able to parse it as rfc3339
147 let ltime = Tm::at_local(ltime.as_utc_to_epoch())?;
148
149 let mut s = date.replacen(' ', "T", 1);
150 if ltime.tm_gmtoff != 0 {
151 let sign = if ltime.tm_gmtoff < 0 { '-' } else { '+' };
152 let minutes = (ltime.tm_gmtoff / 60) % 60;
153 let hours = ltime.tm_gmtoff / (60 * 60);
154 s.push_str(&format!("{}{:02}:{:02}", sign, hours, minutes));
155 } else {
156 s.push('Z');
157 }
158 Ok(s)
159}