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 m >= 1 && m <= 12 && 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 fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Self> {
370 let offset = self.offset_from_utc_datetime(utc);
371 let fixed = offset.fix();
372 let local_secs = utc.timestamp() + fixed.local_minus_utc() as i64;
373 let naive = NaiveDateTime::from_timestamp_opt(local_secs, utc.time.nano).unwrap();
374 DateTime { naive, offset, tz: *self }
375 }
376}
377
378#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
379pub struct FixedOffset {
380 local_minus_utc: i32,
381}
382
383impl FixedOffset {
384 pub fn east_opt(secs: i32) -> Option<Self> {
385 if secs.abs() <= 86400 {
386 Some(Self { local_minus_utc: secs })
387 } else {
388 None
389 }
390 }
391 pub fn west_opt(secs: i32) -> Option<Self> {
392 if secs.abs() <= 86400 {
393 Some(Self { local_minus_utc: -secs })
394 } else {
395 None
396 }
397 }
398 pub fn local_minus_utc(&self) -> i32 {
399 self.local_minus_utc
400 }
401}
402
403impl Offset for FixedOffset {
404 fn fix(&self) -> FixedOffset {
405 *self
406 }
407}
408
409impl std::fmt::Display for FixedOffset {
410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411 let sign = if self.local_minus_utc >= 0 { '+' } else { '-' };
412 let abs_offset = self.local_minus_utc.abs();
413 let hours = abs_offset / 3600;
414 let mins = (abs_offset % 3600) / 60;
415 write!(f, "{}{:02}:{:02}", sign, hours, mins)
416 }
417}
418
419impl TimeZone for FixedOffset {
420 type Offset = FixedOffset;
421
422 fn from_offset(offset: &Self::Offset) -> Self {
423 *offset
424 }
425
426 fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<Self::Offset> {
427 LocalResult::Single(*self)
428 }
429
430 fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<Self::Offset> {
431 LocalResult::Single(*self)
432 }
433
434 fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Self::Offset {
435 *self
436 }
437
438 fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> Self::Offset {
439 *self
440 }
441}
442
443#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
444pub struct Utc;
445
446impl TimeZone for Utc {
447 type Offset = FixedOffset;
448
449 fn from_offset(_offset: &Self::Offset) -> Self {
450 Utc
451 }
452
453 fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<Self::Offset> {
454 LocalResult::Single(FixedOffset::east_opt(0).unwrap())
455 }
456
457 fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<Self::Offset> {
458 LocalResult::Single(FixedOffset::east_opt(0).unwrap())
459 }
460
461 fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Self::Offset {
462 FixedOffset::east_opt(0).unwrap()
463 }
464
465 fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> Self::Offset {
466 FixedOffset::east_opt(0).unwrap()
467 }
468}
469
470impl Utc {
471 pub fn now() -> DateTime<Utc> {
472 let now_system = std::time::SystemTime::now();
473 let duration = now_system.duration_since(std::time::UNIX_EPOCH).unwrap();
474 let naive = NaiveDateTime::from_timestamp_opt(duration.as_secs() as i64, duration.subsec_nanos()).unwrap();
475 DateTime {
476 naive,
477 offset: FixedOffset::east_opt(0).unwrap(),
478 tz: Utc,
479 }
480 }
481}
482
483#[repr(C)]
484struct tm {
485 tm_sec: i32,
486 tm_min: i32,
487 tm_hour: i32,
488 tm_mday: i32,
489 tm_mon: i32,
490 tm_year: i32,
491 tm_wday: i32,
492 tm_yday: i32,
493 tm_isdst: i32,
494 tm_gmtoff: i64,
495 tm_zone: *const std::os::raw::c_char,
496}
497
498unsafe extern "C" {
499 fn localtime_r(timep: *const i64, result: *mut tm) -> *mut tm;
500}
501
502fn get_local_offset_secs(secs: i64) -> i32 {
503 unsafe {
504 let mut t = tm {
505 tm_sec: 0,
506 tm_min: 0,
507 tm_hour: 0,
508 tm_mday: 0,
509 tm_mon: 0,
510 tm_year: 0,
511 tm_wday: 0,
512 tm_yday: 0,
513 tm_isdst: 0,
514 tm_gmtoff: 0,
515 tm_zone: std::ptr::null(),
516 };
517 localtime_r(&secs, &mut t);
518 t.tm_gmtoff as i32
519 }
520}
521
522#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
523pub struct Local;
524
525impl TimeZone for Local {
526 type Offset = FixedOffset;
527
528 fn from_offset(_offset: &Self::Offset) -> Self {
529 Local
530 }
531
532 fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<Self::Offset> {
533 let naive = NaiveDateTime::new(*local, NaiveTime { hour: 0, min: 0, sec: 0, nano: 0 });
534 self.offset_from_local_datetime(&naive)
535 }
536
537 fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset> {
538 let utc_approx = local.timestamp();
539 let offset = get_local_offset_secs(utc_approx);
540 LocalResult::Single(FixedOffset::east_opt(offset).unwrap())
541 }
542
543 fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset {
544 let naive = NaiveDateTime::new(*utc, NaiveTime { hour: 0, min: 0, sec: 0, nano: 0 });
545 self.offset_from_utc_datetime(&naive)
546 }
547
548 fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset {
549 let offset = get_local_offset_secs(utc.timestamp());
550 FixedOffset::east_opt(offset).unwrap()
551 }
552}
553
554impl Local {
555 pub fn now() -> DateTime<Local> {
556 let now_system = std::time::SystemTime::now();
557 let duration = now_system.duration_since(std::time::UNIX_EPOCH).unwrap();
558 let secs = duration.as_secs() as i64;
559 let offset_secs = get_local_offset_secs(secs);
560 let naive = NaiveDateTime::from_timestamp_opt(secs + offset_secs as i64, duration.subsec_nanos()).unwrap();
561 DateTime {
562 naive,
563 offset: FixedOffset::east_opt(offset_secs).unwrap(),
564 tz: Local,
565 }
566 }
567}
568
569#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
573pub struct DateTime<Tz: TimeZone> {
574 pub naive: NaiveDateTime,
575 pub offset: Tz::Offset,
576 pub tz: Tz,
577}
578
579impl<Tz: TimeZone> DateTime<Tz> {
580 pub fn naive_utc(&self) -> NaiveDateTime {
581 let offset = self.offset.fix();
582 let utc_secs = self.naive.timestamp() - offset.local_minus_utc() as i64;
583 NaiveDateTime::from_timestamp_opt(utc_secs, self.naive.time.nano).unwrap()
584 }
585
586 pub fn timestamp(&self) -> i64 {
587 let offset = self.offset.fix();
588 self.naive.timestamp() - offset.local_minus_utc() as i64
589 }
590
591 pub fn naive_local(&self) -> NaiveDateTime {
592 self.naive
593 }
594
595 pub fn with_timezone<Tz2: TimeZone>(&self, tz2: &Tz2) -> DateTime<Tz2> {
596 let utc_dt = self.naive_utc();
597 tz2.from_utc_datetime(&utc_dt)
598 }
599
600 pub fn format<'a>(&'a self, fmt_str: &'a str) -> Format<'a> {
601 Format {
602 dt: &self.naive,
603 offset_secs: self.offset.fix().local_minus_utc(),
604 fmt_str,
605 }
606 }
607}
608
609pub fn parse_offset_secs(offset_str: &str) -> Option<i32> {
610 let offset_str = offset_str.trim();
611 if offset_str == "Z" || offset_str.eq_ignore_ascii_case("utc") || offset_str.is_empty() {
612 return Some(0);
613 }
614 let sign = match offset_str.chars().next()? {
615 '+' => 1,
616 '-' => -1,
617 _ => return None,
618 };
619 let rest = &offset_str[1..];
620 let parts: Vec<&str> = rest.split(':').collect();
621 if parts.len() == 2 {
622 let hours: i32 = parts[0].parse().ok()?;
623 let minutes: i32 = parts[1].parse().ok()?;
624 Some(sign * (hours * 3600 + minutes * 60))
625 } else if parts.len() == 1 {
626 if rest.len() == 4 {
627 let hours: i32 = rest[0..2].parse().ok()?;
628 let minutes: i32 = rest[2..4].parse().ok()?;
629 Some(sign * (hours * 3600 + minutes * 60))
630 } else if rest.len() == 2 || rest.len() == 1 {
631 let hours: i32 = rest.parse().ok()?;
632 Some(sign * hours * 3600)
633 } else {
634 None
635 }
636 } else {
637 None
638 }
639}
640
641impl DateTime<FixedOffset> {
642 pub fn parse_from_rfc3339(s: &str) -> Result<Self, String> {
643 let s = s.trim();
644 if s.len() < 19 {
645 return Err("Datetime string too short".to_string());
646 }
647 let tz_idx = s[19..]
648 .find(|c| c == 'Z' || c == '+' || c == '-')
649 .map(|idx| idx + 19)
650 .ok_or_else(|| "Timezone offset missing".to_string())?;
651 let naive_str = &s[..tz_idx];
652 let offset_str = &s[tz_idx..];
653 let naive = parse_naive_datetime(naive_str)?;
654 let offset_secs = parse_offset_secs(offset_str).ok_or("Invalid offset offset format")?;
655 let utc_secs = naive.timestamp() - offset_secs as i64;
656 let utc_naive = NaiveDateTime::from_timestamp_opt(utc_secs, naive.time.nano).ok_or("Invalid timestamp")?;
657 let tz = FixedOffset::east_opt(offset_secs).ok_or("Invalid offset seconds")?;
658 Ok(tz.from_utc_datetime(&utc_naive))
659 }
660}
661
662impl<Tz: TimeZone> DateTime<Tz> {
663 pub fn signed_duration_since<Tz2: TimeZone>(&self, other: DateTime<Tz2>) -> Duration {
664 let self_utc = self.timestamp();
665 let other_utc = other.timestamp();
666 let diff_secs = self_utc - other_utc;
667 let diff_nanos = self.naive.time.nano as i64 - other.naive.time.nano as i64;
668 let (final_secs, final_nanos) = if diff_nanos < 0 {
669 (diff_secs - 1, diff_nanos + 1_000_000_000)
670 } else {
671 (diff_secs, diff_nanos)
672 };
673 Duration {
674 secs: final_secs,
675 nanos: final_nanos as i32,
676 }
677 }
678}
679
680impl<Tz: TimeZone> Add<Duration> for DateTime<Tz> {
684 type Output = DateTime<Tz>;
685 fn add(self, rhs: Duration) -> Self::Output {
686 let utc = self.naive_utc();
687 let new_secs = utc.timestamp() + rhs.secs;
688 let new_nanos = (utc.time.nano as i64 + rhs.nanos as i64) as u32;
689 let final_secs = new_secs + (new_nanos / 1_000_000_000) as i64;
690 let final_nanos = new_nanos % 1_000_000_000;
691 let new_utc = NaiveDateTime::from_timestamp_opt(final_secs, final_nanos).unwrap();
692 self.tz.from_utc_datetime(&new_utc)
693 }
694}
695
696impl<Tz: TimeZone> Sub<Duration> for DateTime<Tz> {
697 type Output = DateTime<Tz>;
698 fn sub(self, rhs: Duration) -> Self::Output {
699 let utc = self.naive_utc();
700 let new_secs = utc.timestamp() - rhs.secs;
701 let mut new_nanos = utc.time.nano as i64 - rhs.nanos as i64;
702 let final_secs = if new_nanos < 0 {
703 new_nanos += 1_000_000_000;
704 new_secs - 1
705 } else {
706 new_secs
707 };
708 let new_utc = NaiveDateTime::from_timestamp_opt(final_secs, new_nanos as u32).unwrap();
709 self.tz.from_utc_datetime(&new_utc)
710 }
711}
712
713impl Add<Duration> for NaiveDateTime {
714 type Output = NaiveDateTime;
715 fn add(self, rhs: Duration) -> Self::Output {
716 let new_secs = self.timestamp() + rhs.secs;
717 let new_nanos = (self.time.nano as i64 + rhs.nanos as i64) as u32;
718 let final_secs = new_secs + (new_nanos / 1_000_000_000) as i64;
719 let final_nanos = new_nanos % 1_000_000_000;
720 NaiveDateTime::from_timestamp_opt(final_secs, final_nanos).unwrap()
721 }
722}
723
724impl Sub<Duration> for NaiveDateTime {
725 type Output = NaiveDateTime;
726 fn sub(self, rhs: Duration) -> Self::Output {
727 let new_secs = self.timestamp() - rhs.secs;
728 let mut new_nanos = self.time.nano as i64 - rhs.nanos as i64;
729 let final_secs = if new_nanos < 0 {
730 new_nanos += 1_000_000_000;
731 new_secs - 1
732 } else {
733 new_secs
734 };
735 NaiveDateTime::from_timestamp_opt(final_secs, new_nanos as u32).unwrap()
736 }
737}
738
739impl Sub<NaiveDateTime> for NaiveDateTime {
740 type Output = Duration;
741 fn sub(self, rhs: NaiveDateTime) -> Self::Output {
742 self.signed_duration_since(rhs)
743 }
744}
745
746impl Serialize for NaiveDateTime {
750 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
751 where
752 S: serde::Serializer,
753 {
754 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S").to_string())
755 }
756}
757
758impl<'de> Deserialize<'de> for NaiveDateTime {
759 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
760 where
761 D: serde::Deserializer<'de>,
762 {
763 struct NaiveDateTimeVisitor;
764 impl<'de> serde::de::Visitor<'de> for NaiveDateTimeVisitor {
765 type Value = NaiveDateTime;
766 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
767 formatter.write_str("a datetime string in ISO 8601 / RFC 3339 format")
768 }
769 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
770 where
771 E: serde::de::Error,
772 {
773 parse_naive_datetime(value).map_err(|e| E::custom(e))
774 }
775 }
776 deserializer.deserialize_str(NaiveDateTimeVisitor)
777 }
778}
779
780impl Serialize for DateTime<FixedOffset> {
781 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
782 where
783 S: serde::Serializer,
784 {
785 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S%.3f%z").to_string())
786 }
787}
788
789impl<'de> Deserialize<'de> for DateTime<FixedOffset> {
790 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
791 where
792 D: serde::Deserializer<'de>,
793 {
794 struct FixedDateTimeVisitor;
795 impl<'de> serde::de::Visitor<'de> for FixedDateTimeVisitor {
796 type Value = DateTime<FixedOffset>;
797 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
798 formatter.write_str("a datetime string with timezone")
799 }
800 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
801 where
802 E: serde::de::Error,
803 {
804 let value = value.trim();
805 if value.len() < 19 {
806 return Err(E::custom("Datetime string too short"));
807 }
808 let tz_idx = value[19..]
809 .find(|c| c == 'Z' || c == '+' || c == '-')
810 .map(|idx| idx + 19)
811 .ok_or_else(|| E::custom("Timezone offset missing"))?;
812 let naive_str = &value[..tz_idx];
813 let offset_str = &value[tz_idx..];
814 let naive = parse_naive_datetime(naive_str).map_err(|e| E::custom(e))?;
815 let offset_secs = parse_offset_secs(offset_str).ok_or_else(|| E::custom("Invalid offset seconds"))?;
816 let utc_secs = naive.timestamp() - offset_secs as i64;
817 let utc_naive = NaiveDateTime::from_timestamp_opt(utc_secs, naive.time.nano).unwrap();
818 let tz = FixedOffset::east_opt(offset_secs).ok_or_else(|| E::custom("Invalid offset seconds"))?;
819 Ok(tz.from_utc_datetime(&utc_naive))
820 }
821 }
822 deserializer.deserialize_str(FixedDateTimeVisitor)
823 }
824}
825
826impl Serialize for DateTime<Utc> {
827 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
828 where
829 S: serde::Serializer,
830 {
831 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S%.3f%z").to_string())
832 }
833}
834
835impl<'de> Deserialize<'de> for DateTime<Utc> {
836 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
837 where
838 D: serde::Deserializer<'de>,
839 {
840 let dt_fixed = DateTime::<FixedOffset>::deserialize(deserializer)?;
841 Ok(dt_fixed.with_timezone(&Utc))
842 }
843}
844
845impl Serialize for DateTime<Local> {
846 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
847 where
848 S: serde::Serializer,
849 {
850 serializer.serialize_str(&self.format("%Y-%m-%dT%H:%M:%S%.3f%z").to_string())
851 }
852}
853
854impl<'de> Deserialize<'de> for DateTime<Local> {
855 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
856 where
857 D: serde::Deserializer<'de>,
858 {
859 let dt_fixed = DateTime::<FixedOffset>::deserialize(deserializer)?;
860 Ok(dt_fixed.with_timezone(&Local))
861 }
862}
863
864#[cfg(test)]
865mod tests {
866 use super::*;
867
868 #[test]
869 fn test_duration() {
870 let d1 = Duration::seconds(10);
871 let d2 = Duration::seconds(5);
872 assert!(d1 > d2);
873 assert_eq!(d1.num_seconds(), 10);
874 assert_eq!(d1.num_milliseconds(), 10000);
875 assert_eq!(Duration::zero().num_seconds(), 0);
876 }
877
878 #[test]
879 fn test_leap_year() {
880 assert!(is_leap_year(2000));
881 assert!(is_leap_year(2004));
882 assert!(!is_leap_year(1900));
883 assert!(!is_leap_year(2023));
884 assert!(is_leap_year(2024));
885 }
886
887 #[test]
888 fn test_epoch_days() {
889 for days in -10000..10000 {
891 let (y, m, d) = epoch_days_to_date(days);
892 let recon = date_to_epoch_days(y, m, d);
893 assert_eq!(recon, days, "Failed recon at days={}", days);
894 }
895 }
896
897 #[test]
898 fn test_naive_date_time_parsing() {
899 let s = "2026-06-11T20:07:53.123456789";
900 let dt = parse_naive_datetime(s).unwrap();
901 assert_eq!(dt.date.year(), 2026);
902 assert_eq!(dt.date.month(), 6);
903 assert_eq!(dt.date.day(), 11);
904 assert_eq!(dt.time.hour, 20);
905 assert_eq!(dt.time.min, 7);
906 assert_eq!(dt.time.sec, 53);
907 assert_eq!(dt.time.nano, 123456789);
908
909 let s2 = "2026-06-11T20:07:53";
910 let dt2 = parse_naive_datetime(s2).unwrap();
911 assert_eq!(dt2.time.nano, 0);
912 }
913
914 #[test]
915 fn test_datetime_serialization() {
916 let s = "2026-06-11T20:07:53.123+07:00";
917 let dt = DateTime::<FixedOffset>::parse_from_rfc3339(s).unwrap();
918 let serialized = serde_json::to_string(&dt).unwrap();
919 assert!(serialized.contains("2026-06-11T20:07:53.123+0700") || serialized.contains("2026-06-11T20:07:53.123+07:00"));
920
921 let deserialized: DateTime<FixedOffset> = serde_json::from_str(&serialized).unwrap();
922 assert_eq!(deserialized.timestamp(), dt.timestamp());
923 }
924
925 #[test]
926 fn test_datetime_arithmetic() {
927 let dt = Utc::now();
928 let added = dt + Duration::days(2);
929 let subtracted = added - Duration::days(2);
930 assert_eq!(dt.timestamp(), subtracted.timestamp());
931 }
932}