1use crate::datetime::RosettaDateTime;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct RosettaDelta {
9 pub total_seconds: i64,
11 pub years: i64,
13 pub months: i64,
15 pub days: i64,
17 pub hours: i64,
19 pub minutes: i64,
21 pub seconds: i64,
23}
24
25impl RosettaDelta {
26 pub fn between(start: &RosettaDateTime, end: &RosettaDateTime) -> Self {
31 let total = end.timestamp() - start.timestamp();
32 let abs = total.unsigned_abs();
33
34 let years = abs / (365 * 86400);
35 let rem = abs % (365 * 86400);
36 let months = rem / (30 * 86400);
37 let rem = rem % (30 * 86400);
38 let days = rem / 86400;
39 let rem = rem % 86400;
40 let hours = rem / 3600;
41 let rem = rem % 3600;
42 let minutes = rem / 60;
43 let seconds = rem % 60;
44
45 let sign = if total >= 0 { 1i64 } else { -1i64 };
46
47 Self {
48 total_seconds: total,
49 years: sign * years as i64,
50 months: sign * months as i64,
51 days: sign * days as i64,
52 hours: sign * hours as i64,
53 minutes: sign * minutes as i64,
54 seconds: sign * seconds as i64,
55 }
56 }
57
58 pub fn abs_seconds(&self) -> u64 {
60 self.total_seconds.unsigned_abs()
61 }
62
63 pub fn is_positive(&self) -> bool {
65 self.total_seconds >= 0
66 }
67
68 pub fn total_days(&self) -> i64 {
70 self.total_seconds / 86400
71 }
72
73 pub fn total_hours(&self) -> i64 {
75 self.total_seconds / 3600
76 }
77
78 pub fn total_minutes(&self) -> i64 {
80 self.total_seconds / 60
81 }
82}
83
84impl std::fmt::Display for RosettaDelta {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 let abs = self.total_seconds.unsigned_abs();
87 let sign = if self.total_seconds < 0 { "-" } else { "" };
88
89 let d = abs / 86400;
90 let h = (abs % 86400) / 3600;
91 let m = (abs % 3600) / 60;
92 let s = abs % 60;
93
94 if d > 0 {
95 write!(f, "{}{}d {:02}:{:02}:{:02}", sign, d, h, m, s)
96 } else {
97 write!(f, "{}{:02}:{:02}:{:02}", sign, h, m, s)
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::timezone::TzOffset;
106
107 #[test]
108 fn test_delta_basic() {
109 let a = RosettaDateTime::from_components(2023, 10, 1, 12, 0, 0, TzOffset::UTC).unwrap();
110 let b = RosettaDateTime::from_components(2023, 10, 1, 14, 30, 15, TzOffset::UTC).unwrap();
111 let delta = RosettaDelta::between(&a, &b);
112 assert_eq!(delta.hours, 2);
113 assert_eq!(delta.minutes, 30);
114 assert_eq!(delta.seconds, 15);
115 assert!(delta.is_positive());
116 }
117
118 #[test]
119 fn test_delta_negative() {
120 let a = RosettaDateTime::from_components(2023, 10, 5, 12, 0, 0, TzOffset::UTC).unwrap();
121 let b = RosettaDateTime::from_components(2023, 10, 1, 12, 0, 0, TzOffset::UTC).unwrap();
122 let delta = RosettaDelta::between(&a, &b);
123 assert!(!delta.is_positive());
124 assert_eq!(delta.total_days(), -4);
125 }
126
127 #[test]
128 fn test_delta_display() {
129 let a = RosettaDateTime::from_components(2023, 10, 1, 0, 0, 0, TzOffset::UTC).unwrap();
130 let b = RosettaDateTime::from_components(2023, 10, 3, 5, 30, 45, TzOffset::UTC).unwrap();
131 let delta = RosettaDelta::between(&a, &b);
132 assert_eq!(format!("{}", delta), "2d 05:30:45");
133 }
134}