1use time::macros::format_description;
4use time::{OffsetDateTime, UtcOffset};
5
6const DATE_FORMAT: &[time::format_description::FormatItem<'_>] =
7 format_description!("[year]-[month]-[day]");
8const ISO8601_FORMAT: &[time::format_description::FormatItem<'_>] =
9 format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
10
11pub fn current_date() -> String {
18 date_with_offset(
19 OffsetDateTime::now_utc(),
20 robit_ai::logging::local_utc_offset(),
21 )
22}
23
24fn date_with_offset(now: OffsetDateTime, offset: UtcOffset) -> String {
26 now.to_offset(offset)
27 .format(DATE_FORMAT)
28 .expect("date format should be valid")
29}
30
31pub fn current_timestamp() -> String {
36 OffsetDateTime::now_utc()
37 .format(ISO8601_FORMAT)
38 .expect("timestamp format should be valid")
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44 use time::macros::datetime;
45
46 fn offset(hours: i8) -> UtcOffset {
47 UtcOffset::from_hms(hours, 0, 0).unwrap()
48 }
49
50 #[test]
51 fn date_with_offset_crosses_day_forward() {
52 let now = datetime!(2026-09-30 23:00:00 UTC);
54 assert_eq!(date_with_offset(now, UtcOffset::UTC), "2026-09-30");
55 assert_eq!(date_with_offset(now, offset(8)), "2026-10-01");
56 }
57
58 #[test]
59 fn date_with_offset_crosses_day_backward() {
60 let now = datetime!(2026-10-01 00:30:00 UTC);
62 assert_eq!(date_with_offset(now, UtcOffset::UTC), "2026-10-01");
63 assert_eq!(date_with_offset(now, offset(-1)), "2026-09-30");
64 }
65
66 #[test]
67 fn current_date_has_expected_shape() {
68 let date = current_date();
69 assert_eq!(date.len(), 10);
70 let bytes = date.as_bytes();
71 assert!(bytes[..4].iter().all(u8::is_ascii_digit));
72 assert_eq!(bytes[4], b'-');
73 assert!(bytes[5..7].iter().all(u8::is_ascii_digit));
74 assert_eq!(bytes[7], b'-');
75 assert!(bytes[8..].iter().all(u8::is_ascii_digit));
76 }
77}