1use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc};
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8pub fn epoch_seconds() -> i64 {
10 SystemTime::now()
11 .duration_since(UNIX_EPOCH)
12 .unwrap_or(Duration::ZERO)
13 .as_secs() as i64
14}
15
16pub fn epoch_realtime() -> f64 {
18 let now = SystemTime::now()
19 .duration_since(UNIX_EPOCH)
20 .unwrap_or(Duration::ZERO);
21 now.as_secs() as f64 + now.subsec_nanos() as f64 * 1e-9
22}
23
24pub fn epoch_time() -> (i64, i64) {
26 let now = SystemTime::now()
27 .duration_since(UNIX_EPOCH)
28 .unwrap_or(Duration::ZERO);
29 (now.as_secs() as i64, now.subsec_nanos() as i64)
30}
31
32pub fn strftime(
34 format: &str,
35 timestamp: Option<i64>,
36 nanoseconds: Option<i64>,
37) -> Result<String, String> {
38 let (secs, nanos) = if let Some(ts) = timestamp {
39 (ts, nanoseconds.unwrap_or(0))
40 } else {
41 let (s, n) = epoch_time();
42 (s, n)
43 };
44
45 let dt: DateTime<Local> = match Local.timestamp_opt(secs, nanos as u32) {
46 chrono::LocalResult::Single(dt) => dt,
47 chrono::LocalResult::Ambiguous(dt, _) => dt,
48 chrono::LocalResult::None => return Err("unable to convert to time".to_string()),
49 };
50
51 let mut result = format.to_string();
52
53 result = result.replace("%%", "\x00");
54 result = result.replace("%Y", &dt.format("%Y").to_string());
55 result = result.replace("%y", &dt.format("%y").to_string());
56 result = result.replace("%m", &dt.format("%m").to_string());
57 result = result.replace("%d", &dt.format("%d").to_string());
58 result = result.replace("%H", &dt.format("%H").to_string());
59 result = result.replace("%M", &dt.format("%M").to_string());
60 result = result.replace("%S", &dt.format("%S").to_string());
61 result = result.replace("%j", &dt.format("%j").to_string());
62 result = result.replace("%w", &dt.format("%w").to_string());
63 result = result.replace("%u", &dt.format("%u").to_string());
64 result = result.replace("%U", &dt.format("%U").to_string());
65 result = result.replace("%W", &dt.format("%W").to_string());
66 result = result.replace("%a", &dt.format("%a").to_string());
67 result = result.replace("%A", &dt.format("%A").to_string());
68 result = result.replace("%b", &dt.format("%b").to_string());
69 result = result.replace("%B", &dt.format("%B").to_string());
70 result = result.replace("%c", &dt.format("%c").to_string());
71 result = result.replace("%x", &dt.format("%x").to_string());
72 result = result.replace("%X", &dt.format("%X").to_string());
73 result = result.replace("%p", &dt.format("%p").to_string());
74 result = result.replace("%P", &dt.format("%P").to_string());
75 result = result.replace("%Z", &dt.format("%Z").to_string());
76 result = result.replace("%z", &dt.format("%z").to_string());
77 result = result.replace("%e", &dt.format("%e").to_string());
78 result = result.replace("%k", &dt.format("%k").to_string());
79 result = result.replace("%l", &dt.format("%l").to_string());
80 result = result.replace("%n", "\n");
81 result = result.replace("%t", "\t");
82 result = result.replace("%s", &secs.to_string());
83
84 result = result.replace("%N", &format!("{:09}", nanos));
85 result = result.replace("%.N", &format!(".{:09}", nanos));
86 result = result.replace("%3N", &format!("{:03}", nanos / 1_000_000));
87 result = result.replace("%6N", &format!("{:06}", nanos / 1_000));
88 result = result.replace("%9N", &format!("{:09}", nanos));
89
90 result = result.replace('\x00', "%");
91
92 Ok(result)
93}
94
95pub fn strptime(format: &str, input: &str) -> Result<i64, String> {
97 let dt = NaiveDateTime::parse_from_str(input, format)
98 .map_err(|e| format!("format not matched: {}", e))?;
99
100 let local = Local.from_local_datetime(&dt);
101 match local {
102 chrono::LocalResult::Single(dt) => Ok(dt.timestamp()),
103 chrono::LocalResult::Ambiguous(dt, _) => Ok(dt.timestamp()),
104 chrono::LocalResult::None => Err("unable to convert to time".to_string()),
105 }
106}
107
108#[derive(Debug, Default)]
110pub struct StrftimeOptions {
111 pub no_newline: bool,
112 pub quiet: bool,
113 pub reverse: bool,
114 pub scalar: Option<String>,
115}
116
117pub fn builtin_strftime(args: &[&str], options: &StrftimeOptions) -> (i32, String) {
119 if args.is_empty() {
120 return (1, "strftime: format expected\n".to_string());
121 }
122
123 let format = args[0];
124
125 if options.reverse {
126 if args.len() < 2 {
127 return (1, "strftime: timestring expected\n".to_string());
128 }
129
130 match strptime(format, args[1]) {
131 Ok(timestamp) => {
132 if options.scalar.is_some() {
133 (0, timestamp.to_string())
134 } else {
135 (0, format!("{}\n", timestamp))
136 }
137 }
138 Err(e) => {
139 if options.quiet {
140 (1, String::new())
141 } else {
142 (1, format!("strftime: {}\n", e))
143 }
144 }
145 }
146 } else {
147 let timestamp = if args.len() > 1 {
148 match args[1].parse::<i64>() {
149 Ok(ts) => Some(ts),
150 Err(_) => {
151 return (
152 1,
153 format!("strftime: {}: invalid decimal number\n", args[1]),
154 )
155 }
156 }
157 } else {
158 None
159 };
160
161 let nanoseconds = if args.len() > 2 {
162 match args[2].parse::<i64>() {
163 Ok(ns) if ns >= 0 && ns <= 999_999_999 => Some(ns),
164 Ok(_) => {
165 return (
166 1,
167 format!("strftime: {}: invalid nanosecond value\n", args[2]),
168 )
169 }
170 Err(_) => {
171 return (
172 1,
173 format!("strftime: {}: invalid decimal number\n", args[2]),
174 )
175 }
176 }
177 } else {
178 None
179 };
180
181 match strftime(format, timestamp, nanoseconds) {
182 Ok(result) => {
183 let output = if options.no_newline || options.scalar.is_some() {
184 result
185 } else {
186 format!("{}\n", result)
187 };
188 (0, output)
189 }
190 Err(e) => (1, format!("strftime: {}\n", e)),
191 }
192 }
193}
194
195pub fn format_duration(seconds: u64) -> String {
197 let days = seconds / 86400;
198 let hours = (seconds % 86400) / 3600;
199 let mins = (seconds % 3600) / 60;
200 let secs = seconds % 60;
201
202 if days > 0 {
203 format!("{}d {}h {}m {}s", days, hours, mins, secs)
204 } else if hours > 0 {
205 format!("{}h {}m {}s", hours, mins, secs)
206 } else if mins > 0 {
207 format!("{}m {}s", mins, secs)
208 } else {
209 format!("{}s", secs)
210 }
211}
212
213pub fn get_datetime_info() -> std::collections::HashMap<String, String> {
215 let now = Local::now();
216 let mut info = std::collections::HashMap::new();
217
218 info.insert("year".to_string(), now.format("%Y").to_string());
219 info.insert("month".to_string(), now.format("%m").to_string());
220 info.insert("day".to_string(), now.format("%d").to_string());
221 info.insert("hour".to_string(), now.format("%H").to_string());
222 info.insert("minute".to_string(), now.format("%M").to_string());
223 info.insert("second".to_string(), now.format("%S").to_string());
224 info.insert("weekday".to_string(), now.format("%A").to_string());
225 info.insert("monthname".to_string(), now.format("%B").to_string());
226 info.insert("timezone".to_string(), now.format("%Z").to_string());
227 info.insert("offset".to_string(), now.format("%z").to_string());
228 info.insert("epoch".to_string(), now.timestamp().to_string());
229 info.insert(
230 "iso8601".to_string(),
231 now.format("%Y-%m-%dT%H:%M:%S%z").to_string(),
232 );
233
234 info
235}
236
237pub fn convert_timezone(timestamp: i64, to_utc: bool) -> i64 {
239 if to_utc {
240 let dt: DateTime<Local> = Local
241 .timestamp_opt(timestamp, 0)
242 .single()
243 .unwrap_or_else(|| Local::now());
244 dt.with_timezone(&Utc).timestamp()
245 } else {
246 let dt: DateTime<Utc> = Utc
247 .timestamp_opt(timestamp, 0)
248 .single()
249 .unwrap_or_else(Utc::now);
250 dt.with_timezone(&Local).timestamp()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_epoch_seconds() {
260 let secs = epoch_seconds();
261 assert!(secs > 1700000000);
262 }
263
264 #[test]
265 fn test_epoch_realtime() {
266 let rt = epoch_realtime();
267 assert!(rt > 1700000000.0);
268
269 let (secs, _) = epoch_time();
270 assert!((rt - secs as f64).abs() < 1.0);
271 }
272
273 #[test]
274 fn test_epoch_time() {
275 let (secs, nanos) = epoch_time();
276 assert!(secs > 1700000000);
277 assert!(nanos >= 0 && nanos < 1_000_000_000);
278 }
279
280 #[test]
281 fn test_strftime_basic() {
282 let result = strftime("%Y-%m-%d", Some(1700000000), None).unwrap();
283 assert!(result.contains("-"));
284
285 let result = strftime("%%", None, None).unwrap();
286 assert_eq!(result, "%");
287 }
288
289 #[test]
290 fn test_strftime_nanoseconds() {
291 let result = strftime("%N", Some(1700000000), Some(123456789)).unwrap();
292 assert_eq!(result, "123456789");
293
294 let result = strftime("%3N", Some(1700000000), Some(123456789)).unwrap();
295 assert_eq!(result, "123");
296 }
297
298 #[test]
299 fn test_strftime_epoch() {
300 let result = strftime("%s", Some(1700000000), None).unwrap();
301 assert_eq!(result, "1700000000");
302 }
303
304 #[test]
305 fn test_format_duration() {
306 assert_eq!(format_duration(45), "45s");
307 assert_eq!(format_duration(90), "1m 30s");
308 assert_eq!(format_duration(3661), "1h 1m 1s");
309 assert_eq!(format_duration(90061), "1d 1h 1m 1s");
310 }
311
312 #[test]
313 fn test_builtin_strftime() {
314 let (status, output) = builtin_strftime(&["%s"], &StrftimeOptions::default());
315 assert_eq!(status, 0);
316 assert!(!output.is_empty());
317
318 let (status, _) = builtin_strftime(&[], &StrftimeOptions::default());
319 assert_eq!(status, 1);
320 }
321
322 #[test]
323 fn test_builtin_strftime_with_timestamp() {
324 let (status, output) = builtin_strftime(&["%s", "1700000000"], &StrftimeOptions::default());
325 assert_eq!(status, 0);
326 assert!(output.contains("1700000000"));
327 }
328
329 #[test]
330 fn test_get_datetime_info() {
331 let info = get_datetime_info();
332 assert!(info.contains_key("year"));
333 assert!(info.contains_key("epoch"));
334 assert!(info.contains_key("iso8601"));
335 }
336}