saorsa_core/attestation/
sunset.rs1use serde::{Deserialize, Serialize};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub struct SunsetTimestamp {
21 timestamp: u64,
23}
24
25impl SunsetTimestamp {
26 #[must_use]
28 pub fn new(timestamp: u64) -> Self {
29 Self { timestamp }
30 }
31
32 #[must_use]
34 pub fn days_from_now(days: u32) -> Self {
35 let now = SystemTime::now()
36 .duration_since(UNIX_EPOCH)
37 .map(|d| d.as_secs())
38 .unwrap_or(0);
39 let seconds_per_day = 86400u64;
40 Self {
41 timestamp: now + (u64::from(days) * seconds_per_day),
42 }
43 }
44
45 #[must_use]
47 pub fn timestamp(&self) -> u64 {
48 self.timestamp
49 }
50
51 #[must_use]
53 pub fn is_expired(&self) -> bool {
54 let now = SystemTime::now()
55 .duration_since(UNIX_EPOCH)
56 .map(|d| d.as_secs())
57 .unwrap_or(0);
58 now > self.timestamp
59 }
60
61 #[must_use]
67 pub fn is_within_grace_period(&self, grace_days: u32) -> bool {
68 let now = SystemTime::now()
69 .duration_since(UNIX_EPOCH)
70 .map(|d| d.as_secs())
71 .unwrap_or(0);
72
73 if now <= self.timestamp {
74 return true;
76 }
77
78 let seconds_per_day = 86400u64;
79 let grace_seconds = u64::from(grace_days) * seconds_per_day;
80 let grace_deadline = self.timestamp.saturating_add(grace_seconds);
81
82 now <= grace_deadline
83 }
84
85 #[must_use]
87 pub fn days_until_sunset(&self) -> u32 {
88 let now = SystemTime::now()
89 .duration_since(UNIX_EPOCH)
90 .map(|d| d.as_secs())
91 .unwrap_or(0);
92
93 if now >= self.timestamp {
94 return 0;
95 }
96
97 let seconds_remaining = self.timestamp - now;
98 let seconds_per_day = 86400u64;
99 (seconds_remaining / seconds_per_day) as u32
100 }
101
102 #[must_use]
104 pub fn days_since_sunset(&self) -> u32 {
105 let now = SystemTime::now()
106 .duration_since(UNIX_EPOCH)
107 .map(|d| d.as_secs())
108 .unwrap_or(0);
109
110 if now <= self.timestamp {
111 return 0;
112 }
113
114 let seconds_elapsed = now - self.timestamp;
115 let seconds_per_day = 86400u64;
116 (seconds_elapsed / seconds_per_day) as u32
117 }
118}
119
120impl Default for SunsetTimestamp {
121 fn default() -> Self {
122 Self::days_from_now(90)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_sunset_not_expired() {
133 let sunset = SunsetTimestamp::days_from_now(30);
134 assert!(!sunset.is_expired());
135 assert!(sunset.days_until_sunset() > 0);
136 assert_eq!(sunset.days_since_sunset(), 0);
137 }
138
139 #[test]
140 fn test_sunset_expired() {
141 let now = SystemTime::now()
143 .duration_since(UNIX_EPOCH)
144 .unwrap()
145 .as_secs();
146 let sunset = SunsetTimestamp::new(now - 86400);
147 assert!(sunset.is_expired());
148 assert_eq!(sunset.days_until_sunset(), 0);
149 assert!(sunset.days_since_sunset() >= 1);
150 }
151
152 #[test]
153 fn test_grace_period() {
154 let now = SystemTime::now()
156 .duration_since(UNIX_EPOCH)
157 .unwrap()
158 .as_secs();
159 let sunset = SunsetTimestamp::new(now - 3600);
160
161 assert!(sunset.is_expired());
163 assert!(sunset.is_within_grace_period(1));
165 assert!(!sunset.is_within_grace_period(0));
167 }
168
169 #[test]
170 fn test_beyond_grace_period() {
171 let now = SystemTime::now()
173 .duration_since(UNIX_EPOCH)
174 .unwrap()
175 .as_secs();
176 let sunset = SunsetTimestamp::new(now - (7 * 86400));
177
178 assert!(sunset.is_expired());
179 assert!(!sunset.is_within_grace_period(1));
180 assert!(sunset.is_within_grace_period(10));
181 }
182
183 #[test]
184 fn test_days_calculation() {
185 let sunset = SunsetTimestamp::days_from_now(30);
186 let days = sunset.days_until_sunset();
188 assert!((29..=30).contains(&days));
189 }
190}