xrpl/utils/
time_conversion.rs1use crate::utils::exceptions::XRPLTimeRangeException;
5use chrono::TimeZone;
6use chrono::Utc;
7use chrono::{DateTime, LocalResult};
8
9use super::exceptions::XRPLUtilsResult;
10
11pub const RIPPLE_EPOCH: i64 = 946684800;
13pub const MAX_XRPL_TIME: i64 = i64::pow(2, 32);
15
16fn _ripple_check_max<T>(time: i64, ok: T) -> XRPLUtilsResult<T> {
18 if !(0..=MAX_XRPL_TIME).contains(&time) {
19 Err(XRPLTimeRangeException::UnexpectedTimeOverflow {
20 max: MAX_XRPL_TIME,
21 found: time,
22 }
23 .into())
24 } else {
25 Ok(ok)
26 }
27}
28
29pub(crate) fn ripple_time_to_datetime(ripple_time: i64) -> XRPLUtilsResult<DateTime<Utc>> {
36 let datetime = Utc.timestamp_opt(ripple_time + RIPPLE_EPOCH, 0);
37 match datetime {
38 LocalResult::Single(dt) => _ripple_check_max(ripple_time, dt),
39 _ => Err(XRPLTimeRangeException::InvalidLocalTime.into()),
40 }
41}
42
43pub(crate) fn datetime_to_ripple_time(dt: DateTime<Utc>) -> XRPLUtilsResult<i64> {
50 let ripple_time = dt.timestamp() - RIPPLE_EPOCH;
51 _ripple_check_max(ripple_time, ripple_time)
52}
53
54pub fn ripple_time_to_posix(ripple_time: i64) -> XRPLUtilsResult<i64> {
77 _ripple_check_max(ripple_time, ripple_time + RIPPLE_EPOCH)
78}
79
80pub fn posix_to_ripple_time(timestamp: i64) -> XRPLUtilsResult<i64> {
103 let ripple_time = timestamp - RIPPLE_EPOCH;
104 _ripple_check_max(ripple_time, ripple_time)
105}
106
107#[cfg(test)]
108mod test {
109 use super::*;
110
111 #[test]
112 fn test_ripple_time_to_datetime() {
113 let success: DateTime<Utc> = ripple_time_to_datetime(RIPPLE_EPOCH).unwrap();
114 assert_eq!(success.timestamp(), RIPPLE_EPOCH + RIPPLE_EPOCH);
115 }
116
117 #[test]
118 fn test_datetime_to_ripple_time() {
119 let actual = match Utc.timestamp_opt(RIPPLE_EPOCH, 0) {
120 LocalResult::Single(dt) => datetime_to_ripple_time(dt),
121 _ => Err(XRPLTimeRangeException::InvalidLocalTime.into()),
122 };
123 assert_eq!(Ok(0_i64), actual);
124 }
125
126 #[test]
127 fn test_ripple_time_to_posix() {
128 assert_eq!(
129 ripple_time_to_posix(RIPPLE_EPOCH),
130 Ok(RIPPLE_EPOCH + RIPPLE_EPOCH)
131 );
132 }
133
134 #[test]
135 fn test_posix_to_ripple_time() {
136 assert_eq!(posix_to_ripple_time(RIPPLE_EPOCH), Ok(0_i64));
137 }
138
139 #[test]
140 fn accept_posix_round_trip() {
141 let current_time: i64 = Utc::now().timestamp();
142 let ripple_time: i64 = posix_to_ripple_time(current_time).unwrap();
143 let round_trip_time = ripple_time_to_posix(ripple_time);
144
145 assert_eq!(Ok(current_time), round_trip_time);
146 }
147
148 #[test]
149 fn accept_datetime_round_trip() -> XRPLUtilsResult<()> {
150 let current_time: DateTime<Utc> = match Utc.timestamp_opt(Utc::now().timestamp(), 0) {
151 LocalResult::Single(dt) => dt,
152 _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()),
153 };
154 let ripple_time: i64 = datetime_to_ripple_time(current_time).unwrap();
155 let round_trip_time = ripple_time_to_datetime(ripple_time);
156
157 assert_eq!(Ok(current_time), round_trip_time);
158
159 Ok(())
160 }
161
162 #[test]
163 fn accept_ripple_epoch() -> XRPLUtilsResult<()> {
164 let expected = match Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0) {
165 LocalResult::Single(dt) => dt,
166 _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()),
167 };
168 assert_eq!(Ok(expected), ripple_time_to_datetime(0));
169
170 Ok(())
171 }
172
173 #[test]
175 fn accept_datetime_underflow() -> XRPLUtilsResult<()> {
176 let datetime: DateTime<Utc> = match Utc.with_ymd_and_hms(1999, 1, 1, 0, 0, 0) {
177 LocalResult::Single(dt) => dt,
178 _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()),
179 };
180 assert!(datetime_to_ripple_time(datetime).is_err());
181
182 Ok(())
183 }
184
185 #[test]
187 fn accept_posix_underflow() -> XRPLUtilsResult<()> {
188 let datetime: DateTime<Utc> = match Utc.with_ymd_and_hms(1999, 1, 1, 0, 0, 0) {
189 LocalResult::Single(dt) => dt,
190 _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()),
191 };
192 assert!(posix_to_ripple_time(datetime.timestamp()).is_err());
193
194 Ok(())
195 }
196
197 #[test]
203 fn accept_datetime_overflow() -> XRPLUtilsResult<()> {
204 let datetime: DateTime<Utc> = match Utc.with_ymd_and_hms(2137, 1, 1, 0, 0, 0) {
205 LocalResult::Single(dt) => dt,
206 _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()),
207 };
208 assert!(datetime_to_ripple_time(datetime).is_err());
209
210 Ok(())
211 }
212
213 #[test]
214 fn accept_posix_overflow() -> XRPLUtilsResult<()> {
215 let datetime: DateTime<Utc> = match Utc.with_ymd_and_hms(2137, 1, 1, 0, 0, 0) {
216 LocalResult::Single(dt) => dt,
217 _ => return Err(XRPLTimeRangeException::InvalidLocalTime.into()),
218 };
219 assert!(posix_to_ripple_time(datetime.timestamp()).is_err());
220
221 Ok(())
222 }
223}