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 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
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    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// ==========================================
570// 5. DateTime Tz-Aware Struct
571// ==========================================
572#[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
680// ==========================================
681// 6. Arithmetic Operators
682// ==========================================
683impl<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
746// ==========================================
747// 7. Serde Serialization & Deserialization
748// ==========================================
749impl 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        // Test Howard Hinnant's algorithm consistency
890        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}