1use core::time::Duration;
2
3use tz::datetime::DateTime;
4
5use super::error::{IntOverflowError, TzOutOfRangeError};
6use super::{Time, TimeError};
7use crate::NANOS_IN_SECOND;
8
9impl Time {
10 #[inline]
32 #[expect(
33 clippy::missing_panics_doc,
34 reason = "Rounding should never cause an error generating a new time since it's always a truncation"
35 )]
36 pub fn round(&self, ndigits: u32) -> Self {
37 match ndigits {
38 9..=u32::MAX => *self,
39 num_digits => {
57 let local_time_type = *self.inner.local_time_type();
58 let mut unix_time = self.to_int();
59 let nanos = self.nanoseconds();
60
61 let truncating_divisor = 10_u64.pow(9 - num_digits - 1);
64 let rounding_multiple = 10_u64.pow(9 - num_digits);
65
66 let truncated = u64::from(nanos) / truncating_divisor;
67 let mut new_nanos = if truncated % 10 >= 5 {
68 ((truncated / 10) + 1) * rounding_multiple
69 } else {
70 (truncated / 10) * rounding_multiple
71 }
72 .try_into()
73 .expect("new nanos are a truncated version of input which is in bounds for u32");
74
75 if new_nanos >= NANOS_IN_SECOND {
76 unix_time += 1;
77 new_nanos -= NANOS_IN_SECOND;
78 }
79
80 let dt = DateTime::from_timespec_and_local(unix_time, new_nanos, local_time_type)
82 .expect("Could not round the datetime");
83 Self {
84 inner: dt,
85 offset: self.offset,
86 }
87 }
88 }
89 }
90}
91
92impl Time {
94 pub fn checked_add(self, duration: Duration) -> Result<Self, TimeError> {
102 let unix_time = self.inner.unix_time();
103 let nanoseconds = self.inner.nanoseconds();
104 let offset = self.offset;
105
106 let duration_seconds = i64::try_from(duration.as_secs())?;
107 let duration_subsecs = duration.subsec_nanos();
108
109 let mut seconds = unix_time.checked_add(duration_seconds).ok_or(IntOverflowError::new())?;
110 let mut nanoseconds = nanoseconds
111 .checked_add(duration_subsecs)
112 .ok_or(IntOverflowError::new())?;
113
114 if nanoseconds > NANOS_IN_SECOND {
115 seconds += 1;
116 nanoseconds -= NANOS_IN_SECOND;
117 }
118
119 Self::with_timespec_and_offset(seconds, nanoseconds, offset)
120 }
121
122 pub fn checked_add_i64(&self, seconds: i64) -> Result<Self, TimeError> {
130 if seconds.is_negative() {
131 let seconds = seconds
132 .checked_neg()
133 .and_then(|secs| u64::try_from(secs).ok())
134 .ok_or(IntOverflowError::new())?;
135 self.checked_sub_u64(seconds)
136 } else {
137 let seconds = u64::try_from(seconds).map_err(|_| IntOverflowError::new())?;
138 self.checked_add_u64(seconds)
139 }
140 }
141
142 pub fn checked_add_u64(&self, seconds: u64) -> Result<Self, TimeError> {
150 let duration = Duration::from_secs(seconds);
151 self.checked_add(duration)
152 }
153
154 pub fn checked_add_f64(&self, seconds: f64) -> Result<Self, TimeError> {
162 if seconds.is_nan() || seconds.is_infinite() {
164 return Err(TzOutOfRangeError::new().into());
165 }
166
167 if seconds.is_sign_positive() {
168 self.checked_add(Duration::try_from_secs_f64(seconds)?)
169 } else {
170 self.checked_sub(Duration::try_from_secs_f64(-seconds)?)
171 }
172 }
173}
174
175impl Time {
177 pub fn checked_sub(self, duration: Duration) -> Result<Self, TimeError> {
185 let unix_time = self.inner.unix_time();
186 let nanoseconds = self.inner.nanoseconds();
187 let offset = self.offset;
188
189 let duration_seconds = i64::try_from(duration.as_secs())?;
190 let duration_subsecs = duration.subsec_nanos();
191
192 let mut seconds = unix_time.checked_sub(duration_seconds).ok_or(IntOverflowError::new())?;
193 let nanoseconds = if let Some(nanos) = nanoseconds.checked_sub(duration_subsecs) {
194 nanos
195 } else {
196 seconds -= 1;
197 nanoseconds + NANOS_IN_SECOND - duration_subsecs
198 };
199
200 Self::with_timespec_and_offset(seconds, nanoseconds, offset)
201 }
202
203 pub fn checked_sub_i64(self, seconds: i64) -> Result<Self, TimeError> {
211 if seconds.is_negative() {
212 let seconds = seconds
213 .checked_neg()
214 .and_then(|secs| u64::try_from(secs).ok())
215 .ok_or(IntOverflowError::new())?;
216 self.checked_add_u64(seconds)
217 } else {
218 let seconds = u64::try_from(seconds).map_err(|_| IntOverflowError::new())?;
219 self.checked_sub_u64(seconds)
220 }
221 }
222
223 pub fn checked_sub_u64(self, seconds: u64) -> Result<Self, TimeError> {
231 let duration = Duration::from_secs(seconds);
232 self.checked_sub(duration)
233 }
234
235 pub fn checked_sub_f64(self, seconds: f64) -> Result<Self, TimeError> {
243 if seconds.is_nan() || seconds.is_infinite() {
245 return Err(TzOutOfRangeError::new().into());
246 }
247
248 if seconds.is_sign_positive() {
249 self.checked_sub(Duration::try_from_secs_f64(seconds)?)
250 } else {
251 self.checked_add(Duration::try_from_secs_f64(-seconds)?)
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 fn datetime() -> Time {
261 Time::utc(2019, 4, 7, 23, 59, 59, 500_000_000).unwrap()
263 }
264
265 #[test]
266 fn rounding() {
267 let dt = Time::utc(2010, 3, 30, 5, 43, 25, 123_456_789).unwrap();
268 assert_eq!(0, dt.round(0).nanoseconds());
269 assert_eq!(100_000_000, dt.round(1).nanoseconds());
270 assert_eq!(120_000_000, dt.round(2).nanoseconds());
271 assert_eq!(123_000_000, dt.round(3).nanoseconds());
272 assert_eq!(123_500_000, dt.round(4).nanoseconds());
273 assert_eq!(123_460_000, dt.round(5).nanoseconds());
274 assert_eq!(123_457_000, dt.round(6).nanoseconds());
275 assert_eq!(123_456_800, dt.round(7).nanoseconds());
276 assert_eq!(123_456_790, dt.round(8).nanoseconds());
277 assert_eq!(123_456_789, dt.round(9).nanoseconds());
278 assert_eq!(123_456_789, dt.round(10).nanoseconds());
279 assert_eq!(123_456_789, dt.round(11).nanoseconds());
280 }
281
282 #[test]
283 fn rounding_rollup() {
284 let dt = Time::utc(1999, 12, 31, 23, 59, 59, 900_000_000).unwrap();
285 let rounded = dt.round(0);
286 let dt_unix = dt.to_int();
287 let rounded_unix = rounded.to_int();
288 assert_eq!(0, rounded.nanoseconds());
289 assert_eq!(dt_unix + 1, rounded_unix);
290 }
291
292 #[test]
293 fn add_int_to_time() {
294 let dt = datetime();
295 let succ: Time = dt.checked_add_u64(1).unwrap();
296 assert_eq!(dt.to_int() + 1, succ.to_int());
297 assert_eq!(dt.year(), succ.year());
298 assert_eq!(dt.month(), succ.month());
299 assert_ne!(dt.day(), succ.day());
300 assert_ne!(dt.hour(), succ.hour());
301 assert_ne!(dt.minute(), succ.minute());
302 assert_eq!(succ.second(), 0);
303 if succ.nanoseconds() > 500_000_000 {
305 assert!(succ.nanoseconds() - 500_000_000 < 50);
306 } else {
307 assert!(500_000_000 - succ.nanoseconds() < 50);
308 }
309 }
310
311 #[test]
312 fn add_out_of_fixnum_range_float_sec() {
313 let dt = datetime();
314 dt.checked_add_f64(f64::MAX).unwrap_err();
315
316 let dt = datetime();
317 dt.checked_add_f64(f64::MIN).unwrap_err();
318 }
319
320 #[test]
321 fn add_subsec_float_to_time() {
322 let dt = datetime();
323 let succ: Time = dt.checked_add_f64(0.2).unwrap();
324 assert_eq!(dt.to_int(), succ.to_int());
325 assert_eq!(dt.year(), succ.year());
326 assert_eq!(dt.month(), succ.month());
327 assert_eq!(dt.day(), succ.day());
328 assert_eq!(dt.hour(), succ.hour());
329 assert_eq!(dt.minute(), succ.minute());
330 assert_eq!(succ.second(), 59);
331 if succ.nanoseconds() > 700_000_000 {
333 assert!(succ.nanoseconds() - 700_000_000 < 50);
334 } else {
335 assert!(700_000_000 - succ.nanoseconds() < 50);
336 }
337
338 let dt = datetime();
339 let succ: Time = dt.checked_add_f64(0.7).unwrap();
340 assert_eq!(dt.to_int() + 1, succ.to_int());
341 assert_eq!(dt.year(), succ.year());
342 assert_eq!(dt.month(), succ.month());
343 assert_ne!(dt.day(), succ.day());
344 assert_ne!(dt.hour(), succ.hour());
345 assert_ne!(dt.minute(), succ.minute());
346 assert_eq!(succ.second(), 0);
347 if succ.nanoseconds() > 200_000_000 {
349 assert!(succ.nanoseconds() - 200_000_000 < 50);
350 } else {
351 assert!(200_000_000 - succ.nanoseconds() < 50);
352 }
353 }
354
355 #[test]
356 fn add_float_to_time() {
357 let dt = datetime();
358 let succ: Time = dt.checked_add_f64(1.2).unwrap();
359 assert_eq!(dt.to_int() + 1, succ.to_int());
360 assert_eq!(dt.year(), succ.year());
361 assert_eq!(dt.month(), succ.month());
362 assert_ne!(dt.day(), succ.day());
363 assert_ne!(dt.hour(), succ.hour());
364 assert_ne!(dt.minute(), succ.minute());
365 assert_eq!(succ.second(), 0);
366 if succ.nanoseconds() > 700_000_000 {
368 assert!(succ.nanoseconds() - 700_000_000 < 50);
369 } else {
370 assert!(700_000_000 - succ.nanoseconds() < 50);
371 }
372
373 let dt = datetime();
374 let succ: Time = dt.checked_add_f64(1.7).unwrap();
375 assert_eq!(dt.to_int() + 2, succ.to_int());
376 assert_eq!(dt.year(), succ.year());
377 assert_eq!(dt.month(), succ.month());
378 assert_ne!(dt.day(), succ.day());
379 assert_ne!(dt.hour(), succ.hour());
380 assert_ne!(dt.minute(), succ.minute());
381 assert_eq!(succ.second(), 1);
382 if succ.nanoseconds() > 200_000_000 {
384 assert!(succ.nanoseconds() - 200_000_000 < 50);
385 } else {
386 assert!(200_000_000 - succ.nanoseconds() < 50);
387 }
388 }
389
390 #[test]
391 fn sub_int_to_time() {
392 let dt = datetime();
393 let succ: Time = dt.checked_sub_u64(1).unwrap();
394 assert_eq!(dt.to_int() - 1, succ.to_int());
395 assert_eq!(dt.year(), succ.year());
396 assert_eq!(dt.month(), succ.month());
397 assert_eq!(dt.day(), succ.day());
398 assert_eq!(dt.hour(), succ.hour());
399 assert_eq!(dt.minute(), succ.minute());
400 assert_eq!(succ.second(), 58);
401 if succ.nanoseconds() > 500_000_000 {
403 assert!(succ.nanoseconds() - 500_000_000 < 50);
404 } else {
405 assert!(500_000_000 - succ.nanoseconds() < 50);
406 }
407 }
408
409 #[test]
410 fn sub_out_of_fixnum_range_float_sec() {
411 let dt = datetime();
412 dt.checked_sub_f64(f64::MAX).unwrap_err();
413
414 let dt = datetime();
415 dt.checked_sub_f64(f64::MIN).unwrap_err();
416 }
417
418 #[test]
419 fn sub_subsec_float_to_time() {
420 let dt = datetime();
421 let succ: Time = dt.checked_sub_f64(0.2).unwrap();
422 assert_eq!(dt.to_int(), succ.to_int());
423 assert_eq!(dt.year(), succ.year());
424 assert_eq!(dt.month(), succ.month());
425 assert_eq!(dt.day(), succ.day());
426 assert_eq!(dt.hour(), succ.hour());
427 assert_eq!(dt.minute(), succ.minute());
428 assert_eq!(succ.second(), 59);
429 if succ.nanoseconds() > 300_000_000 {
431 assert!(succ.nanoseconds() - 300_000_000 < 50);
432 } else {
433 assert!(300_000_000 - succ.nanoseconds() < 50);
434 }
435
436 let dt = datetime();
437 let succ: Time = dt.checked_sub_f64(0.7).unwrap();
438 assert_eq!(dt.to_int() - 1, succ.to_int());
439 assert_eq!(dt.year(), succ.year());
440 assert_eq!(dt.month(), succ.month());
441 assert_eq!(dt.day(), succ.day());
442 assert_eq!(dt.hour(), succ.hour());
443 assert_eq!(dt.minute(), succ.minute());
444 assert_eq!(succ.second(), 58);
445 if succ.nanoseconds() > 800_000_000 {
447 assert!(succ.nanoseconds() - 800_000_000 < 50);
448 } else {
449 assert!(800_000_000 - succ.nanoseconds() < 50);
450 }
451 }
452
453 #[test]
454 fn sub_float_to_time() {
455 let dt = datetime();
456 let succ: Time = dt.checked_sub_f64(1.2).unwrap();
457 assert_eq!(dt.to_int() - 1, succ.to_int());
458 assert_eq!(dt.year(), succ.year());
459 assert_eq!(dt.month(), succ.month());
460 assert_eq!(dt.day(), succ.day());
461 assert_eq!(dt.hour(), succ.hour());
462 assert_eq!(dt.minute(), succ.minute());
463 assert_eq!(succ.second(), 58);
464 if succ.nanoseconds() > 300_000_000 {
466 assert!(succ.nanoseconds() - 300_000_000 < 50);
467 } else {
468 assert!(300_000_000 - succ.nanoseconds() < 50);
469 }
470
471 let dt = datetime();
472 let succ: Time = dt.checked_sub_f64(1.7).unwrap();
473 assert_eq!(dt.to_int() - 2, succ.to_int());
474 assert_eq!(dt.year(), succ.year());
475 assert_eq!(dt.month(), succ.month());
476 assert_eq!(dt.day(), succ.day());
477 assert_eq!(dt.hour(), succ.hour());
478 assert_eq!(dt.minute(), succ.minute());
479 assert_eq!(succ.second(), 57);
480 if succ.nanoseconds() > 800_000_000 {
482 assert!(succ.nanoseconds() - 800_000_000 < 50);
483 } else {
484 assert!(800_000_000 - succ.nanoseconds() < 50);
485 }
486 }
487}