time.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. fmt,
  20. time::{Duration, UNIX_EPOCH},
  21. };
  22. #[cfg(feature = "async-serial")]
  23. use darkfi_serial::async_trait;
  24. use darkfi_serial::{SerialDecodable, SerialEncodable};
  25. use crate::{Error, Result};
  26. const SECS_IN_DAY: u64 = 86400;
  27. const MIN_IN_HOUR: u64 = 60;
  28. const SECS_IN_HOUR: u64 = 3600;
  29. /// Represents the number of days in each month for both leap and non-leap years.
  30. const DAYS_IN_MONTHS: [[u64; 12]; 2] = [
  31. [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
  32. [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], // Leap years
  33. ];
  34. /// Wrapper struct to represent system timestamps.
  35. #[derive(
  36. Hash,
  37. Clone,
  38. Copy,
  39. Debug,
  40. SerialEncodable,
  41. SerialDecodable,
  42. PartialEq,
  43. PartialOrd,
  44. Ord,
  45. Eq,
  46. Default,
  47. )]
  48. pub struct Timestamp(u64);
  49. impl Timestamp {
  50. /// Returns the inner `u64` of `Timestamp`
  51. pub fn inner(&self) -> u64 {
  52. self.0
  53. }
  54. /// Generate a `Timestamp` of the current time.
  55. pub fn current_time() -> Self {
  56. Self(UNIX_EPOCH.elapsed().unwrap().as_secs())
  57. }
  58. /// Calculates the elapsed time of a `Timestamp` up to the time of calling the function.
  59. pub fn elapsed(&self) -> Result<Self> {
  60. Self::current_time().checked_sub(*self)
  61. }
  62. /// Add `self` to a given timestamp
  63. /// Errors on integer overflow.
  64. pub fn checked_add(&self, ts: Self) -> Result<Self> {
  65. if let Some(result) = self.inner().checked_add(ts.inner()) {
  66. Ok(Self(result))
  67. } else {
  68. Err(Error::AdditionOverflow)
  69. }
  70. }
  71. /// Subtract `self` with a given timestamp
  72. /// Errors on integer underflow.
  73. pub fn checked_sub(&self, ts: Self) -> Result<Self> {
  74. if let Some(result) = self.inner().checked_sub(ts.inner()) {
  75. Ok(Self(result))
  76. } else {
  77. Err(Error::SubtractionUnderflow)
  78. }
  79. }
  80. pub const fn from_u64(x: u64) -> Self {
  81. Self(x)
  82. }
  83. }
  84. impl From<u64> for Timestamp {
  85. fn from(x: u64) -> Self {
  86. Self(x)
  87. }
  88. }
  89. impl fmt::Display for Timestamp {
  90. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  91. let date = timestamp_to_date(self.0, DateFormat::DateTime);
  92. write!(f, "{date}")
  93. }
  94. }
  95. #[derive(Clone, Copy, Debug, SerialEncodable, SerialDecodable, PartialEq, PartialOrd, Eq)]
  96. pub struct NanoTimestamp(pub u128);
  97. impl NanoTimestamp {
  98. pub fn inner(&self) -> u128 {
  99. self.0
  100. }
  101. pub const fn from_secs(secs: u128) -> Self {
  102. Self(secs * 1_000_000_000)
  103. }
  104. pub fn current_time() -> Self {
  105. Self(UNIX_EPOCH.elapsed().unwrap().as_nanos())
  106. }
  107. pub fn elapsed(&self) -> Result<Self> {
  108. Self::current_time().checked_sub(*self)
  109. }
  110. pub fn checked_sub(&self, ts: Self) -> Result<Self> {
  111. if let Some(result) = self.inner().checked_sub(ts.inner()) {
  112. Ok(Self(result))
  113. } else {
  114. Err(Error::SubtractionUnderflow)
  115. }
  116. }
  117. }
  118. impl fmt::Display for NanoTimestamp {
  119. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  120. let date = timestamp_to_date(self.0.try_into().unwrap(), DateFormat::Nanos);
  121. write!(f, "{date}")
  122. }
  123. }
  124. pub enum DateFormat {
  125. Default,
  126. Date,
  127. DateTime,
  128. Nanos,
  129. }
  130. /// Represents a UTC `DateTime` with individual fields for date and time components.
  131. #[derive(Clone, Debug, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)]
  132. pub struct DateTime {
  133. pub year: u32,
  134. pub month: u32,
  135. pub day: u32,
  136. pub hour: u32,
  137. pub min: u32,
  138. pub sec: u32,
  139. pub nanos: u32,
  140. }
  141. impl DateTime {
  142. pub fn new() -> Self {
  143. Self { year: 0, month: 0, day: 0, hour: 0, min: 0, sec: 0, nanos: 0 }
  144. }
  145. pub fn date(&self) -> Date {
  146. Date { year: self.year, month: self.month, day: self.day }
  147. }
  148. pub fn from_timestamp(secs: u64, nsecs: u32) -> Self {
  149. let leap_year = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) };
  150. let mut date_time = DateTime::new();
  151. let mut year = 1970;
  152. let time = secs % SECS_IN_DAY;
  153. let mut day_number = secs / SECS_IN_DAY;
  154. date_time.nanos = nsecs;
  155. date_time.sec = (time % MIN_IN_HOUR) as u32;
  156. date_time.min = ((time % SECS_IN_HOUR) / MIN_IN_HOUR) as u32;
  157. date_time.hour = (time / SECS_IN_HOUR) as u32;
  158. loop {
  159. let year_size = if leap_year(year) { 366 } else { 365 };
  160. if day_number >= year_size {
  161. day_number -= year_size;
  162. year += 1;
  163. } else {
  164. break
  165. }
  166. }
  167. date_time.year = year;
  168. let mut month = 0;
  169. while day_number >= DAYS_IN_MONTHS[if leap_year(year) { 1 } else { 0 }][month] {
  170. day_number -= DAYS_IN_MONTHS[if leap_year(year) { 1 } else { 0 }][month];
  171. month += 1;
  172. }
  173. date_time.month = month as u32 + 1;
  174. date_time.day = day_number as u32 + 1;
  175. date_time
  176. }
  177. /// Provides a `DateTime` instance from a string in "YYYY-MM-DDTHH:mm:ss" format.
  178. ///
  179. /// This function parses and validates the timestamp string, returning a `DateTime` instance
  180. /// with the parsed year, month, day, hour, minute, and second. Nanoseconds are not included
  181. /// in the input string and default to zero. If the input string does not match the expected
  182. /// format or contains invalid date or time values, it returns an [`Error::ParseFailed`] error.
  183. pub fn from_timestamp_str(timestamp_str: &str) -> Result<Self> {
  184. // Split the input string into date and time based on the 'T' separator
  185. let parts: Vec<&str> = timestamp_str.split('T').collect();
  186. // Check if the split parts have the correct length
  187. if parts.len() != 2 {
  188. return Err(Error::ParseFailed("Invalid timestamp format"));
  189. }
  190. // Parse the date into a vec
  191. let date_components: Vec<u32> = parts[0]
  192. .split('-')
  193. .map(|s| s.parse::<u32>().map_err(|_| Error::ParseFailed("Invalid date component")))
  194. .collect::<Result<Vec<u32>>>()?;
  195. // Verify year, month, and day are provided
  196. if date_components.len() != 3 {
  197. return Err(Error::ParseFailed("Invalid date format"));
  198. }
  199. // Parse the time into a vec
  200. let time_components: Vec<u32> = parts[1]
  201. .split(':')
  202. .map(|s| s.parse::<u32>().map_err(|_| Error::ParseFailed("Invalid time component")))
  203. .collect::<Result<Vec<u32>>>()?;
  204. // Verify that hour, minute, second are provided
  205. if time_components.len() != 3 {
  206. return Err(Error::ParseFailed("Invalid time format"));
  207. }
  208. // Destructure the date components into year, month, and day
  209. let (year, month, day) = (date_components[0], date_components[1], date_components[2]);
  210. // Validate month and day
  211. if !(1..=12).contains(&month) || !Self::is_valid_day(year, month, day) {
  212. return Err(Error::ParseFailed("Invalid month or day"));
  213. }
  214. // Destructure the time components into hour, minute, and second
  215. let (hour, min, sec) = (time_components[0], time_components[1], time_components[2]);
  216. // Validate hour, minute, and second values
  217. if hour > 23 || min > 59 || sec > 59 {
  218. return Err(Error::ParseFailed("Invalid hour, minute or second"));
  219. }
  220. // Return a new DateTime instance with parsed values and default nanoseconds set to 0
  221. Ok(DateTime { year, month, day, hour, min, sec, nanos: 0 })
  222. }
  223. /// Auxiliary function that determines whether the specified day is within the valid range
  224. /// for the given month and year, accounting for leap years. It returns `true` if the day
  225. /// is valid.
  226. fn is_valid_day(year: u32, month: u32, day: u32) -> bool {
  227. let days_in_month = DAYS_IN_MONTHS[(year.is_multiple_of(4) &&
  228. (!year.is_multiple_of(100) || year.is_multiple_of(400)))
  229. as usize][(month - 1) as usize];
  230. day > 0 && day <= days_in_month as u32
  231. }
  232. }
  233. impl fmt::Display for DateTime {
  234. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  235. write!(
  236. f,
  237. "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
  238. self.year, self.month, self.day, self.hour, self.min, self.sec
  239. )
  240. }
  241. }
  242. #[derive(Clone, Debug, Default)]
  243. pub struct Date {
  244. pub day: u32,
  245. pub month: u32,
  246. pub year: u32,
  247. }
  248. impl fmt::Display for Date {
  249. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  250. write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
  251. }
  252. }
  253. // TODO: fix logic and add corresponding test case
  254. pub fn timestamp_to_date(timestamp: u64, format: DateFormat) -> String {
  255. if timestamp == 0 {
  256. return "".to_string();
  257. }
  258. match format {
  259. DateFormat::Default => "".to_string(),
  260. DateFormat::Date => DateTime::from_timestamp(timestamp, 0).date().to_string(),
  261. DateFormat::DateTime => DateTime::from_timestamp(timestamp, 0).to_string(),
  262. DateFormat::Nanos => {
  263. const A_BILLION: u64 = 1_000_000_000;
  264. let dt =
  265. DateTime::from_timestamp(timestamp / A_BILLION, (timestamp % A_BILLION) as u32);
  266. format!(
  267. "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{}",
  268. dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec, dt.nanos
  269. )
  270. }
  271. }
  272. }
  273. /// Formats a `Duration` into a user-friendly format using days, hours, minutes, and seconds,
  274. /// and returns the formatted string.
  275. ///
  276. /// Durations less than one minute include fractional seconds with nanosecond precision (up to 9 decimal places),
  277. /// while durations of one minute or longer display as whole seconds, rounded to the nearest second.
  278. ///
  279. /// The output format includes the following components:
  280. /// - `{days}d` for days
  281. /// - `{hours}h` for hours
  282. /// - `{minutes}m` for minutes
  283. /// - `{seconds}s` for seconds
  284. ///
  285. /// When all components are non-zero, the format appears as:
  286. /// ```plaintext
  287. /// {days}d {hours}h {minutes}m {seconds}s
  288. /// ```
  289. pub fn fmt_duration(duration: Duration) -> String {
  290. let total_secs = duration.as_secs_f64();
  291. // Calculate each time component
  292. let days = (total_secs / 86400.0).floor() as u64;
  293. let hours = ((total_secs % 86400.0) / 3600.0).floor() as u64;
  294. let minutes = ((total_secs % 3600.0) / 60.0).floor() as u64;
  295. // Calculate fractional seconds (rounding to nanosecond precision)
  296. let seconds = (total_secs % 60.0 * 1_000_000_000.0).round() / 1_000_000_000.0;
  297. let mut parts = Vec::new();
  298. // Include non-zero components for dys, hours and minutes
  299. if days > 0 {
  300. parts.push(format!("{days}d"));
  301. }
  302. if hours > 0 {
  303. parts.push(format!("{hours}h"));
  304. }
  305. if minutes > 0 {
  306. parts.push(format!("{minutes}m"));
  307. }
  308. // Include seconds if they are non-zero or if all other components are zero (i.e., 0s)
  309. if seconds > 0.0 || (days == 0 && hours == 0 && minutes == 0) {
  310. // For durations shorter than 1 minute, include fractional seconds up to 9 decimal places
  311. if days == 0 && hours == 0 && minutes == 0 && seconds.fract() != 0.0 {
  312. parts.push(format!("{seconds:.9}s"));
  313. } else {
  314. // Otherwise, include rounded whole seconds
  315. parts.push(format!("{}s", seconds.round() as u64));
  316. }
  317. }
  318. parts.join(" ")
  319. }
  320. #[cfg(test)]
  321. mod tests {
  322. use super::{fmt_duration, DateTime, Timestamp};
  323. use std::time::Duration;
  324. #[test]
  325. fn check_ts_add_overflow() {
  326. assert!(Timestamp::current_time().checked_add(u64::MAX.into()).is_err());
  327. }
  328. #[test]
  329. fn check_ts_sub_underflow() {
  330. let cur = Timestamp::current_time().checked_add(10_000.into()).unwrap();
  331. assert!(cur.elapsed().is_err());
  332. }
  333. #[test]
  334. /// Tests the `from_timestamp_str` function to ensure it correctly converts timestamp strings into `DateTime` instances.
  335. fn test_from_timestamp_str() {
  336. // Verify validate dates
  337. let valid_timestamps = vec![
  338. (
  339. "2024-01-01T12:00:00",
  340. DateTime { year: 2024, month: 1, day: 1, hour: 12, min: 0, sec: 0, nanos: 0 },
  341. ),
  342. (
  343. "2024-02-29T23:59:59",
  344. DateTime { year: 2024, month: 2, day: 29, hour: 23, min: 59, sec: 59, nanos: 0 },
  345. ), // Leap year
  346. (
  347. "2023-12-31T00:00:00",
  348. DateTime { year: 2023, month: 12, day: 31, hour: 0, min: 0, sec: 0, nanos: 0 },
  349. ),
  350. (
  351. "1970-01-01T00:00:00",
  352. DateTime { year: 1970, month: 1, day: 1, hour: 0, min: 0, sec: 0, nanos: 0 },
  353. ), // Unix epoch
  354. ];
  355. for (timestamp_str, expected) in valid_timestamps {
  356. let result = DateTime::from_timestamp_str(timestamp_str)
  357. .expect("Valid timestamp should not fail");
  358. assert_eq!(result, expected);
  359. }
  360. // Verify boundary conditions
  361. let boundary_timestamps = vec![
  362. (
  363. "2023-02-28T23:59:59",
  364. DateTime { year: 2023, month: 2, day: 28, hour: 23, min: 59, sec: 59, nanos: 0 },
  365. ),
  366. (
  367. "2023-03-01T00:00:00",
  368. DateTime { year: 2023, month: 3, day: 1, hour: 0, min: 0, sec: 0, nanos: 0 },
  369. ),
  370. (
  371. "2024-02-29T12:30:30",
  372. DateTime { year: 2024, month: 2, day: 29, hour: 12, min: 30, sec: 30, nanos: 0 },
  373. ), // Leap year
  374. ];
  375. for (timestamp_str, expected) in boundary_timestamps {
  376. let result = DateTime::from_timestamp_str(timestamp_str)
  377. .expect("Valid timestamp should not fail");
  378. assert_eq!(result, expected);
  379. }
  380. // Verify invalid timestamps
  381. let invalid_timestamps = vec![
  382. "2023-02-30T12:00:00", // Invalid day
  383. "2023-04-31T12:00:00", // Invalid day
  384. "2023-13-01T12:00:00", // Invalid month
  385. "2023-01-01T12.00.00", // Invalid format
  386. "2023-01-01", // Missing time part
  387. "2023-01-01 12.00.00", // Missing T separator
  388. "2023/01/01T12:00", // Incorrect date separator
  389. "2023-01-01T-12:-60:-60", // Invalid time components
  390. ];
  391. for timestamp_str in invalid_timestamps {
  392. let result = DateTime::from_timestamp_str(timestamp_str);
  393. assert!(result.is_err(), "Expected error for invalid timestamp '{timestamp_str}'");
  394. }
  395. }
  396. #[test]
  397. /// Tests the `fmt_duration` function to ensure it correctly formats durations.
  398. pub fn test_fmt_duration() {
  399. // Zero duration (edge case)
  400. let duration = Duration::new(0, 0);
  401. assert_eq!(fmt_duration(duration), "0s");
  402. // Small durations with fractional seconds
  403. let duration = Duration::new(0, 987654321);
  404. assert_eq!(fmt_duration(duration), "0.987654321s");
  405. // Exactly 1 second
  406. let duration = Duration::new(1, 0);
  407. assert_eq!(fmt_duration(duration), "1s");
  408. // Exactly 59.987654321 seconds (just under a minute)
  409. let duration = Duration::new(59, 987654321);
  410. assert_eq!(fmt_duration(duration), "59.987654321s");
  411. // Exactly 1 minute
  412. let duration = Duration::new(60, 0);
  413. assert_eq!(fmt_duration(duration), "1m");
  414. // 1 minute and 1 second
  415. let duration = Duration::new(61, 0);
  416. assert_eq!(fmt_duration(duration), "1m 1s");
  417. // 1 hour
  418. let duration = Duration::new(3600, 0);
  419. assert_eq!(fmt_duration(duration), "1h");
  420. // 1 hour, 15 minutes, and 37 seconds
  421. let duration = Duration::new(4537, 0);
  422. assert_eq!(fmt_duration(duration), "1h 15m 37s");
  423. // Large duration with rounded seconds
  424. let duration = Duration::new((12 * 86400) + (11 * 3600) + (59 * 60) + 59, 0);
  425. assert_eq!(fmt_duration(duration), "12d 11h 59m 59s");
  426. }
  427. }