Skip to main content

rustbasic_core/
chrono.rs

1use serde::{Deserialize, Serialize};
2use std::ops::{Add, Sub};
3
4// ==========================================
5// 1. Duration Implementation
6// ==========================================
7#[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
59// ==========================================
60// 2. Leap Year & Calendar Arithmetic
61// ==========================================
62pub 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
75// Howard Hinnant's O(1) Epoch Days Calendar Algorithm
76pub 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// ==========================================
103// 3. NaiveDate, NaiveTime, NaiveDateTime
104// ==========================================
105#[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
226// Format string parsing helper
227pub 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
286// Parsing function helper
287pub 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// ==========================================
333// 4. Timezone traits & FixedOffset, Utc, Local
334// ==========================================
335#[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    // NOTE: `from_utc_datetime` takes `&self` intentionally to match the chrono crate's
370    // trait interface. Clippy's `wrong_self_convention` warning is suppressed here.
371    #[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// ==========================================
573// 5. DateTime Tz-Aware Struct
574// ==========================================
575#[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
683// ==========================================
684// 6. Arithmetic Operators
685// ==========================================
686impl<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
749// ==========================================
750// 7. Serde Serialization & Deserialization
751// ==========================================
752impl 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        // Test Howard Hinnant's algorithm consistency
893        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}