1#![deny(rustdoc::all)]
6#![allow(rustdoc::missing_doc_code_examples)]
7#![deny(clippy::unwrap_used, clippy::integer_arithmetic)]
8#![deny(missing_docs)]
9
10use num_traits::cast::ToPrimitive;
11
12pub const ERA_NUM_PERIODS: usize = (u8::MAX as usize) + 1;
14
15pub const PERIOD_SECONDS: u32 = 86_400 * 3;
17
18pub const SECONDS_PER_ERA: u64 = (ERA_NUM_PERIODS as u64) * (PERIOD_SECONDS as u64);
20
21pub const COMMON_ERA_UNIX_TS: u64 = 1640995200;
23
24pub fn calculate_era_start_ts(era: u16) -> Option<u64> {
26 COMMON_ERA_UNIX_TS.checked_add(SECONDS_PER_ERA.checked_mul(era.into())?)
27}
28
29pub fn calculate_period_start_ts(era: u16, period: u8) -> Option<u64> {
31 calculate_era_start_ts(era)?
32 .checked_add(period.to_u64()?.checked_mul(PERIOD_SECONDS.to_u64()?)?)
33}
34
35pub fn has_period_elapsed(era: u16, period: u8, now: i64) -> Option<bool> {
39 let start = calculate_period_start_ts(era, period)?;
40 let now = now.to_u64()?;
41 Some(now > start)
45}
46
47pub fn calculate_era_and_period_of_ts(now: u64) -> Option<(u16, u8)> {
49 let current_era: u16 = now
50 .checked_sub(COMMON_ERA_UNIX_TS)?
51 .checked_div(SECONDS_PER_ERA)?
52 .to_u16()?;
53 let current_era_start_ts = calculate_era_start_ts(current_era)?;
54 let current_period: u8 = now
55 .checked_sub(current_era_start_ts)?
56 .checked_div(PERIOD_SECONDS.into())?
57 .to_u8()?;
58 Some((current_era, current_period))
59}
60
61pub fn calculate_next_era_and_period(era: u16, period: u8) -> Option<(u16, u8)> {
63 Some(if period == u8::MAX {
64 (era.checked_add(1)?, 0_u8)
65 } else {
66 (era, period.checked_add(1)?)
67 })
68}
69
70pub fn calculate_next_era_and_period_of_ts(now: u64) -> Option<(u16, u8)> {
72 let (current_era, current_period) = calculate_era_and_period_of_ts(now)?;
73 calculate_next_era_and_period(current_era, current_period)
74}
75
76#[cfg(test)]
77#[allow(clippy::unwrap_used, clippy::integer_arithmetic)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_has_period_elapsed() {
83 let current_time = (COMMON_ERA_UNIX_TS + (PERIOD_SECONDS as u64) * 2)
85 .to_i64()
86 .unwrap();
87
88 assert!(has_period_elapsed(0, 0, current_time).unwrap());
89 assert!(has_period_elapsed(0, 1, current_time).unwrap());
90 assert!(!has_period_elapsed(0, 2, current_time).unwrap());
91
92 assert!(!has_period_elapsed(1, 0, current_time).unwrap());
93 }
94
95 #[test]
96 fn test_has_period_elapsed_boundary() {
97 let current_time = (COMMON_ERA_UNIX_TS + (PERIOD_SECONDS as u64) * 2 + 1)
99 .to_i64()
100 .unwrap();
101
102 assert!(has_period_elapsed(0, 0, current_time).unwrap());
103 assert!(has_period_elapsed(0, 1, current_time).unwrap());
104 assert!(has_period_elapsed(0, 2, current_time).unwrap());
105 assert!(!has_period_elapsed(0, 3, current_time).unwrap());
106
107 assert!(!has_period_elapsed(1, 0, current_time).unwrap());
108 }
109
110 #[test]
111 fn test_calculate_next_era_and_period_normal() {
112 let era = 2_u16;
113 let period = 4_u8;
114 let start = calculate_period_start_ts(era, period).unwrap() + 40;
115
116 let (result_era, result_period) = calculate_era_and_period_of_ts(start).unwrap();
117 assert_eq!(result_era, era);
118 assert_eq!(result_period, period);
119
120 let (result_next_era, result_next_period) =
121 calculate_next_era_and_period_of_ts(start).unwrap();
122 assert_eq!(result_next_era, era);
123 assert_eq!(result_next_period, period + 1);
124 }
125
126 #[test]
127 fn test_calculate_next_era_and_period_boundary() {
128 let era = 2_u16;
129 let period = 255_u8;
130 let start = calculate_period_start_ts(era, period).unwrap() + 40;
131
132 let (result_era, result_period) = calculate_era_and_period_of_ts(start).unwrap();
133 assert_eq!(result_era, era);
134 assert_eq!(result_period, period);
135
136 let (result_next_era, result_next_period) =
137 calculate_next_era_and_period_of_ts(start).unwrap();
138 assert_eq!(result_next_era, era + 1);
139 assert_eq!(result_next_period, 0_u8);
140 }
141}