1use std::ffi::CString;
23use std::mem;
24
25mod c {
26 extern "C" {
27 #[cfg(unix)]
28 pub(crate) fn tzset();
29 #[cfg(windows)]
30 pub(crate) fn _tzset();
31 pub(crate) fn strftime(
32 s: *mut libc::c_char,
33 max: libc::size_t,
34 format: *const libc::c_char,
35 tm: *const libc::tm,
36 ) -> usize;
37 pub(crate) fn time(tloc: *const libc::time_t) -> libc::time_t;
38 #[cfg(unix)]
39 pub(crate) fn localtime_r(t: *const libc::time_t, tm: *mut libc::tm);
40 #[cfg(windows)]
41 pub(crate) fn _localtime64_s(tm: *mut libc::tm, t: *const libc::time_t);
42 #[cfg(unix)]
43 pub(crate) fn gmtime_r(t: *const libc::time_t, tm: *mut libc::tm);
44 #[cfg(windows)]
45 pub(crate) fn _gmtime64_s(tm: *mut libc::tm, t: *const libc::time_t);
46 }
47}
48
49pub fn get_local_tm_from_epoch(epoch: libc::time_t) -> libc::tm {
51 unsafe {
52 let mut now: libc::tm = mem::zeroed();
53 #[cfg(unix)]
54 c::localtime_r(&epoch, &mut now);
55 #[cfg(windows)]
56 c::_localtime64_s(&mut now, &epoch);
57 now
58 }
59}
60
61pub fn get_gmt_tm_from_epoch(epoch: libc::time_t) -> libc::tm {
63 unsafe {
64 let mut now: libc::tm = mem::zeroed();
65 #[cfg(unix)]
66 c::gmtime_r(&epoch, &mut now);
67 #[cfg(windows)]
68 c::_gmtime64_s(&mut now, &epoch);
69 now
70 }
71}
72
73fn strftime(format: &str, tm: &libc::tm) -> String {
75 let f = CString::new(format).unwrap();
76 let buf = [0_u8; 100];
77 let l: usize = unsafe { c::strftime(buf.as_ptr() as _, buf.len(), f.as_ptr() as *const _, tm) };
78 std::string::String::from_utf8_lossy(&buf[..l]).to_string()
79}
80
81pub fn strftime_local(format: &str, epoch: libc::time_t) -> String {
83 let tm = get_local_tm_from_epoch(epoch);
84 strftime(format, &tm)
85}
86
87pub fn strftime_gmt(format: &str, epoch: libc::time_t) -> String {
89 let tm = get_gmt_tm_from_epoch(epoch);
90 strftime(format, &tm)
91}
92
93pub fn set_locale() {
95 unsafe {
96 libc::setlocale(libc::LC_ALL, b"\0".as_ptr() as _);
97 }
98}
99
100pub fn tz_set() {
102 unsafe {
103 #[cfg(unix)]
104 c::tzset();
105 #[cfg(windows)]
106 c::_tzset();
107 }
108}
109
110pub fn epoch() -> libc::time_t {
112 unsafe { c::time(std::ptr::null()) }
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::*;
118
119 const EPOCH: libc::time_t = 1_565_151_596;
120
121 #[test]
122 #[cfg(unix)]
123 fn format_time_and_date_in_gmt_and_cest() {
124 use std::env;
125
126 env::set_var("LC_ALL", "en_US.UTF-8");
127 env::set_var("TZ", "Europe/Brussels");
128
129 tz_set();
130 set_locale();
131
132 let gmt = strftime_gmt("%c", EPOCH);
133 let local = strftime_local("%c", EPOCH);
134 #[cfg(target_os = "linux")]
135 assert_eq!(gmt, "Wed 07 Aug 2019 04:19:56 AM GMT");
136 #[cfg(target_os = "macos")]
137 assert_eq!(gmt, "Wed Aug 7 04:19:56 2019");
138 #[cfg(target_os = "linux")]
139 assert_eq!(local, "Wed 07 Aug 2019 06:19:56 AM CEST");
140 #[cfg(target_os = "macos")]
141 assert_eq!(local, "Wed Aug 7 06:19:56 2019");
142
143 env::set_var("LC_ALL", "fr_BE.UTF-8");
144 env::set_var("TZ", "Europe/Brussels");
145
146 tz_set();
147 set_locale();
148
149 let gmt = strftime_gmt("%c", EPOCH);
150 let local = strftime_local("%c", EPOCH);
151 #[cfg(target_os = "linux")]
152 assert_eq!(gmt, "mer 07 aoû 2019 04:19:56 GMT");
153 #[cfg(target_os = "macos")]
154 assert_eq!(gmt, "Mer 7 aoû 04:19:56 2019");
155 #[cfg(target_os = "linux")]
156 assert_eq!(local, "mer 07 aoû 2019 06:19:56 CEST");
157 #[cfg(target_os = "macos")]
158 assert_eq!(local, "Mer 7 aoû 06:19:56 2019");
159 }
160
161 #[test]
166 #[cfg(windows)]
167 fn format_time_and_date_on_windows() {
168 tz_set();
169 set_locale();
170
171 let gmt = strftime_gmt("%c", EPOCH);
172 let local = strftime_local("%c", EPOCH);
173 #[cfg(target_os = "windows")]
174 assert_eq!(gmt, "8/7/2019 4:19:56 AM");
175 #[cfg(target_os = "windows")]
176 assert_eq!(local, "8/7/2019 4:19:56 AM");
177 }
178}