1use serde::{Deserialize, Serialize};
2use std::ops::{Add, Sub};
3
4#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct Duration {
9 pub secs: i64,
10 pub nanos: i32,
11}
12
13impl Duration {
14 pub fn zero() -> Self { Self { secs: 0, nanos: 0 } }
15 pub fn seconds(secs: i64) -> Self { Self { secs, nanos: 0 } }
16 pub fn minutes(mins: i64) -> Self { Self { secs: mins * 60, nanos: 0 } }
17 pub fn hours(hours: i64) -> Self { Self { secs: hours * 3600, nanos: 0 } }
18 pub fn days(days: i64) -> Self { Self { secs: days * 86400, nanos: 0 } }
19 pub fn milliseconds(ms: i64) -> Self {
20 Self {
21 secs: ms / 1000,
22 nanos: ((ms % 1000) * 1_000_000) as i32,
23 }
24 }
25 pub fn microseconds(us: i64) -> Self {
26 Self {
27 secs: us / 1_000_000,
28 nanos: ((us % 1_000_000) * 1000) as i32,
29 }
30 }
31 pub fn nanoseconds(ns: i64) -> Self {
32 Self {
33 secs: ns / 1_000_000_000,
34 nanos: (ns % 1_000_000_000) as i32,
35 }
36 }
37
38 pub fn num_seconds(&self) -> i64 {
39 self.secs
40 }
41
42 pub fn num_minutes(&self) -> i64 {
43 self.secs / 60
44 }
45
46 pub fn num_hours(&self) -> i64 {
47 self.secs / 3600
48 }
49
50 pub fn num_days(&self) -> i64 {
51 self.secs / 86400
52 }
53
54 pub fn num_milliseconds(&self) -> i64 {
55 self.secs * 1000 + (self.nanos / 1_000_000) as i64
56 }
57}
58
59pub fn is_leap_year(year: i32) -> bool {
63 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
64}
65
66pub fn days_in_month(year: i32, month: u32) -> u32 {
67 match month {
68 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
69 4 | 6 | 9 | 11 => 30,
70 2 => if is_leap_year(year) { 29 } else { 28 },
71 _ => 0,
72 }
73}
74
75pub fn epoch_days_to_date(epoch_days: i64) -> (i32, u32, u32) {
77 let z = epoch_days + 719468;
78 let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
79 let doe = (z - era * 146097) as u32;
80 let yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
81 let mut y = (yoe as i32) + era as i32 * 400;
82 let doy = doe - (365*yoe + yoe/4 - yoe/100);
83 let mp = (5*doy + 2)/153;
84 let d = doy - (153*mp + 2)/5 + 1;
85 let m = if mp < 10 { mp + 3 } else { mp - 9 };
86 if m <= 2 {
87 y += 1;
88 }
89 (y, m, d)
90}
91
92pub fn date_to_epoch_days(y: i32, m: u32, d: u32) -> i64 {
93 let y = y - if m <= 2 { 1 } else { 0 };
94 let era = (if y >= 0 { y } else { y - 399 }) / 400;
95 let yoe = (y - era * 400) as u32;
96 let m_adj = m as i32;
97 let doy = (153 * (if m_adj > 2 { m_adj - 3 } else { m_adj + 9 }) + 2)/5 + (d as i32 - 1);
98 let doe = yoe as i32 * 365 + yoe as i32 / 4 - yoe as i32 / 100 + doy;
99 era as i64 * 146097 + doe as i64 - 719468
100}
101
102#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
106pub struct NaiveDate {
107 y: i32,
108 m: u32,
109 d: u32,
110}
111
112impl NaiveDate {
113 pub fn from_ymd_opt(y: i32, m: u32, d: u32) -> Option<Self> {
114 if (1..=12).contains(&m) && d >= 1 && d <= days_in_month(y, m) {
115 Some(Self { y, m, d })
116 } else {
117 None
118 }
119 }
120 pub fn year(&self) -> i32 { self.y }
121 pub fn month(&self) -> u32 { self.m }
122 pub fn day(&self) -> u32 { self.d }
123}
124
125#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
126pub struct NaiveTime {
127 hour: u32,
128 min: u32,
129 sec: u32,
130 pub nano: u32,
131}
132
133impl NaiveTime {
134 pub fn from_hms_opt(hour: u32, min: u32, sec: u32) -> Option<Self> {
135 Self::from_hms_nano_opt(hour, min, sec, 0)
136 }
137
138 pub fn from_hms_nano_opt(hour: u32, min: u32, sec: u32, nano: u32) -> Option<Self> {
139 if hour < 24 && min < 60 && sec < 60 && nano < 1_000_000_000 {
140 Some(Self { hour, min, sec, nano })
141 } else {
142 None
143 }
144 }
145}
146
147#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub struct NaiveDateTime {
149 pub date: NaiveDate,
150 pub time: NaiveTime,
151}
152
153impl NaiveDateTime {
154 pub fn new(date: NaiveDate, time: NaiveTime) -> Self {
155 Self { date, time }
156 }
157
158 pub fn from_timestamp_opt(secs: i64, nsecs: u32) -> Option<Self> {
159 if nsecs >= 1_000_000_000 { return None; }
160 let days = if secs >= 0 { secs / 86400 } else { (secs - 86399) / 86400 };
161 let mut rem_secs = (secs - days * 86400) as u32;
162 let hour = rem_secs / 3600;
163 rem_secs %= 3600;
164 let min = rem_secs / 60;
165 let sec = rem_secs % 60;
166 let (y, m, d) = epoch_days_to_date(days);
167 Some(Self {
168 date: NaiveDate { y, m, d },
169 time: NaiveTime { hour, min, sec, nano: nsecs },
170 })
171 }
172
173 pub fn timestamp(&self) -> i64 {
174 let days = date_to_epoch_days(self.date.y, self.date.m, self.date.d);
175 days * 86400 + (self.time.hour as i64 * 3600) + (self.time.min as i64 * 60) + (self.time.sec as i64)
176 }
177
178 pub fn date(&self) -> NaiveDate { self.date }
179 pub fn time(&self) -> NaiveTime { self.time }
180
181 pub fn format<'a>(&'a self, fmt_str: &'a str) -> Format<'a> {
182 Format {
183 dt: self,
184 offset_secs: 0,
185 fmt_str,
186 }
187 }
188
189 pub fn signed_duration_since(&self, other: NaiveDateTime) -> Duration {
190 let diff_secs = self.timestamp() - other.timestamp();
191 let diff_nanos = self.time.nano as i64 - other.time.nano as i64;
192 let (final_secs, final_nanos) = if diff_nanos < 0 {
193 (diff_secs - 1, diff_nanos + 1_000_000_000)
194 } else {
195 (diff_secs, diff_nanos)
196 };
197 Duration {
198 secs: final_secs,
199 nanos: final_nanos as i32,
200 }
201 }
202}
203
204impl std::fmt::Display for NaiveDate {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 write!(f, "{:04}-{:02}-{:02}", self.y, self.m, self.d)
207 }
208}
209
210impl std::fmt::Display for NaiveTime {
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 write!(f, "{:02}:{:02}:{:02}", self.hour, self.min, self.sec)?;
213 if self.nano > 0 {
214 write!(f, ".{:09}", self.nano)?;
215 }
216 Ok(())
217 }
218}
219
220impl std::fmt::Display for NaiveDateTime {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 write!(f, "{} {}", self.date, self.time)
223 }
224}
225
226pub struct Format<'a> {
228 dt: &'a NaiveDateTime,
229 offset_secs: i32,
230 fmt_str: &'a str,
231}
232
233impl<'a> std::fmt::Display for Format<'a> {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 let mut chars = self.fmt_str.chars().peekable();
236 while let Some(c) = chars.next() {
237 if c == '%' {
238 match chars.next() {
239 Some('Y') => write!(f, "{:04}", self.dt.date.y)?,
240 Some('m') => write!(f, "{:02}", self.dt.date.m)?,
241 Some('d') => write!(f, "{:02}", self.dt.date.d)?,
242 Some('H') => write!(f, "{:02}", self.dt.time.hour)?,
243 Some('M') => write!(f, "{:02}", self.dt.time.min)?,
244 Some('S') => write!(f, "{:02}", self.dt.time.sec)?,
245 Some('f') => write!(f, "{:09}", self.dt.time.nano)?,
246 Some('.') => {
247 if chars.peek() == Some(&'3') {
248 chars.next();
249 if chars.peek() == Some(&'f') {
250 chars.next();
251 write!(f, ".{:03}", self.dt.time.nano / 1_000_000)?;
252 } else {
253 write!(f, "%.3")?;
254 }
255 } else if chars.peek() == Some(&'6') {
256 chars.next();
257 if chars.peek() == Some(&'f') {
258 chars.next();
259 write!(f, ".{:06}", self.dt.time.nano / 1_000)?;
260 } else {
261 write!(f, "%.6")?;
262 }
263 } else {
264 write!(f, "%.")?;
265 }
266 }
267 Some('z') => {
268 let sign = if self.offset_secs >= 0 { '+' } else { '-' };
269 let abs_offset = self.offset_secs.abs();
270 let hours = abs_offset / 3600;
271 let mins = (abs_offset % 3600) / 60;
272 write!(f, "{}{:02}{:02}", sign, hours, mins)?;
273 }
274 Some('%') => write!(f, "%")?,
275 Some(other) => write!(f, "%{}", other)?,
276 None => write!(f, "%")?,
277 }
278 } else {
279 write!(f, "{}", c)?;
280 }
281 }
282 Ok(())
283 }
284}
285
286pub fn parse_naive_datetime(s: &str) -> Result<NaiveDateTime, String> {
288 let s = s.trim();
289 if s.len() < 19 {
290 return Err(format!("Datetime string too short: {}", s));
291 }
292 let year: i32 = s[0..4].parse().map_err(|_| "Invalid year")?;
293 let month: u32 = s[5..7].parse().map_err(|_| "Invalid month")?;
294 let day: u32 = s[8..10].parse().map_err(|_| "Invalid day")?;
295 let hour: u32 = s[11..13].parse().map_err(|_| "Invalid hour")?;
296 let min: u32 = s[14..16].parse().map_err(|_| "Invalid minute")?;
297 let sec: u32 = s[17..19].parse().map_err(|_| "Invalid second")?;
298
299 let mut nano = 0;
300 if s.len() > 19 && (s.as_bytes()[19] == b'.' || s.as_bytes()[19] == b',') {
301 let rest = &s[20..];
302 let end = rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(rest.len());
303 let digit_str = &rest[..end];
304 if !digit_str.is_empty() {
305 let val: u32 = digit_str.parse().map_err(|_| "Invalid subsecond")?;
306 let len = digit_str.len() as u32;
307 let multiplier = match len {
308 1 => 100_000_000,
309 2 => 10_000_000,
310 3 => 1_000_000,
311 4 => 100_000,
312 5 => 10_000,
313 6 => 1_000,
314 7 => 100,
315 8 => 10,
316 9 => 1,
317 _ => 0,
318 };
319 if len <= 9 {
320 nano = val * multiplier;
321 } else {
322 nano = digit_str[..9].parse::<u32>().unwrap();
323 }
324 }
325 }
326
327 let date = NaiveDate { y: year, m: month, d: day };
328 let time = NaiveTime::from_hms_nano_opt(hour, min, sec, nano).ok_or("Invalid time components")?;
329 Ok(NaiveDateTime::new(date, time))
330}
331
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336pub enum LocalResult<T> {
337 None,
338 Single(T),
339 Ambiguous(T, T),
340}
341
342impl<T> LocalResult<T> {
343 pub fn single(self) -> Option<T> {
344 match self {
345 LocalResult::Single(t) => Some(t),
346 _ => None,
347 }
348 }
349 pub fn unwrap(self) -> T {
350 match self {
351 LocalResult::Single(t) => t,
352 _ => panic!("LocalResult::unwrap failed"),
353 }
354 }
355}
356
357pub trait Offset: Copy + Clone + std::fmt::Display {
358 fn fix(&self) -> FixedOffset;
359}
360
361pub trait TimeZone: Sized + Copy + Clone {
362 type Offset: Offset;
363 fn from_offset(offset: &Self::Offset) -> Self;
364 fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<Self::Offset>;
365 fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset>;
366 fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset;
367 fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset;
368
369 #[allow(clippy::wrong_self_convention)]
372 fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Self> {
373 let offset = self.offset_from_utc_datetime(utc);
374 let fixed = offset.fix();
375 let local_secs = utc.timestamp() + fixed.local_minus_utc() as i64;
376 let naive = NaiveDateTime::from_timestamp_opt(local_secs, utc.time.nano).unwrap();
377 DateTime { naive, offset, tz: *self }
378 }
379}
380
381#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
382pub struct FixedOffset {
383 local_minus_utc: i32,
384}
385
386impl FixedOffset {
387 pub fn east_opt(secs: i32) -> Option<Self> {
388 if secs.abs() <= 86400 {
389 Some(Self { local_minus_utc: secs })
390 } else {
391 None
392 }
393 }
394 pub fn west_opt(secs: i32) -> Option<Self> {
395 if secs.abs() <= 86400 {
396 Some(Self { local_minus_utc: -secs })
397 } else {
398 None
399 }
400 }
401 pub fn local_minus_utc(&self) -> i32 {
402 self.local_minus_utc
403 }
404}
405
406impl Offset for FixedOffset {
407 fn fix(&self) -> FixedOffset {
408 *self
409 }
410}
411
412impl std::fmt::Display for FixedOffset {
413 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414 let sign = if self.local_minus_utc >= 0 { '+' } else { '-' };
415 let abs_offset = self.local_minus_utc.abs();
416 let hours = abs_offset / 3600;
417 let mins = (abs_offset % 3600) / 60;
418 write!(f, "{}{:02}:{:02}", sign, hours, mins)
419 }
420}
421
422impl TimeZone for FixedOffset {
423 type Offset = FixedOffset;
424
425 fn from_offset(offset: &Self::Offset) -> Self {
426 *offset
427 }
428
429 fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<Self::Offset> {
430 LocalResult::Single(*self)
431 }
432
433 fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<Self::Offset> {
434 LocalResult::Single(*self)
435 }
436
437 fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Self::Offset {
438 *self
439 }
440
441 fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> Self::Offset {
442 *self
443 }
444}
445
446#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
447pub struct Utc;
448
449impl TimeZone for Utc {
450 type Offset = FixedOffset;
451
452 fn from_offset(_offset: &Self::Offset) -> Self {
453 Utc
454 }
455
456 fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<Self::Offset> {
457 LocalResult::Single(FixedOffset::east_opt(0).unwrap())
458 }
459
460 fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<Self::Offset> {
461 LocalResult::Single(FixedOffset::east_opt(0).unwrap())
462 }
463
464 fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Self::Offset {
465 FixedOffset::east_opt(0).unwrap()
466 }
467
468 fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> Self::Offset {
469 FixedOffset::east_opt(0).unwrap()
470 }
471}
472
473impl Utc {
474 pub fn now() -> DateTime<Utc> {
475 let now_system = std::time::SystemTime::now();
476 let duration = now_system.duration_since(std::time::UNIX_EPOCH).unwrap();
477 let naive = NaiveDateTime::from_timestamp_opt(duration.as_secs() as i64, duration.subsec_nanos()).unwrap();
478 DateTime {
479 naive,
480 offset: FixedOffset::east_opt(0).unwrap(),
481 tz: Utc,
482 }
483 }
484}
485
486#[repr(C)]
487struct tm {
488 tm_sec: i32,
489 tm_min: i32,
490 tm_hour: i32,
491 tm_mday: i32,
492 tm_mon: i32,
493 tm_year: i32,
494 tm_wday: i32,
495 tm_yday: i32,
496 tm_isdst: i32,
497 tm_gmtoff: i64,
498 tm_zone: *const std::os::raw::c_char,
499}
500
501unsafe extern "C" {
502 fn localtime_r(timep: *const i64, result: *mut tm) -> *mut tm;
503}
504
505fn get_local_offset_secs(secs: i64) -> i32 {
506 unsafe {
507 let mut t = tm {
508 tm_sec: 0,
509 tm_min: 0,
510 tm_hour: 0,
511 tm_mday: 0,
512 tm_mon: 0,
513 tm_year: 0,
514 tm_wday: 0,
515 tm_yday: 0,
516 tm_isdst: 0,
517 tm_gmtoff: 0,
518 tm_zone: std::ptr::null(),
519 };
520 localtime_r(&secs, &mut t);
521 t.tm_gmtoff as i32
522 }
523}
524
525#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
526pub struct Local;
527
528impl TimeZone for Local {
529 type Offset = FixedOffset;
530
531 fn from_offset(_offset: &Self::Offset) -> Self {
532 Local
533 }
534
535 fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<Self::Offset> {
536 let naive = NaiveDateTime::new(*local, NaiveTime { hour: 0, min: 0, sec: 0, nano: 0 });
537 self.offset_from_local_datetime(&naive)
538 }
539
540 fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset> {
541 let utc_approx = local.timestamp();
542 let offset = get_local_offset_secs(utc_approx);
543 LocalResult::Single(FixedOffset::east_opt(offset).unwrap())
544 }
545
546 fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset {
547 let naive = NaiveDateTime::new(*utc, NaiveTime { hour: 0, min: 0, sec: 0, nano: 0 });
548 self.offset_from_utc_datetime(&naive)
549 }
550
551 fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
552 let offset = get_local_offset_secs(utc.timestamp());
553 FixedOffset::east_opt(offset).unwrap()
554 }
555}
556
557impl Local {
558 pub fn now() -> DateTime<Local> {
559 let now_system = std::time::SystemTime::now();
560 let duration = now_system.duration_since(std::time::UNIX_EPOCH).unwrap();
561 let secs = duration.as_secs() as i64;
562 let offset_secs = get_local_offset_secs(secs);
563 let naive = NaiveDateTime::from_timestamp_opt(secs + offset_secs as i64, duration.subsec_nanos()).unwrap();
564 DateTime {
565 naive,
566 offset: FixedOffset::east_opt(offset_secs).unwrap(),
567 tz: Local,
568 }
569 }
570}
571
572#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
576pub struct DateTime<Tz: TimeZone> {
577 pub naive: NaiveDateTime,
578 pub offset: Tz::Offset,
579 pub tz: Tz,
580}
581
582impl<Tz: TimeZone> DateTime<Tz> {
583 pub fn naive_utc(&self) -> NaiveDateTime {
584 let offset = self.offset.fix();
585 let utc_secs = self.naive.timestamp() - offset.local_minus_utc() as i64;
586 NaiveDateTime::from_timestamp_opt(utc_secs, self.naive.time.nano).unwrap()
587 }
588
589 pub fn timestamp(&self) -> i64 {
590 let offset = self.offset.fix();
591 self.naive.timestamp() - offset.local_minus_utc() as i64
592 }
593
594 pub fn naive_local(&self) -> NaiveDateTime {
595 self.naive
596 }
597
598 pub fn with_timezone<Tz2: TimeZone>(&self, tz2: &Tz2) -> DateTime<Tz2> {
599 let utc_dt = self.naive_utc();
600 tz2.from_utc_datetime(&utc_dt)
601 }
602
603 pub fn format<'a>(&'a self, fmt_str: &'a str) -> Format<'a> {
604 Format {
605 dt: &self.naive,
606 offset_secs: self.offset.fix().local_minus_utc(),
607 fmt_str,
608 }
609 }
610}
611
612pub fn parse_offset_secs(offset_str: &str) -> Option<i32> {
613 let offset_str = offset_str.trim();
614 if offset_str == "Z" || offset_str.eq_ignore_ascii_case("utc") || offset_str.is_empty() {
615 return Some(0);
616 }
617 let sign = match offset_str.chars().next()? {
618 '+' => 1,
619 '-' => -1,
620 _ => return None,
621 };
622 let rest = &offset_str[1..];
623 let parts: Vec<&str> = rest.split(':').collect();
624 if parts.len() == 2 {
625 let hours: i32 = parts[0].parse().ok()?;
626 let minutes: i32 = parts[1].parse().ok()?;
627 Some(sign * (hours * 3600 + minutes * 60))
628 } else if parts.len() == 1 {
629 if rest.len() == 4 {
630 let hours: i32 = rest[0..2].parse().ok()?;
631 let minutes: i32 = rest[2..4].parse().ok()?;
632 Some(sign * (hours * 3600 + minutes * 60))
633 } else if rest.len() == 2 || rest.len() == 1 {
634 let hours: i32 = rest.parse().ok()?;
635 Some(sign * hours * 3600)
636 } else {
637 None
638 }
639 } else {
640 None
641 }
642}
643
644impl DateTime<FixedOffset> {
645 pub fn parse_from_rfc3339(s: &str) -> Result<Self, String> {
646 let s = s.trim();
647 if s.len() < 19 {
648 return Err("Datetime string too short".to_string());
649 }
650 let tz_idx = s[19..]
651 .find(['Z', '+', '-'])
652 .map(|idx| idx + 19)
653 .ok_or_else(|| "Timezone offset missing".to_string())?;
654 let naive_str = &s[..tz_idx];
655 let offset_str = &s[tz_idx..];
656 let naive = parse_naive_datetime(naive_str)?;
657 let offset_secs = parse_offset_secs(offset_str).ok_or("Invalid offset offset format")?;
658 let utc_secs = naive.timestamp() - offset_secs as i64;
659 let utc_naive = NaiveDateTime::from_timestamp_opt(utc_secs, naive.time.nano).ok_or("Invalid timestamp")?;
660 let tz = FixedOffset::east_opt(offset_secs).ok_or("Invalid offset seconds")?;
661 Ok(tz.from_utc_datetime(&utc_naive))
662 }
663}
664
665impl<Tz: TimeZone> DateTime<Tz> {
666 pub fn signed_duration_since<Tz2: TimeZone>(&self, other: DateTime<Tz2>) -> Duration {
667 let self_utc = self.timestamp();
668 let other_utc = other.timestamp();
669 let diff_secs = self_utc - other_utc;
670 let diff_nanos = self.naive.time.nano as i64 - other.naive.time.nano as i64;
671 let (final_secs, final_nanos) = if diff_nanos < 0 {
672 (diff_secs - 1, diff_nanos + 1_000_000_000)
673 } else {
674 (diff_secs, diff_nanos)
675 };
676 Duration {
677 secs: final_secs,
678 nanos: final_nanos as i32,
679 }
680 }
681}
682
683impl<Tz: TimeZone> Add<Duration> for DateTime<Tz> {
687 type Output = DateTime<Tz>;
688 fn add(self, rhs: Duration) -> Self::Output {
689 let utc = self.naive_utc();
690 let new_secs = utc.timestamp() + rhs.secs;
691 let new_nanos = (utc.time.nano as i64 + rhs.nanos as i64) as u32;
692 let final_secs = new_secs + (new_nanos / 1_000_000_000) as i64;
693 let final_nanos = new_nanos % 1_000_000_000;
694 let new_utc = NaiveDateTime::from_timestamp_opt(final_secs, final_nanos).unwrap();
695 self.tz.from_utc_datetime(&new_utc)
696 }
697}
698
699impl<Tz: TimeZone> Sub<Duration> for DateTime<Tz> {
700 type Output = DateTime<Tz>;
701 fn sub(self, rhs: Duration) -> Self::Output {
702 let utc = self.naive_utc();
703 let new_secs = utc.timestamp() - rhs.secs;
704 let mut new_nanos = utc.time.nano as i64 - rhs.nanos as i64;
705 let final_secs = if new_nanos < 0 {
706 new_nanos += 1_000_000_000;
707 new_secs - 1
708 } else {
709 new_secs
710 };
711 let new_utc = NaiveDateTime::from_timestamp_opt(final_secs, new_nanos as u32).unwrap();
712 self.tz.from_utc_datetime(&new_utc)
713 }
714}
715
716impl Add<Duration> for NaiveDateTime {
717 type Output = NaiveDateTime;
718 fn add(self, rhs: Duration) -> Self::Output {
719 let new_secs = self.timestamp() + rhs.secs;
720 let new_nanos = (self.time.nano as i64 + rhs.nanos as i64) as u32;
721 let final_secs = new_secs + (new_nanos / 1_000_000_000) as i64;
722 let final_nanos = new_nanos % 1_000_000_000;
723 NaiveDateTime::from_timestamp_opt(final_secs, final_nanos).unwrap()
724 }
725}
726
727impl Sub<Duration> for NaiveDateTime {
728 type Output = NaiveDateTime;
729 fn sub(self, rhs: Duration) -> Self::Output {
730 let new_secs = self.timestamp() - rhs.secs;
731 let mut new_nanos = self.time.nano as i64 - rhs.nanos as i64;
732 let final_secs = if new_nanos < 0 {
733 new_nanos += 1_000_000_000;
734 new_secs - 1
735 } else {
736 new_secs
737 };
738 NaiveDateTime::from_timestamp_opt(final_secs, new_nanos as u32).unwrap()
739 }
740}
741
742impl Sub<NaiveDateTime> for NaiveDateTime {
743 type Output = Duration;
744 fn sub(self, rhs: NaiveDateTime) -> Self::Output {
745 self.signed_duration_since(rhs)
746 }
747}
748
749impl Serialize for NaiveDateTime {
753 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
754 where
755 S: serde::Serializer,
756 {
757 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S").to_string())
758 }
759}
760
761impl<'de> Deserialize<'de> for NaiveDateTime {
762 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
763 where
764 D: serde::Deserializer<'de>,
765 {
766 struct NaiveDateTimeVisitor;
767 impl<'de> serde::de::Visitor<'de> for NaiveDateTimeVisitor {
768 type Value = NaiveDateTime;
769 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
770 formatter.write_str("a datetime string in ISO 8601 / RFC 3339 format")
771 }
772 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
773 where
774 E: serde::de::Error,
775 {
776 parse_naive_datetime(value).map_err(|e| E::custom(e))
777 }
778 }
779 deserializer.deserialize_str(NaiveDateTimeVisitor)
780 }
781}
782
783impl Serialize for DateTime<FixedOffset> {
784 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
785 where
786 S: serde::Serializer,
787 {
788 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S%.3f%z").to_string())
789 }
790}
791
792impl<'de> Deserialize<'de> for DateTime<FixedOffset> {
793 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
794 where
795 D: serde::Deserializer<'de>,
796 {
797 struct FixedDateTimeVisitor;
798 impl<'de> serde::de::Visitor<'de> for FixedDateTimeVisitor {
799 type Value = DateTime<FixedOffset>;
800 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
801 formatter.write_str("a datetime string with timezone")
802 }
803 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
804 where
805 E: serde::de::Error,
806 {
807 let value = value.trim();
808 if value.len() < 19 {
809 return Err(E::custom("Datetime string too short"));
810 }
811 let tz_idx = value[19..]
812 .find(['Z', '+', '-'])
813 .map(|idx| idx + 19)
814 .ok_or_else(|| E::custom("Timezone offset missing"))?;
815 let naive_str = &value[..tz_idx];
816 let offset_str = &value[tz_idx..];
817 let naive = parse_naive_datetime(naive_str).map_err(|e| E::custom(e))?;
818 let offset_secs = parse_offset_secs(offset_str).ok_or_else(|| E::custom("Invalid offset seconds"))?;
819 let utc_secs = naive.timestamp() - offset_secs as i64;
820 let utc_naive = NaiveDateTime::from_timestamp_opt(utc_secs, naive.time.nano).unwrap();
821 let tz = FixedOffset::east_opt(offset_secs).ok_or_else(|| E::custom("Invalid offset seconds"))?;
822 Ok(tz.from_utc_datetime(&utc_naive))
823 }
824 }
825 deserializer.deserialize_str(FixedDateTimeVisitor)
826 }
827}
828
829impl Serialize for DateTime<Utc> {
830 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
831 where
832 S: serde::Serializer,
833 {
834 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S%.3f%z").to_string())
835 }
836}
837
838impl<'de> Deserialize<'de> for DateTime<Utc> {
839 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
840 where
841 D: serde::Deserializer<'de>,
842 {
843 let dt_fixed = DateTime::<FixedOffset>::deserialize(deserializer)?;
844 Ok(dt_fixed.with_timezone(&Utc))
845 }
846}
847
848impl Serialize for DateTime<Local> {
849 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
850 where
851 S: serde::Serializer,
852 {
853 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S%.3f%z").to_string())
854 }
855}
856
857impl<'de> Deserialize<'de> for DateTime<Local> {
858 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
859 where
860 D: serde::Deserializer<'de>,
861 {
862 let dt_fixed = DateTime::<FixedOffset>::deserialize(deserializer)?;
863 Ok(dt_fixed.with_timezone(&Local))
864 }
865}
866
867#[cfg(test)]
868mod tests {
869 use super::*;
870
871 #[test]
872 fn test_duration() {
873 let d1 = Duration::seconds(10);
874 let d2 = Duration::seconds(5);
875 assert!(d1 > d2);
876 assert_eq!(d1.num_seconds(), 10);
877 assert_eq!(d1.num_milliseconds(), 10000);
878 assert_eq!(Duration::zero().num_seconds(), 0);
879 }
880
881 #[test]
882 fn test_leap_year() {
883 assert!(is_leap_year(2000));
884 assert!(is_leap_year(2004));
885 assert!(!is_leap_year(1900));
886 assert!(!is_leap_year(2023));
887 assert!(is_leap_year(2024));
888 }
889
890 #[test]
891 fn test_epoch_days() {
892 for days in -10000..10000 {
894 let (y, m, d) = epoch_days_to_date(days);
895 let recon = date_to_epoch_days(y, m, d);
896 assert_eq!(recon, days, "Failed recon at days={}", days);
897 }
898 }
899
900 #[test]
901 fn test_naive_date_time_parsing() {
902 let s = "2026-06-11T20:07:53.123456789";
903 let dt = parse_naive_datetime(s).unwrap();
904 assert_eq!(dt.date.year(), 2026);
905 assert_eq!(dt.date.month(), 6);
906 assert_eq!(dt.date.day(), 11);
907 assert_eq!(dt.time.hour, 20);
908 assert_eq!(dt.time.min, 7);
909 assert_eq!(dt.time.sec, 53);
910 assert_eq!(dt.time.nano, 123456789);
911
912 let s2 = "2026-06-11T20:07:53";
913 let dt2 = parse_naive_datetime(s2).unwrap();
914 assert_eq!(dt2.time.nano, 0);
915 }
916
917 #[test]
918 fn test_datetime_serialization() {
919 let s = "2026-06-11T20:07:53.123+07:00";
920 let dt = DateTime::<FixedOffset>::parse_from_rfc3339(s).unwrap();
921 let serialized = serde_json::to_string(&dt).unwrap();
922 assert!(serialized.contains("2026-06-11T20:07:53.123+0700") || serialized.contains("2026-06-11T20:07:53.123+07:00"));
923
924 let deserialized: DateTime<FixedOffset> = serde_json::from_str(&serialized).unwrap();
925 assert_eq!(deserialized.timestamp(), dt.timestamp());
926 }
927
928 #[test]
929 fn test_datetime_arithmetic() {
930 let dt = Utc::now();
931 let added = dt + Duration::days(2);
932 let subtracted = added - Duration::days(2);
933 assert_eq!(dt.timestamp(), subtracted.timestamp());
934 }
935}