1#![cfg_attr(not(feature = "std"), no_std)]
2
3use core::cmp::Ordering;
25use core::fmt;
26use core::str::FromStr;
27
28#[cfg(feature = "python")]
29mod python;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum Weekday {
34 Monday,
35 Tuesday,
36 Wednesday,
37 Thursday,
38 Friday,
39 Saturday,
40 Sunday,
41}
42
43impl Weekday {
44 pub fn number_from_monday(self) -> u8 {
45 match self {
46 Weekday::Monday => 1,
47 Weekday::Tuesday => 2,
48 Weekday::Wednesday => 3,
49 Weekday::Thursday => 4,
50 Weekday::Friday => 5,
51 Weekday::Saturday => 6,
52 Weekday::Sunday => 7,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum DateError {
60 InvalidDate,
62 OutOfRange,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub struct Date {
71 pub year: i32,
72 pub month: u8, pub day: u8, }
75
76impl Date {
77 pub fn from_ymd(year: i32, month: u8, day: u8) -> Result<Self, DateError> {
79 if !(1..=12).contains(&month) {
80 return Err(DateError::InvalidDate);
81 }
82 let dim = days_in_month(year, month);
83 if day == 0 || day > dim {
84 return Err(DateError::InvalidDate);
85 }
86 Ok(Date { year, month, day })
87 }
88
89 pub const fn from_ymd_unchecked(year: i32, month: u8, day: u8) -> Self {
93 debug_assert!(month >= 1 && month <= 12);
95 debug_assert!(day >= 1 && day <= 31);
96 Date { year, month, day }
97 }
98
99 pub fn from_days_since_unix_epoch(days: i64) -> Result<Self, DateError> {
106 const ERAS: i64 = 4_726_498_270;
108 const D_SHIFT: i64 = 146_097 * ERAS - 719_469;
109 const Y_SHIFT: i64 = 400 * ERAS - 1;
110 const C1: u64 = 505_054_698_555_331;
111 const C2: u64 = 50_504_432_782_230_121;
112 const C3: u64 = 8_619_973_866_219_416;
113
114 let rev: i64 = D_SHIFT - days;
115
116 let cen: i64 = (((rev as u64 as u128) * (C1 as u128)) >> 64) as i64;
118 let jul: i64 = rev + cen - cen / 4;
119
120 let num: u128 = (jul as u64 as u128) * (C2 as u128);
121 let yrs: i64 = Y_SHIFT - ((num >> 64) as i64);
122 let low: u64 = num as u64;
123 let ypt: i64 = ((782_432u128 * low as u128) >> 64) as i64;
124
125 let bump = ypt < 126_464;
126 let shift: i64 = if bump { 191_360 } else { 977_792 };
127
128 let n: i64 = (yrs.rem_euclid(4)) * 512 + shift - ypt;
129
130 let d: i64 = (((((n as u64) & 0xFFFF) as u128) * (C3 as u128)) >> 64) as i64;
131
132 let day_i: i64 = d + 1;
133 let month_i: i64 = n / 65_536;
134 let year_i: i64 = yrs + if bump { 1 } else { 0 };
135
136 if !(i32::MIN as i64..=i32::MAX as i64).contains(&year_i) {
137 return Err(DateError::OutOfRange);
138 }
139 let year = year_i as i32;
140 let month = month_i as u8;
141 let day = day_i as u8;
142
143 if Date::from_ymd(year, month, day).is_err() {
145 return Err(DateError::InvalidDate);
146 }
147
148 Ok(Date { year, month, day })
149 }
150
151 pub fn days_since_unix_epoch(self) -> i64 {
156 days_from_civil(self.year, self.month, self.day)
157 }
158
159 pub fn weekday(self) -> Weekday {
163 let days = self.days_since_unix_epoch();
165 let w = days.rem_euclid(7);
166 match w {
167 0 => Weekday::Thursday,
168 1 => Weekday::Friday,
169 2 => Weekday::Saturday,
170 3 => Weekday::Sunday,
171 4 => Weekday::Monday,
172 5 => Weekday::Tuesday,
173 6 => Weekday::Wednesday,
174 _ => unreachable!(),
175 }
176 }
177
178 pub fn ordinal(self) -> u16 {
180 let month = self.month;
181 let day = self.day as u16;
182 const CUM_DAYS: [u16; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
183 let mut ord = CUM_DAYS[(month - 1) as usize] + day;
184 if month > 2 && is_leap_year(self.year) {
185 ord += 1;
186 }
187 ord
188 }
189
190 pub fn add_days(self, days: i64) -> Result<Date, DateError> {
192 let base = self.days_since_unix_epoch();
193 Date::from_days_since_unix_epoch(base + days)
194 }
195}
196
197impl PartialOrd for Date {
198 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
199 Some(self.cmp(other))
200 }
201}
202
203impl Ord for Date {
204 fn cmp(&self, other: &Self) -> Ordering {
205 let days_self = self.days_since_unix_epoch();
206 let days_other = other.days_since_unix_epoch();
207 days_self.cmp(&days_other)
208 }
209}
210
211impl fmt::Display for Date {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
215 }
216}
217
218impl FromStr for Date {
219 type Err = DateError;
220
221 fn from_str(s: &str) -> Result<Self, Self::Err> {
223 let bytes = s.as_bytes();
224 if bytes.is_empty() {
225 return Err(DateError::InvalidDate);
226 }
227
228 let mut start = 0;
229 if bytes[0] == b'+' || bytes[0] == b'-' {
230 start = 1;
231 if start == bytes.len() {
232 return Err(DateError::InvalidDate);
233 }
234 }
235
236 let mut first = None;
237 let mut second = None;
238 for (i, &b) in bytes.iter().enumerate().skip(start) {
239 if b == b'-' {
240 if first.is_none() {
241 first = Some(i);
242 } else if second.is_none() {
243 second = Some(i);
244 } else {
245 return Err(DateError::InvalidDate);
246 }
247 }
248 }
249
250 let (first, second) = match (first, second) {
251 (Some(first), Some(second)) => (first, second),
252 _ => return Err(DateError::InvalidDate),
253 };
254
255 let y = parse_i32_bytes(&bytes[..first]).ok_or(DateError::InvalidDate)?;
256 let m = parse_u32_bytes(&bytes[first + 1..second], 12).ok_or(DateError::InvalidDate)? as u8;
257 let d = parse_u32_bytes(&bytes[second + 1..], 31).ok_or(DateError::InvalidDate)? as u8;
258 Date::from_ymd(y, m, d)
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub enum TimeError {
265 InvalidTime,
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
270pub struct Time {
271 pub hour: u8, pub minute: u8, pub second: u8, pub nanosecond: u32, }
276
277impl Time {
278 pub fn from_hms_nano(
279 hour: u8,
280 minute: u8,
281 second: u8,
282 nanosecond: u32,
283 ) -> Result<Self, TimeError> {
284 if hour > 23 || minute > 59 || second > 59 || nanosecond >= 1_000_000_000 {
285 return Err(TimeError::InvalidTime);
286 }
287 Ok(Time {
288 hour,
289 minute,
290 second,
291 nanosecond,
292 })
293 }
294
295 pub fn seconds_since_midnight(self) -> u32 {
297 (self.hour as u32) * 3600 + (self.minute as u32) * 60 + (self.second as u32)
298 }
299
300 pub fn nanos_since_midnight(self) -> u64 {
302 self.seconds_since_midnight() as u64 * 1_000_000_000 + self.nanosecond as u64
303 }
304
305 pub fn from_seconds_nanos(secs: u32, nanos: u32) -> Result<Self, TimeError> {
307 if secs >= 86_400 || nanos >= 1_000_000_000 {
308 return Err(TimeError::InvalidTime);
309 }
310 let hour = (secs / 3600) as u8;
311 let rem = secs % 3600;
312 let minute = (rem / 60) as u8;
313 let second = (rem % 60) as u8;
314 Time::from_hms_nano(hour, minute, second, nanos)
315 }
316}
317
318impl PartialOrd for Time {
319 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
320 Some(self.cmp(other))
321 }
322}
323
324impl Ord for Time {
325 fn cmp(&self, other: &Self) -> Ordering {
326 let nanos_self = self.nanos_since_midnight();
327 let nanos_other = other.nanos_since_midnight();
328 nanos_self.cmp(&nanos_other)
329 }
330}
331
332impl fmt::Display for Time {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 if self.nanosecond == 0 {
335 write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
336 } else {
337 let mut frac = [b'0'; 9];
339 let mut ns = self.nanosecond;
340 for i in (0..9).rev() {
341 frac[i] = b'0' + (ns % 10) as u8;
342 ns /= 10;
343 }
344 let mut end = 9;
346 while end > 0 && frac[end - 1] == b'0' {
347 end -= 1;
348 }
349 let frac_str = core::str::from_utf8(&frac[..end]).unwrap_or("0");
350 write!(
351 f,
352 "{:02}:{:02}:{:02}.{}",
353 self.hour, self.minute, self.second, frac_str
354 )
355 }
356 }
357}
358
359impl FromStr for Time {
360 type Err = TimeError;
361
362 fn from_str(s: &str) -> Result<Self, Self::Err> {
364 let bytes = s.as_bytes();
365 let (hms_bytes, frac_bytes) = match bytes.iter().position(|&b| b == b'.') {
366 Some(idx) => (&bytes[..idx], Some(&bytes[idx + 1..])),
367 None => (bytes, None),
368 };
369
370 let mut first = None;
371 let mut second = None;
372 for (i, &b) in hms_bytes.iter().enumerate() {
373 if b == b':' {
374 if first.is_none() {
375 first = Some(i);
376 } else if second.is_none() {
377 second = Some(i);
378 } else {
379 return Err(TimeError::InvalidTime);
380 }
381 }
382 }
383
384 let (first, second) = match (first, second) {
385 (Some(first), Some(second)) => (first, second),
386 _ => return Err(TimeError::InvalidTime),
387 };
388
389 let h = parse_u32_bytes(&hms_bytes[..first], 23).ok_or(TimeError::InvalidTime)? as u8;
390 let m =
391 parse_u32_bytes(&hms_bytes[first + 1..second], 59).ok_or(TimeError::InvalidTime)? as u8;
392 let sec =
393 parse_u32_bytes(&hms_bytes[second + 1..], 59).ok_or(TimeError::InvalidTime)? as u8;
394
395 let nanos = if let Some(fr) = frac_bytes {
396 parse_fraction_nanos(fr).ok_or(TimeError::InvalidTime)?
397 } else {
398 0
399 };
400
401 Time::from_hms_nano(h, m, sec, nanos)
402 }
403}
404
405#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
407pub struct Duration {
408 nanos: i128,
409}
410
411impl Duration {
412 pub const ZERO: Duration = Duration { nanos: 0 };
413
414 pub fn seconds(secs: i64) -> Duration {
415 Duration {
416 nanos: (secs as i128) * 1_000_000_000,
417 }
418 }
419
420 pub fn milliseconds(ms: i64) -> Duration {
421 Duration {
422 nanos: (ms as i128) * 1_000_000,
423 }
424 }
425
426 pub fn microseconds(us: i64) -> Duration {
427 Duration {
428 nanos: (us as i128) * 1_000,
429 }
430 }
431
432 pub fn nanoseconds(ns: i128) -> Duration {
433 Duration { nanos: ns }
434 }
435
436 pub fn total_seconds(self) -> f64 {
437 self.nanos as f64 / 1_000_000_000.0
438 }
439
440 pub fn total_nanos(self) -> i128 {
441 self.nanos
442 }
443}
444
445impl core::ops::Add for Duration {
446 type Output = Duration;
447 fn add(self, rhs: Duration) -> Duration {
448 Duration {
449 nanos: self.nanos + rhs.nanos,
450 }
451 }
452}
453
454impl core::ops::Sub for Duration {
455 type Output = Duration;
456 fn sub(self, rhs: Duration) -> Duration {
457 Duration {
458 nanos: self.nanos - rhs.nanos,
459 }
460 }
461}
462
463impl core::ops::Neg for Duration {
464 type Output = Duration;
465 fn neg(self) -> Duration {
466 Duration { nanos: -self.nanos }
467 }
468}
469
470impl PartialOrd for Duration {
471 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
472 Some(self.cmp(other))
473 }
474}
475
476impl Ord for Duration {
477 fn cmp(&self, other: &Self) -> Ordering {
478 self.nanos.cmp(&other.nanos)
479 }
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
484pub struct DateTime {
485 pub date: Date,
486 pub time: Time,
487}
488
489impl DateTime {
490 pub fn new(date: Date, time: Time) -> DateTime {
491 DateTime { date, time }
492 }
493
494 pub fn from_unix_timestamp(secs: i64, nanos: i32) -> Result<DateTime, DateError> {
497 let mut s = secs as i128;
499 let mut n = nanos as i128;
500 s += n.div_euclid(1_000_000_000);
501 n = n.rem_euclid(1_000_000_000);
502 let s_i64 = s as i64;
503
504 let days = s_i64.div_euclid(86_400);
505 let secs_of_day = s_i64.rem_euclid(86_400);
506 let date = Date::from_days_since_unix_epoch(days)?;
507 let time = Time::from_seconds_nanos(secs_of_day as u32, n as u32)
508 .map_err(|_| DateError::InvalidDate)?;
509 Ok(DateTime { date, time })
510 }
511
512 pub fn unix_timestamp(self) -> i64 {
514 let days = self.date.days_since_unix_epoch();
515 let day_secs = self.time.seconds_since_midnight() as i64;
516 days * 86_400 + day_secs
517 }
518
519 pub fn unix_timestamp_nanos(self) -> i128 {
521 self.unix_timestamp() as i128 * 1_000_000_000 + self.time.nanosecond as i128
522 }
523
524 pub fn add_duration(self, dur: Duration) -> Result<DateTime, DateError> {
526 let t = self.unix_timestamp_nanos() + dur.total_nanos();
527 let secs = t.div_euclid(1_000_000_000);
528 let nanos = t.rem_euclid(1_000_000_000);
529 DateTime::from_unix_timestamp(secs as i64, nanos as i32)
530 }
531
532 pub fn difference(self, other: DateTime) -> Duration {
534 Duration::nanoseconds(self.unix_timestamp_nanos() - other.unix_timestamp_nanos())
535 }
536
537 #[cfg(feature = "std")]
539 pub fn now_utc() -> Result<Self, DateError> {
540 use std::time::{SystemTime, UNIX_EPOCH};
541 let now = SystemTime::now();
542 match now.duration_since(UNIX_EPOCH) {
543 Ok(dur) => {
544 DateTime::from_unix_timestamp(dur.as_secs() as i64, dur.subsec_nanos() as i32)
545 }
546 Err(e) => {
547 let dur = e.duration();
548 let secs = dur.as_secs() as i64;
549 let nanos = dur.subsec_nanos() as i32;
550 DateTime::from_unix_timestamp(-secs, -nanos)
551 }
552 }
553 }
554}
555
556impl fmt::Display for DateTime {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 write!(f, "{}T{}Z", self.date, self.time)
560 }
561}
562
563impl FromStr for DateTime {
564 type Err = ();
565
566 fn from_str(s: &str) -> Result<Self, Self::Err> {
568 let s = s
569 .strip_suffix('Z')
570 .or_else(|| s.strip_suffix('z'))
571 .ok_or(())?;
572 let (date_str, time_str) = s.split_once('T').or_else(|| s.split_once(' ')).ok_or(())?;
573 let date = date_str.parse::<Date>().map_err(|_| ())?;
574 let time = time_str.parse::<Time>().map_err(|_| ())?;
575 Ok(DateTime { date, time })
576 }
577}
578
579impl PartialOrd for DateTime {
580 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
581 Some(self.cmp(other))
582 }
583}
584
585impl Ord for DateTime {
586 fn cmp(&self, other: &Self) -> Ordering {
587 self.unix_timestamp_nanos()
588 .cmp(&other.unix_timestamp_nanos())
589 }
590}
591
592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
594pub enum UtcOffsetError {
595 OutOfRange,
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
600pub struct UtcOffset {
601 seconds: i32,
602}
603
604impl UtcOffset {
605 pub fn from_seconds(seconds: i32) -> Result<Self, UtcOffsetError> {
607 if !(-86_400..=86_400).contains(&seconds) {
609 return Err(UtcOffsetError::OutOfRange);
610 }
611 Ok(UtcOffset { seconds })
612 }
613
614 pub fn from_hours_minutes(
620 sign_positive: bool,
621 hours: u8,
622 minutes: u8,
623 ) -> Result<Self, UtcOffsetError> {
624 if hours > 23 || minutes > 59 {
625 return Err(UtcOffsetError::OutOfRange);
626 }
627 let total = (hours as i32) * 3600 + (minutes as i32) * 60;
628 let total = if sign_positive { total } else { -total };
629 Self::from_seconds(total)
630 }
631
632 pub fn as_seconds(self) -> i32 {
633 self.seconds
634 }
635
636 pub fn is_utc(self) -> bool {
637 self.seconds == 0
638 }
639}
640
641impl PartialOrd for UtcOffset {
642 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
643 Some(self.cmp(other))
644 }
645}
646
647impl Ord for UtcOffset {
648 fn cmp(&self, other: &Self) -> Ordering {
649 self.seconds.cmp(&other.seconds)
650 }
651}
652
653impl fmt::Display for UtcOffset {
654 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655 let mut secs = self.seconds;
656 let sign = if secs >= 0 { '+' } else { '-' };
657 if secs < 0 {
658 secs = -secs;
659 }
660 let hours = secs / 3600;
661 let minutes = (secs % 3600) / 60;
662 write!(f, "{}{:02}:{:02}", sign, hours, minutes)
663 }
664}
665
666#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
668pub struct OffsetDateTime {
669 pub utc: DateTime,
671 pub offset: UtcOffset,
673}
674
675impl OffsetDateTime {
676 pub fn from_utc(utc: DateTime, offset: UtcOffset) -> Self {
678 OffsetDateTime { utc, offset }
679 }
680
681 pub fn from_local(date: Date, time: Time, offset: UtcOffset) -> Result<Self, DateError> {
683 let local = DateTime::new(date, time);
684 let utc = local.add_duration(Duration::seconds(-(offset.as_seconds() as i64)))?;
685 Ok(OffsetDateTime { utc, offset })
686 }
687
688 pub fn to_local(&self) -> Result<DateTime, DateError> {
690 self.utc
691 .add_duration(Duration::seconds(self.offset.as_seconds() as i64))
692 }
693
694 pub fn unix_timestamp(&self) -> i64 {
696 self.utc.unix_timestamp()
697 }
698
699 pub fn unix_timestamp_nanos(&self) -> i128 {
701 self.utc.unix_timestamp_nanos()
702 }
703
704 pub fn add_duration(&self, dur: Duration) -> Result<Self, DateError> {
706 let utc = self.utc.add_duration(dur)?;
707 Ok(OffsetDateTime {
708 utc,
709 offset: self.offset,
710 })
711 }
712
713 pub fn difference(&self, other: OffsetDateTime) -> Duration {
715 self.utc.difference(other.utc)
716 }
717}
718
719impl fmt::Display for OffsetDateTime {
720 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
721 let local = self
723 .to_local()
724 .expect("OffsetDateTime local representation out of range");
725 write!(f, "{}T{}", local.date, local.time)?;
726 if self.offset.is_utc() {
727 write!(f, "Z")
728 } else {
729 write!(f, "{}", self.offset)
730 }
731 }
732}
733
734impl FromStr for OffsetDateTime {
735 type Err = ();
736
737 fn from_str(s: &str) -> Result<Self, Self::Err> {
740 let s = s.trim();
741 let (date_part, rest) = s.split_once('T').or_else(|| s.split_once(' ')).ok_or(())?;
742 let date: Date = date_part.parse().map_err(|_| ())?;
743
744 let (time_part, offset_part) = if rest.ends_with('Z') || rest.ends_with('z') {
746 (&rest[..rest.len() - 1], "Z")
747 } else {
748 let idx = rest.rfind(['+', '-']).ok_or(())?;
749 (&rest[..idx], &rest[idx..])
750 };
751
752 let time: Time = time_part.parse().map_err(|_| ())?;
753 let offset = parse_rfc3339_offset(offset_part).map_err(|_| ())?;
754 OffsetDateTime::from_local(date, time, offset).map_err(|_| ())
755 }
756}
757
758impl PartialOrd for OffsetDateTime {
759 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
760 Some(self.cmp(other))
761 }
762}
763
764impl Ord for OffsetDateTime {
765 fn cmp(&self, other: &Self) -> Ordering {
766 self.utc.cmp(&other.utc)
767 }
768}
769
770const POW10_U32: [u32; 10] = [
773 1,
774 10,
775 100,
776 1_000,
777 10_000,
778 100_000,
779 1_000_000,
780 10_000_000,
781 100_000_000,
782 1_000_000_000,
783];
784
785fn parse_i32_bytes(bytes: &[u8]) -> Option<i32> {
786 if bytes.is_empty() {
787 return None;
788 }
789 let mut idx = 0;
790 let mut neg = false;
791 match bytes[0] {
792 b'+' => idx = 1,
793 b'-' => {
794 idx = 1;
795 neg = true;
796 }
797 _ => {}
798 }
799 if idx == bytes.len() {
800 return None;
801 }
802
803 let limit: i64 = if neg {
804 i32::MAX as i64 + 1
805 } else {
806 i32::MAX as i64
807 };
808 let mut val: i64 = 0;
809 for &b in &bytes[idx..] {
810 if !b.is_ascii_digit() {
811 return None;
812 }
813 let digit = (b - b'0') as i64;
814 if val > limit / 10 || (val == limit / 10 && digit > limit % 10) {
815 return None;
816 }
817 val = val * 10 + digit;
818 }
819
820 if neg {
821 val = -val;
822 }
823 Some(val as i32)
824}
825
826fn parse_u32_bytes(bytes: &[u8], max: u32) -> Option<u32> {
827 if bytes.is_empty() {
828 return None;
829 }
830 let mut val: u32 = 0;
831 for &b in bytes {
832 if !b.is_ascii_digit() {
833 return None;
834 }
835 let digit = (b - b'0') as u32;
836 if val > max / 10 || (val == max / 10 && digit > max % 10) {
837 return None;
838 }
839 val = val * 10 + digit;
840 }
841 Some(val)
842}
843
844fn parse_fraction_nanos(bytes: &[u8]) -> Option<u32> {
845 let len = bytes.len();
846 if len == 0 || len > 9 {
847 return None;
848 }
849 let mut val: u32 = 0;
850 for &b in bytes {
851 if !b.is_ascii_digit() {
852 return None;
853 }
854 val = val * 10 + (b - b'0') as u32;
855 }
856 let scale = 9 - len;
857 Some(val * POW10_U32[scale])
858}
859
860#[derive(Debug, Clone, Copy, PartialEq, Eq)]
862pub enum Rfc3339OffsetError {
863 InvalidFormat,
864 OutOfRange,
865}
866
867pub fn parse_rfc3339_offset(s: &str) -> Result<UtcOffset, Rfc3339OffsetError> {
868 if s == "Z" || s == "z" {
869 return UtcOffset::from_seconds(0).map_err(|_| Rfc3339OffsetError::OutOfRange);
870 }
871 let bytes = s.as_bytes();
872 if bytes.len() < 3 {
873 return Err(Rfc3339OffsetError::InvalidFormat);
874 }
875 let sign_positive = match bytes[0] {
876 b'+' => true,
877 b'-' => false,
878 _ => return Err(Rfc3339OffsetError::InvalidFormat),
879 };
880 let body = &bytes[1..];
881
882 let mut colon = None;
883 for (idx, &b) in body.iter().enumerate() {
884 if b == b':' {
885 colon = Some(idx);
886 break;
887 }
888 }
889
890 let (h_bytes, m_bytes) = if let Some(colon_idx) = colon {
891 let h = &body[..colon_idx];
892 let m = &body[colon_idx + 1..];
893 if h.is_empty() || h.len() > 2 || m.len() > 2 {
894 return Err(Rfc3339OffsetError::InvalidFormat);
895 }
896 (h, m)
897 } else if body.len() == 2 {
898 (&body[..2], &[][..])
899 } else if body.len() == 4 {
900 (&body[..2], &body[2..])
901 } else {
902 return Err(Rfc3339OffsetError::InvalidFormat);
903 };
904
905 if h_bytes.len() > 2 || m_bytes.len() > 2 {
906 return Err(Rfc3339OffsetError::InvalidFormat);
907 }
908
909 let hours = parse_u32_bytes(h_bytes, 99).ok_or(Rfc3339OffsetError::InvalidFormat)? as u8;
910 let minutes = if m_bytes.is_empty() {
911 0
912 } else {
913 parse_u32_bytes(m_bytes, 99).ok_or(Rfc3339OffsetError::InvalidFormat)? as u8
914 };
915 UtcOffset::from_hours_minutes(sign_positive, hours, minutes)
916 .map_err(|_| Rfc3339OffsetError::OutOfRange)
917}
918
919fn is_leap_year(year: i32) -> bool {
920 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
921}
922
923fn days_in_month(year: i32, month: u8) -> u8 {
924 match month {
925 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
926 4 | 6 | 9 | 11 => 30,
927 2 => {
928 if is_leap_year(year) {
929 29
930 } else {
931 28
932 }
933 }
934 _ => 0,
935 }
936}
937
938fn days_from_civil(y: i32, m: u8, d: u8) -> i64 {
941 let y = y as i64;
942 let m = m as i64;
943 let d = d as i64;
944 let y0 = y - if m <= 2 { 1 } else { 0 };
945 let era = if y0 >= 0 { y0 / 400 } else { (y0 - 399) / 400 };
946 let yoe = y0 - era * 400; let mp = m + if m > 2 { -3 } else { 9 }; let doy = (153 * mp + 2) / 5 + d - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; era * 146_097 + doe - 719_468
951}