1use std::{
20 fmt,
21 time::{Duration, UNIX_EPOCH},
22};
23
24#[cfg(feature = "async-serial")]
25use darkfi_serial::async_trait;
26
27use darkfi_serial::{SerialDecodable, SerialEncodable};
28
29use crate::{Error, Result};
30
31const SECS_IN_DAY: u64 = 86400;
32const MIN_IN_HOUR: u64 = 60;
33const SECS_IN_HOUR: u64 = 3600;
34const DAYS_IN_MONTHS: [[u64; 12]; 2] = [
36 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
37 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], ];
39
40#[derive(
42 Hash,
43 Clone,
44 Copy,
45 Debug,
46 SerialEncodable,
47 SerialDecodable,
48 PartialEq,
49 PartialOrd,
50 Ord,
51 Eq,
52 Default,
53)]
54pub struct Timestamp(u64);
55
56impl Timestamp {
57 pub fn inner(&self) -> u64 {
59 self.0
60 }
61
62 pub fn current_time() -> Self {
64 Self(UNIX_EPOCH.elapsed().unwrap().as_secs())
65 }
66
67 pub fn elapsed(&self) -> Result<Self> {
69 Self::current_time().checked_sub(*self)
70 }
71
72 pub fn checked_add(&self, ts: Self) -> Result<Self> {
75 if let Some(result) = self.inner().checked_add(ts.inner()) {
76 Ok(Self(result))
77 } else {
78 Err(Error::AdditionOverflow)
79 }
80 }
81
82 pub fn checked_sub(&self, ts: Self) -> Result<Self> {
85 if let Some(result) = self.inner().checked_sub(ts.inner()) {
86 Ok(Self(result))
87 } else {
88 Err(Error::SubtractionUnderflow)
89 }
90 }
91
92 pub const fn from_u64(x: u64) -> Self {
93 Self(x)
94 }
95}
96
97impl From<u64> for Timestamp {
98 fn from(x: u64) -> Self {
99 Self(x)
100 }
101}
102
103impl fmt::Display for Timestamp {
104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 let date = timestamp_to_date(self.0, DateFormat::DateTime);
106 write!(f, "{}", date)
107 }
108}
109
110#[derive(Clone, Copy, Debug, SerialEncodable, SerialDecodable, PartialEq, PartialOrd, Eq)]
111pub struct NanoTimestamp(pub u128);
112
113impl NanoTimestamp {
114 pub fn inner(&self) -> u128 {
115 self.0
116 }
117
118 pub const fn from_secs(secs: u128) -> Self {
119 Self(secs * 1_000_000_000)
120 }
121
122 pub fn current_time() -> Self {
123 Self(UNIX_EPOCH.elapsed().unwrap().as_nanos())
124 }
125
126 pub fn elapsed(&self) -> Result<Self> {
127 Self::current_time().checked_sub(*self)
128 }
129
130 pub fn checked_sub(&self, ts: Self) -> Result<Self> {
131 if let Some(result) = self.inner().checked_sub(ts.inner()) {
132 Ok(Self(result))
133 } else {
134 Err(Error::SubtractionUnderflow)
135 }
136 }
137}
138impl fmt::Display for NanoTimestamp {
139 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140 let date = timestamp_to_date(self.0.try_into().unwrap(), DateFormat::Nanos);
141 write!(f, "{}", date)
142 }
143}
144
145pub enum DateFormat {
146 Default,
147 Date,
148 DateTime,
149 Nanos,
150}
151
152#[derive(Clone, Debug, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)]
154pub struct DateTime {
155 pub year: u32,
156 pub month: u32,
157 pub day: u32,
158 pub hour: u32,
159 pub min: u32,
160 pub sec: u32,
161 pub nanos: u32,
162}
163
164impl DateTime {
165 pub fn new() -> Self {
166 Self { year: 0, month: 0, day: 0, hour: 0, min: 0, sec: 0, nanos: 0 }
167 }
168
169 pub fn date(&self) -> Date {
170 Date { year: self.year, month: self.month, day: self.day }
171 }
172
173 pub fn from_timestamp(secs: u64, nsecs: u32) -> Self {
174 let leap_year = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) };
175
176 let mut date_time = DateTime::new();
177 let mut year = 1970;
178
179 let time = secs % SECS_IN_DAY;
180 let mut day_number = secs / SECS_IN_DAY;
181
182 date_time.nanos = nsecs;
183 date_time.sec = (time % MIN_IN_HOUR) as u32;
184 date_time.min = ((time % SECS_IN_HOUR) / MIN_IN_HOUR) as u32;
185 date_time.hour = (time / SECS_IN_HOUR) as u32;
186
187 loop {
188 let year_size = if leap_year(year) { 366 } else { 365 };
189 if day_number >= year_size {
190 day_number -= year_size;
191 year += 1;
192 } else {
193 break
194 }
195 }
196 date_time.year = year;
197
198 let mut month = 0;
199 while day_number >= DAYS_IN_MONTHS[if leap_year(year) { 1 } else { 0 }][month] {
200 day_number -= DAYS_IN_MONTHS[if leap_year(year) { 1 } else { 0 }][month];
201 month += 1;
202 }
203 date_time.month = month as u32 + 1;
204 date_time.day = day_number as u32 + 1;
205
206 date_time
207 }
208
209 pub fn from_timestamp_str(timestamp_str: &str) -> Result<Self> {
216 let parts: Vec<&str> = timestamp_str.split('T').collect();
218
219 if parts.len() != 2 {
221 return Err(Error::ParseFailed("Invalid timestamp format"));
222 }
223
224 let date_components: Vec<u32> = parts[0]
226 .split('-')
227 .map(|s| s.parse::<u32>().map_err(|_| Error::ParseFailed("Invalid date component")))
228 .collect::<Result<Vec<u32>>>()?;
229
230 if date_components.len() != 3 {
232 return Err(Error::ParseFailed("Invalid date format"));
233 }
234
235 let time_components: Vec<u32> = parts[1]
237 .split(':')
238 .map(|s| s.parse::<u32>().map_err(|_| Error::ParseFailed("Invalid time component")))
239 .collect::<Result<Vec<u32>>>()?;
240
241 if time_components.len() != 3 {
243 return Err(Error::ParseFailed("Invalid time format"));
244 }
245
246 let (year, month, day) = (date_components[0], date_components[1], date_components[2]);
248
249 if !(1..=12).contains(&month) || !Self::is_valid_day(year, month, day) {
251 return Err(Error::ParseFailed("Invalid month or day"));
252 }
253
254 let (hour, min, sec) = (time_components[0], time_components[1], time_components[2]);
256
257 if hour > 23 || min > 59 || sec > 59 {
259 return Err(Error::ParseFailed("Invalid hour, minute or second"));
260 }
261
262 Ok(DateTime { year, month, day, hour, min, sec, nanos: 0 })
264 }
265
266 fn is_valid_day(year: u32, month: u32, day: u32) -> bool {
270 let days_in_month = DAYS_IN_MONTHS
271 [(year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) as usize]
272 [(month - 1) as usize];
273 day > 0 && day <= days_in_month as u32
274 }
275}
276
277impl fmt::Display for DateTime {
278 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279 write!(
280 f,
281 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
282 self.year, self.month, self.day, self.hour, self.min, self.sec
283 )
284 }
285}
286
287#[derive(Clone, Debug, Default)]
288pub struct Date {
289 pub day: u32,
290 pub month: u32,
291 pub year: u32,
292}
293
294impl fmt::Display for Date {
295 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
296 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
297 }
298}
299
300pub fn timestamp_to_date(timestamp: u64, format: DateFormat) -> String {
302 if timestamp == 0 {
303 return "".to_string();
304 }
305
306 match format {
307 DateFormat::Default => "".to_string(),
308 DateFormat::Date => DateTime::from_timestamp(timestamp, 0).date().to_string(),
309 DateFormat::DateTime => DateTime::from_timestamp(timestamp, 0).to_string(),
310 DateFormat::Nanos => {
311 const A_BILLION: u64 = 1_000_000_000;
312 let dt =
313 DateTime::from_timestamp(timestamp / A_BILLION, (timestamp % A_BILLION) as u32);
314 format!(
315 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{}",
316 dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec, dt.nanos
317 )
318 }
319 }
320}
321
322pub fn fmt_duration(duration: Duration) -> String {
339 let total_secs = duration.as_secs_f64();
340
341 let days = (total_secs / 86400.0).floor() as u64;
343 let hours = ((total_secs % 86400.0) / 3600.0).floor() as u64;
344 let minutes = ((total_secs % 3600.0) / 60.0).floor() as u64;
345
346 let seconds = (total_secs % 60.0 * 1_000_000_000.0).round() / 1_000_000_000.0;
348
349 let mut parts = Vec::new();
350
351 if days > 0 {
353 parts.push(format!("{}d", days));
354 }
355 if hours > 0 {
356 parts.push(format!("{}h", hours));
357 }
358 if minutes > 0 {
359 parts.push(format!("{}m", minutes));
360 }
361
362 if seconds > 0.0 || (days == 0 && hours == 0 && minutes == 0) {
364 if days == 0 && hours == 0 && minutes == 0 && seconds.fract() != 0.0 {
366 parts.push(format!("{:.9}s", seconds));
367 } else {
368 parts.push(format!("{}s", seconds.round() as u64));
370 }
371 }
372
373 parts.join(" ")
374}
375
376#[cfg(test)]
377mod tests {
378 use super::{fmt_duration, DateTime, Timestamp};
379 use std::time::Duration;
380
381 #[test]
382 fn check_ts_add_overflow() {
383 assert!(Timestamp::current_time().checked_add(u64::MAX.into()).is_err());
384 }
385
386 #[test]
387 fn check_ts_sub_underflow() {
388 let cur = Timestamp::current_time().checked_add(10_000.into()).unwrap();
389 assert!(cur.elapsed().is_err());
390 }
391
392 #[test]
393 fn test_from_timestamp_str() {
395 let valid_timestamps = vec![
397 (
398 "2024-01-01T12:00:00",
399 DateTime { year: 2024, month: 1, day: 1, hour: 12, min: 0, sec: 0, nanos: 0 },
400 ),
401 (
402 "2024-02-29T23:59:59",
403 DateTime { year: 2024, month: 2, day: 29, hour: 23, min: 59, sec: 59, nanos: 0 },
404 ), (
406 "2023-12-31T00:00:00",
407 DateTime { year: 2023, month: 12, day: 31, hour: 0, min: 0, sec: 0, nanos: 0 },
408 ),
409 (
410 "1970-01-01T00:00:00",
411 DateTime { year: 1970, month: 1, day: 1, hour: 0, min: 0, sec: 0, nanos: 0 },
412 ), ];
414
415 for (timestamp_str, expected) in valid_timestamps {
416 let result = DateTime::from_timestamp_str(timestamp_str)
417 .expect("Valid timestamp should not fail");
418 assert_eq!(result, expected);
419 }
420
421 let boundary_timestamps = vec![
423 (
424 "2023-02-28T23:59:59",
425 DateTime { year: 2023, month: 2, day: 28, hour: 23, min: 59, sec: 59, nanos: 0 },
426 ),
427 (
428 "2023-03-01T00:00:00",
429 DateTime { year: 2023, month: 3, day: 1, hour: 0, min: 0, sec: 0, nanos: 0 },
430 ),
431 (
432 "2024-02-29T12:30:30",
433 DateTime { year: 2024, month: 2, day: 29, hour: 12, min: 30, sec: 30, nanos: 0 },
434 ), ];
436
437 for (timestamp_str, expected) in boundary_timestamps {
438 let result = DateTime::from_timestamp_str(timestamp_str)
439 .expect("Valid timestamp should not fail");
440 assert_eq!(result, expected);
441 }
442
443 let invalid_timestamps = vec![
445 "2023-02-30T12:00:00", "2023-04-31T12:00:00", "2023-13-01T12:00:00", "2023-01-01T12.00.00", "2023-01-01", "2023-01-01 12.00.00", "2023/01/01T12:00", "2023-01-01T-12:-60:-60", ];
454
455 for timestamp_str in invalid_timestamps {
456 let result = DateTime::from_timestamp_str(timestamp_str);
457 assert!(result.is_err(), "Expected error for invalid timestamp '{}'", timestamp_str);
458 }
459 }
460 #[test]
461 pub fn test_fmt_duration() {
463 let duration = Duration::new(0, 0);
465 assert_eq!(fmt_duration(duration), "0s");
466
467 let duration = Duration::new(0, 987654321);
469 assert_eq!(fmt_duration(duration), "0.987654321s");
470
471 let duration = Duration::new(1, 0);
473 assert_eq!(fmt_duration(duration), "1s");
474
475 let duration = Duration::new(59, 987654321);
477 assert_eq!(fmt_duration(duration), "59.987654321s");
478
479 let duration = Duration::new(60, 0);
481 assert_eq!(fmt_duration(duration), "1m");
482
483 let duration = Duration::new(61, 0);
485 assert_eq!(fmt_duration(duration), "1m 1s");
486
487 let duration = Duration::new(3600, 0);
489 assert_eq!(fmt_duration(duration), "1h");
490
491 let duration = Duration::new(4537, 0);
493 assert_eq!(fmt_duration(duration), "1h 15m 37s");
494
495 let duration = Duration::new((12 * 86400) + (11 * 3600) + (59 * 60) + 59, 0);
497 assert_eq!(fmt_duration(duration), "12d 11h 59m 59s");
498 }
499}