1use chrono::{DateTime, Local, NaiveDate, SecondsFormat, Utc};
2
3pub fn local(millis: i64) -> String {
5 DateTime::<Utc>::from_timestamp_millis(millis)
6 .map(|time| {
7 time.with_timezone(&Local)
8 .format("%Y-%m-%d %H:%M")
9 .to_string()
10 })
11 .unwrap_or_default()
12}
13
14pub fn utc(millis: i64) -> String {
16 DateTime::<Utc>::from_timestamp_millis(millis)
17 .map(|time| time.to_rfc3339_opts(SecondsFormat::Secs, true))
18 .unwrap_or_default()
19}
20
21pub fn date(millis: i64) -> String {
23 DateTime::<Utc>::from_timestamp_millis(millis)
24 .map(|time| time.format("%Y-%m-%d").to_string())
25 .unwrap_or_default()
26}
27
28pub fn day_millis(value: &str) -> Option<i64> {
30 NaiveDate::parse_from_str(value, "%Y-%m-%d")
31 .ok()
32 .and_then(|day| day.and_hms_opt(0, 0, 0))
33 .map(|midnight| midnight.and_utc().timestamp_millis())
34}
35
36pub fn today() -> String {
38 Local::now().format("%Y-%m-%d").to_string()
39}
40
41pub fn hours_minutes(minutes: i64) -> String {
43 match (minutes / 60, minutes % 60) {
44 (0, rest) => format!("{rest}m"),
45 (hours, 0) => format!("{hours}h"),
46 (hours, rest) => format!("{hours}h {rest}m"),
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn utc_and_dates() {
56 assert_eq!(utc(0), "1970-01-01T00:00:00Z");
57 assert_eq!(utc(1_791_590_400_000), "2026-10-10T00:00:00Z");
58 assert_eq!(date(1_791_590_400_000), "2026-10-10");
59 }
60
61 #[test]
62 fn days_are_midnight_utc_and_print_back_unchanged() {
63 assert_eq!(day_millis("2026-10-10"), Some(1_791_590_400_000));
64 for day in ["2026-01-01", "2026-03-29", "2026-10-25", "2024-02-29"] {
65 assert_eq!(date(day_millis(day).unwrap()), day);
66 }
67 assert_eq!(day_millis("2026-02-30"), None);
68 assert_eq!(day_millis("10.10.2026"), None);
69 assert_eq!(day_millis(""), None);
70 }
71
72 #[test]
73 fn today_is_a_local_day() {
74 assert!(day_millis(&today()).is_some());
75 }
76
77 #[test]
78 fn minutes_read_as_hours_and_minutes() {
79 assert_eq!(hours_minutes(0), "0m");
80 assert_eq!(hours_minutes(45), "45m");
81 assert_eq!(hours_minutes(120), "2h");
82 assert_eq!(hours_minutes(750), "12h 30m");
83 }
84
85 #[test]
86 fn local_has_minutes() {
87 assert_eq!(local(1_791_590_400_000).len(), "2026-10-10 00:00".len());
88 }
89}