1use std::{
32 convert::TryInto,
33 ffi::CString,
34 fmt,
35 mem::MaybeUninit,
36 os::raw::{c_char, c_int, c_long},
37};
38
39#[allow(non_camel_case_types)]
40type time_t = i64;
41
42pub type TimeStamp = i64;
44
45#[repr(C)]
46#[derive(Debug, Copy, Clone)]
47struct tm {
48 pub tm_sec: c_int,
49 pub tm_min: c_int,
50 pub tm_hour: c_int,
51 pub tm_mday: c_int,
52 pub tm_mon: c_int,
53 pub tm_year: c_int,
54 pub tm_wday: c_int,
55 pub tm_yday: c_int,
56 pub tm_isdst: c_int,
57 pub tm_gmtoff: c_long,
58 pub tm_zone: *mut c_char,
59}
60
61extern "C" {
62 fn gmtime_r(ts: *const time_t, tm: *mut tm) -> *mut tm;
63 fn strftime(s: *mut c_char, maxsize: usize, format: *const c_char, timeptr: *const tm)
64 -> usize;
65}
66
67#[derive(Debug, Clone, Copy, Eq, PartialEq)]
68pub enum Error {
69 TimeError,
70 FormatError,
71}
72
73impl fmt::Display for Error {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Error::TimeError => write!(f, "Time error"),
77 Error::FormatError => write!(f, "Format error"),
78 }
79 }
80}
81
82impl std::error::Error for Error {}
83
84#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
86pub struct Components {
87 pub sec: u8,
89 pub min: u8,
91 pub hour: u8,
93 pub month_day: u8,
95 pub month: u8,
97 pub year: i16,
99 pub week_day: u8,
101 pub year_day: u16,
103}
104
105pub fn components_utc(ts_seconds: TimeStamp) -> Result<Components, Error> {
107 let mut tm = MaybeUninit::<tm>::uninit();
108 if unsafe { gmtime_r(&ts_seconds, tm.as_mut_ptr() as *mut tm) }.is_null() {
109 return Err(Error::TimeError);
110 }
111 let tm = unsafe { tm.assume_init() };
112 Ok(Components {
113 sec: tm.tm_sec as _,
114 min: tm.tm_min as _,
115 hour: tm.tm_hour as _,
116 month_day: tm.tm_mday as _,
117 month: (1 + tm.tm_mon) as _,
118 year: (1900 + tm.tm_year) as _,
119 week_day: tm.tm_wday as _,
120 year_day: tm.tm_yday as _,
121 })
122}
123
124pub fn now() -> Result<TimeStamp, Error> {
126 std::time::SystemTime::now()
127 .duration_since(std::time::UNIX_EPOCH)
128 .map_err(|_| Error::TimeError)?
129 .as_secs()
130 .try_into()
131 .map_err(|_| Error::TimeError)
132}
133
134pub fn strftime_utc(format: impl AsRef<str>, ts_seconds: TimeStamp) -> Result<String, Error> {
137 let format = format.as_ref();
138 let mut tm = MaybeUninit::<tm>::uninit();
139 if unsafe { gmtime_r(&ts_seconds, tm.as_mut_ptr() as *mut tm) }.is_null() {
140 return Err(Error::TimeError);
141 }
142 let tm = unsafe { tm.assume_init() };
143
144 let format_len = format.len();
145 let format = CString::new(format).map_err(|_| Error::FormatError)?;
146 let mut buf_size = format_len;
147 let mut buf: Vec<u8> = vec![0; buf_size];
148 loop {
149 let len = unsafe {
150 strftime(
151 buf.as_mut_ptr() as *mut c_char,
152 buf_size,
153 format.as_ptr() as *const c_char,
154 &tm,
155 )
156 };
157 if len == 0 {
158 buf_size *= 2;
159 buf.resize(buf_size, 0);
160 } else {
161 buf.truncate(len);
162 return String::from_utf8(buf).map_err(|_| Error::FormatError);
163 }
164 }
165}