1use std::cmp::Ordering;
2use std::fmt;
3
4pub fn is_leap_year(year: i32) -> bool {
7 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
8}
9
10pub fn days_in_month(year: i32, month: u32) -> u32 {
11 match month {
12 1 => 31,
13 2 => {
14 if is_leap_year(year) {
15 29
16 } else {
17 28
18 }
19 }
20 3 => 31,
21 4 => 30,
22 5 => 31,
23 6 => 30,
24 7 => 31,
25 8 => 31,
26 9 => 30,
27 10 => 31,
28 11 => 30,
29 12 => 31,
30 _ => 0,
31 }
32}
33
34fn days_from_civil(y: i32, m: u32, d: u32) -> i64 {
37 let y = y as i64 - if m <= 2 { 1 } else { 0 };
38 let m = m as i64;
39 let d = d as i64;
40 let era = if y >= 0 { y } else { y - 399 } / 400;
41 let yoe = (y - era * 400) as u64;
42 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
43 let doe = yoe as i64 * 365 + yoe as i64 / 4 - yoe as i64 / 100 + doy;
44 era * 146097 + doe - 719468
45}
46
47fn civil_from_days(z: i64) -> (i32, u32, u32) {
49 let z = z + 719468;
50 let era = if z >= 0 { z } else { z - 146096 } / 146097;
51 let doe = (z - era * 146097) as u64;
52 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
53 let y = yoe as i64 + era * 400;
54 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
55 let mp = (5 * doy + 2) / 153;
56 let d = doy - (153 * mp + 2) / 5 + 1;
57 let m = if mp < 10 { mp + 3 } else { mp - 9 };
58 let y = if m <= 2 { y + 1 } else { y };
59 (y as i32, m as u32, d as u32)
60}
61
62#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
70fn unix_now() -> (u64, u32) {
71 use std::time::{SystemTime, UNIX_EPOCH};
72 let dur = SystemTime::now()
73 .duration_since(UNIX_EPOCH)
74 .unwrap_or_default();
75 (dur.as_secs(), dur.subsec_nanos())
76}
77
78#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
79fn unix_now() -> (u64, u32) {
80 let ms = js_sys::Date::now();
81 if !ms.is_finite() || ms < 0.0 {
82 return (0, 0);
83 }
84 let secs = (ms / 1_000.0).floor();
85 let nanos = ((ms - secs * 1_000.0) * 1_000_000.0).round();
86 (secs as u64, nanos as u32)
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct LoraDate {
93 pub year: i32,
94 pub month: u32,
95 pub day: u32,
96}
97
98impl LoraDate {
99 pub fn new(year: i32, month: u32, day: u32) -> Result<Self, String> {
100 if !(1..=12).contains(&month) {
101 return Err(format!("Invalid month: {month}"));
102 }
103 let max = days_in_month(year, month);
104 if day < 1 || day > max {
105 return Err(format!("Invalid day {day} for {year}-{month:02}"));
106 }
107 Ok(Self { year, month, day })
108 }
109
110 pub fn parse(s: &str) -> Result<Self, String> {
111 let parts: Vec<&str> = s.split('-').collect();
112 if parts.len() != 3 {
113 return Err(format!("Invalid date format: {s}"));
114 }
115 let year = parts[0]
116 .parse::<i32>()
117 .map_err(|_| format!("Invalid date: {s}"))?;
118 let month = parts[1]
119 .parse::<u32>()
120 .map_err(|_| format!("Invalid date: {s}"))?;
121 let day = parts[2]
122 .parse::<u32>()
123 .map_err(|_| format!("Invalid date: {s}"))?;
124 Self::new(year, month, day)
125 }
126
127 pub fn today() -> Self {
128 let (secs, _) = unix_now();
129 let days = (secs / 86400) as i64;
130 Self::from_epoch_days(days)
131 }
132
133 pub fn to_epoch_days(&self) -> i64 {
134 days_from_civil(self.year, self.month, self.day)
135 }
136
137 pub fn from_epoch_days(days: i64) -> Self {
138 let (y, m, d) = civil_from_days(days);
139 Self {
140 year: y,
141 month: m,
142 day: d,
143 }
144 }
145
146 pub fn day_of_week(&self) -> u32 {
147 let z = self.to_epoch_days();
148 (((z % 7) + 7 + 3) % 7 + 1) as u32
149 }
150
151 pub fn day_of_year(&self) -> u32 {
152 let mut doy = self.day;
153 for m in 1..self.month {
154 doy += days_in_month(self.year, m);
155 }
156 doy
157 }
158
159 pub fn add_duration(&self, dur: &LoraDuration) -> Self {
160 let total_months = self.year as i64 * 12 + (self.month as i64 - 1) + dur.months;
162 let new_year = total_months.div_euclid(12) as i32;
163 let new_month = (total_months.rem_euclid(12) + 1) as u32;
164 let max_day = days_in_month(new_year, new_month);
165 let new_day = self.day.min(max_day);
166 let epoch = days_from_civil(new_year, new_month, new_day) + dur.days;
168 Self::from_epoch_days(epoch)
169 }
170
171 pub fn sub_duration(&self, dur: &LoraDuration) -> Self {
172 self.add_duration(&dur.negate())
173 }
174
175 pub fn truncate_to_month(&self) -> Self {
176 Self {
177 year: self.year,
178 month: self.month,
179 day: 1,
180 }
181 }
182}
183
184impl PartialOrd for LoraDate {
185 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
186 Some(self.cmp(other))
187 }
188}
189
190impl Ord for LoraDate {
191 fn cmp(&self, other: &Self) -> Ordering {
192 self.year
193 .cmp(&other.year)
194 .then(self.month.cmp(&other.month))
195 .then(self.day.cmp(&other.day))
196 }
197}
198
199impl fmt::Display for LoraDate {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct LoraTime {
209 pub hour: u32,
210 pub minute: u32,
211 pub second: u32,
212 pub nanosecond: u32,
213 pub offset_seconds: i32,
214}
215
216impl LoraTime {
217 pub fn new(
218 hour: u32,
219 minute: u32,
220 second: u32,
221 nanosecond: u32,
222 offset_seconds: i32,
223 ) -> Result<Self, String> {
224 if hour > 23 {
225 return Err(format!("Invalid hour: {hour}"));
226 }
227 if minute > 59 {
228 return Err(format!("Invalid minute: {minute}"));
229 }
230 if second > 59 {
231 return Err(format!("Invalid second: {second}"));
232 }
233 Ok(Self {
234 hour,
235 minute,
236 second,
237 nanosecond,
238 offset_seconds,
239 })
240 }
241
242 pub fn parse(s: &str) -> Result<Self, String> {
243 let (h, m, sec, ns, offset) = parse_time_string(s)?;
244 let offset = offset.unwrap_or(0);
245 Self::new(h, m, sec, ns, offset)
246 }
247
248 pub fn now() -> Self {
249 let (secs, nanos) = unix_now();
250 let day_secs = secs % 86400;
251 Self {
252 hour: (day_secs / 3600) as u32,
253 minute: ((day_secs % 3600) / 60) as u32,
254 second: (day_secs % 60) as u32,
255 nanosecond: nanos,
256 offset_seconds: 0,
257 }
258 }
259}
260
261impl fmt::Display for LoraTime {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
264 format_subsecond(f, self.nanosecond)?;
265 format_offset(f, self.offset_seconds)
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct LoraLocalTime {
273 pub hour: u32,
274 pub minute: u32,
275 pub second: u32,
276 pub nanosecond: u32,
277}
278
279impl LoraLocalTime {
280 pub fn new(hour: u32, minute: u32, second: u32, nanosecond: u32) -> Result<Self, String> {
281 if hour > 23 {
282 return Err(format!("Invalid hour: {hour}"));
283 }
284 if minute > 59 {
285 return Err(format!("Invalid minute: {minute}"));
286 }
287 if second > 59 {
288 return Err(format!("Invalid second: {second}"));
289 }
290 Ok(Self {
291 hour,
292 minute,
293 second,
294 nanosecond,
295 })
296 }
297
298 pub fn parse(s: &str) -> Result<Self, String> {
299 let (h, m, sec, ns, _) = parse_time_string(s)?;
300 Self::new(h, m, sec, ns)
301 }
302
303 pub fn now() -> Self {
304 let (secs, nanos) = unix_now();
305 let day_secs = secs % 86400;
306 Self {
307 hour: (day_secs / 3600) as u32,
308 minute: ((day_secs % 3600) / 60) as u32,
309 second: (day_secs % 60) as u32,
310 nanosecond: nanos,
311 }
312 }
313}
314
315impl fmt::Display for LoraLocalTime {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
318 format_subsecond(f, self.nanosecond)
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
325pub struct LoraDateTime {
326 pub year: i32,
327 pub month: u32,
328 pub day: u32,
329 pub hour: u32,
330 pub minute: u32,
331 pub second: u32,
332 pub nanosecond: u32,
333 pub offset_seconds: i32,
334}
335
336impl LoraDateTime {
337 #[allow(clippy::too_many_arguments)] pub fn new(
339 year: i32,
340 month: u32,
341 day: u32,
342 hour: u32,
343 minute: u32,
344 second: u32,
345 nanosecond: u32,
346 offset_seconds: i32,
347 ) -> Result<Self, String> {
348 LoraDate::new(year, month, day)?;
349 if hour > 23 {
350 return Err(format!("Invalid hour: {hour}"));
351 }
352 if minute > 59 {
353 return Err(format!("Invalid minute: {minute}"));
354 }
355 if second > 59 {
356 return Err(format!("Invalid second: {second}"));
357 }
358 Ok(Self {
359 year,
360 month,
361 day,
362 hour,
363 minute,
364 second,
365 nanosecond,
366 offset_seconds,
367 })
368 }
369
370 pub fn parse(s: &str) -> Result<Self, String> {
371 let t_pos = s
372 .find('T')
373 .ok_or_else(|| format!("Invalid datetime: {s}"))?;
374 let date_part = &s[..t_pos];
375 let time_part = &s[t_pos + 1..];
376
377 let date = LoraDate::parse(date_part)?;
378 let (h, m, sec, ns, offset) = parse_time_string(time_part)?;
379 let offset = offset.unwrap_or(0);
380
381 Self::new(date.year, date.month, date.day, h, m, sec, ns, offset)
382 }
383
384 pub fn now() -> Self {
385 let (secs, nanos) = unix_now();
386 let days = (secs / 86400) as i64;
387 let day_secs = secs % 86400;
388 let (y, mo, d) = civil_from_days(days);
389 Self {
390 year: y,
391 month: mo,
392 day: d,
393 hour: (day_secs / 3600) as u32,
394 minute: ((day_secs % 3600) / 60) as u32,
395 second: (day_secs % 60) as u32,
396 nanosecond: nanos,
397 offset_seconds: 0,
398 }
399 }
400
401 pub fn to_epoch_millis(&self) -> i64 {
403 let days = days_from_civil(self.year, self.month, self.day);
404 let day_secs = self.hour as i64 * 3600 + self.minute as i64 * 60 + self.second as i64;
405 let utc_secs = days * 86400 + day_secs - self.offset_seconds as i64;
406 utc_secs * 1000 + self.nanosecond as i64 / 1_000_000
407 }
408
409 pub fn add_duration(&self, dur: &LoraDuration) -> Self {
410 let total_months = self.year as i64 * 12 + (self.month as i64 - 1) + dur.months;
412 let new_year = total_months.div_euclid(12) as i32;
413 let new_month = (total_months.rem_euclid(12) + 1) as u32;
414 let max_day = days_in_month(new_year, new_month);
415 let new_day = self.day.min(max_day);
416
417 let base_days = days_from_civil(new_year, new_month, new_day) + dur.days;
419 let base_secs =
420 self.hour as i64 * 3600 + self.minute as i64 * 60 + self.second as i64 + dur.seconds;
421
422 let total_secs = base_days * 86400 + base_secs;
423 let final_days = total_secs.div_euclid(86400);
424 let rem = total_secs.rem_euclid(86400);
425 let (y, m, d) = civil_from_days(final_days);
426
427 Self {
428 year: y,
429 month: m,
430 day: d,
431 hour: (rem / 3600) as u32,
432 minute: ((rem % 3600) / 60) as u32,
433 second: (rem % 60) as u32,
434 nanosecond: self.nanosecond,
435 offset_seconds: self.offset_seconds,
436 }
437 }
438
439 pub fn truncate_to_day(&self) -> Self {
440 Self {
441 year: self.year,
442 month: self.month,
443 day: self.day,
444 hour: 0,
445 minute: 0,
446 second: 0,
447 nanosecond: 0,
448 offset_seconds: self.offset_seconds,
449 }
450 }
451
452 pub fn truncate_to_hour(&self) -> Self {
453 Self {
454 year: self.year,
455 month: self.month,
456 day: self.day,
457 hour: self.hour,
458 minute: 0,
459 second: 0,
460 nanosecond: 0,
461 offset_seconds: self.offset_seconds,
462 }
463 }
464
465 pub fn date(&self) -> LoraDate {
466 LoraDate {
467 year: self.year,
468 month: self.month,
469 day: self.day,
470 }
471 }
472}
473
474impl PartialOrd for LoraDateTime {
475 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
476 Some(self.cmp(other))
477 }
478}
479
480impl Ord for LoraDateTime {
481 fn cmp(&self, other: &Self) -> Ordering {
482 self.to_epoch_millis().cmp(&other.to_epoch_millis())
483 }
484}
485
486impl fmt::Display for LoraDateTime {
487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488 write!(
489 f,
490 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
491 self.year, self.month, self.day, self.hour, self.minute, self.second
492 )?;
493 format_subsecond(f, self.nanosecond)?;
494 format_offset(f, self.offset_seconds)
495 }
496}
497
498#[derive(Debug, Clone, PartialEq, Eq)]
501pub struct LoraLocalDateTime {
502 pub year: i32,
503 pub month: u32,
504 pub day: u32,
505 pub hour: u32,
506 pub minute: u32,
507 pub second: u32,
508 pub nanosecond: u32,
509}
510
511impl LoraLocalDateTime {
512 pub fn parse(s: &str) -> Result<Self, String> {
513 let t_pos = s
514 .find('T')
515 .ok_or_else(|| format!("Invalid localdatetime: {s}"))?;
516 let date = LoraDate::parse(&s[..t_pos])?;
517 let (h, m, sec, ns, _) = parse_time_string(&s[t_pos + 1..])?;
518 if h > 23 {
519 return Err(format!("Invalid hour: {h}"));
520 }
521 if m > 59 {
522 return Err(format!("Invalid minute: {m}"));
523 }
524 if sec > 59 {
525 return Err(format!("Invalid second: {sec}"));
526 }
527 Ok(Self {
528 year: date.year,
529 month: date.month,
530 day: date.day,
531 hour: h,
532 minute: m,
533 second: sec,
534 nanosecond: ns,
535 })
536 }
537
538 pub fn now() -> Self {
539 let (secs, nanos) = unix_now();
540 let days = (secs / 86400) as i64;
541 let day_secs = secs % 86400;
542 let (y, mo, d) = civil_from_days(days);
543 Self {
544 year: y,
545 month: mo,
546 day: d,
547 hour: (day_secs / 3600) as u32,
548 minute: ((day_secs % 3600) / 60) as u32,
549 second: (day_secs % 60) as u32,
550 nanosecond: nanos,
551 }
552 }
553}
554
555impl fmt::Display for LoraLocalDateTime {
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 write!(
558 f,
559 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
560 self.year, self.month, self.day, self.hour, self.minute, self.second
561 )?;
562 format_subsecond(f, self.nanosecond)
563 }
564}
565
566#[derive(Debug, Clone, PartialEq, Eq)]
569pub struct LoraDuration {
570 pub months: i64,
571 pub days: i64,
572 pub seconds: i64,
573 pub nanoseconds: i64,
574}
575
576impl LoraDuration {
577 pub fn zero() -> Self {
578 Self {
579 months: 0,
580 days: 0,
581 seconds: 0,
582 nanoseconds: 0,
583 }
584 }
585
586 pub fn parse(s: &str) -> Result<Self, String> {
587 let s = s.trim();
588 if !s.starts_with('P') {
589 return Err(format!("Invalid duration format: {s}"));
590 }
591 let rest = &s[1..];
592 if rest.is_empty() {
593 return Err(format!("Invalid duration: {s}"));
594 }
595
596 let mut months: i64 = 0;
597 let mut days: i64 = 0;
598 let mut seconds: i64 = 0;
599 let mut nanoseconds: i64 = 0;
600 let mut in_time = false;
601 let mut num_buf = String::new();
602
603 for c in rest.chars() {
604 match c {
605 'T' => {
606 in_time = true;
607 }
608 '0'..='9' | '.' => {
609 num_buf.push(c);
610 }
611 'Y' if !in_time => {
612 let n: i64 = num_buf
613 .parse()
614 .map_err(|_| format!("Invalid duration: {s}"))?;
615 months += n * 12;
616 num_buf.clear();
617 }
618 'M' if !in_time => {
619 let n: i64 = num_buf
620 .parse()
621 .map_err(|_| format!("Invalid duration: {s}"))?;
622 months += n;
623 num_buf.clear();
624 }
625 'W' if !in_time => {
626 let n: i64 = num_buf
627 .parse()
628 .map_err(|_| format!("Invalid duration: {s}"))?;
629 days += n * 7;
630 num_buf.clear();
631 }
632 'D' => {
633 let n: i64 = num_buf
634 .parse()
635 .map_err(|_| format!("Invalid duration: {s}"))?;
636 days += n;
637 num_buf.clear();
638 }
639 'H' if in_time => {
640 let n: i64 = num_buf
641 .parse()
642 .map_err(|_| format!("Invalid duration: {s}"))?;
643 seconds += n * 3600;
644 num_buf.clear();
645 }
646 'M' if in_time => {
647 let n: i64 = num_buf
648 .parse()
649 .map_err(|_| format!("Invalid duration: {s}"))?;
650 seconds += n * 60;
651 num_buf.clear();
652 }
653 'S' if in_time => {
654 if num_buf.contains('.') {
655 let n: f64 = num_buf
656 .parse()
657 .map_err(|_| format!("Invalid duration: {s}"))?;
658 seconds += n.floor() as i64;
659 let frac = n - n.floor();
660 if frac > 0.0 {
661 nanoseconds += (frac * 1_000_000_000.0) as i64;
662 }
663 } else {
664 let n: i64 = num_buf
665 .parse()
666 .map_err(|_| format!("Invalid duration: {s}"))?;
667 seconds += n;
668 }
669 num_buf.clear();
670 }
671 _ => return Err(format!("Invalid duration format: {s}")),
672 }
673 }
674
675 if !num_buf.is_empty() {
676 return Err(format!("Trailing number in duration: {s}"));
677 }
678
679 Ok(Self {
680 months,
681 days,
682 seconds,
683 nanoseconds,
684 })
685 }
686
687 pub fn negate(&self) -> Self {
688 Self {
689 months: -self.months,
690 days: -self.days,
691 seconds: -self.seconds,
692 nanoseconds: -self.nanoseconds,
693 }
694 }
695
696 pub fn add(&self, other: &Self) -> Self {
697 Self {
698 months: self.months + other.months,
699 days: self.days + other.days,
700 seconds: self.seconds + other.seconds,
701 nanoseconds: self.nanoseconds + other.nanoseconds,
702 }
703 }
704
705 pub fn mul_int(&self, n: i64) -> Self {
706 Self {
707 months: self.months * n,
708 days: self.days * n,
709 seconds: self.seconds * n,
710 nanoseconds: self.nanoseconds * n,
711 }
712 }
713
714 pub fn div_int(&self, n: i64) -> Self {
715 if n == 0 {
716 return Self::zero();
717 }
718 Self {
719 months: self.months / n,
720 days: self.days / n,
721 seconds: self.seconds / n,
722 nanoseconds: self.nanoseconds / n,
723 }
724 }
725
726 pub fn between_dates(from: &LoraDate, to: &LoraDate) -> Self {
728 let sign: i64 = if from <= to { 1 } else { -1 };
729 let (earlier, later) = if from <= to { (from, to) } else { (to, from) };
730
731 let mut months = (later.year as i64 - earlier.year as i64) * 12
733 + (later.month as i64 - earlier.month as i64);
734
735 let intermediate = earlier.add_duration(&LoraDuration {
737 months,
738 days: 0,
739 seconds: 0,
740 nanoseconds: 0,
741 });
742 if intermediate.to_epoch_days() > later.to_epoch_days() {
743 months -= 1;
744 }
745 let intermediate = earlier.add_duration(&LoraDuration {
746 months,
747 days: 0,
748 seconds: 0,
749 nanoseconds: 0,
750 });
751 let remaining_days = later.to_epoch_days() - intermediate.to_epoch_days();
752
753 Self {
754 months: months * sign,
755 days: remaining_days * sign,
756 seconds: 0,
757 nanoseconds: 0,
758 }
759 }
760
761 pub fn in_days(from: &LoraDate, to: &LoraDate) -> Self {
763 let days = to.to_epoch_days() - from.to_epoch_days();
764 Self {
765 months: 0,
766 days,
767 seconds: 0,
768 nanoseconds: 0,
769 }
770 }
771
772 pub fn between_datetimes(from: &LoraDateTime, to: &LoraDateTime) -> Self {
774 let ms_diff = to.to_epoch_millis() - from.to_epoch_millis();
775 let total_secs = ms_diff / 1000;
776 let remaining_ms = ms_diff % 1000;
777 Self {
778 months: 0,
779 days: total_secs / 86400,
780 seconds: total_secs % 86400,
781 nanoseconds: remaining_ms * 1_000_000,
782 }
783 }
784
785 pub fn total_seconds_approx(&self) -> f64 {
787 self.months as f64 * 2_629_800.0
789 + self.days as f64 * 86400.0
790 + self.seconds as f64
791 + self.nanoseconds as f64 / 1_000_000_000.0
792 }
793
794 pub fn years_component(&self) -> i64 {
795 self.months / 12
796 }
797 pub fn months_component(&self) -> i64 {
798 self.months % 12
799 }
800 pub fn days_component(&self) -> i64 {
801 self.days
802 }
803 pub fn hours_component(&self) -> i64 {
804 self.seconds / 3600
805 }
806 pub fn minutes_component(&self) -> i64 {
807 (self.seconds % 3600) / 60
808 }
809 pub fn seconds_component(&self) -> i64 {
810 self.seconds % 60
811 }
812}
813
814impl PartialOrd for LoraDuration {
815 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
816 Some(self.cmp(other))
817 }
818}
819
820impl Ord for LoraDuration {
821 fn cmp(&self, other: &Self) -> Ordering {
822 self.total_seconds_approx()
825 .total_cmp(&other.total_seconds_approx())
826 }
827}
828
829impl fmt::Display for LoraDuration {
830 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
831 write!(f, "P")?;
832 let years = self.months / 12;
833 let months = self.months % 12;
834 if years != 0 {
835 write!(f, "{}Y", years)?;
836 }
837 if months != 0 {
838 write!(f, "{}M", months)?;
839 }
840 if self.days != 0 {
841 write!(f, "{}D", self.days)?;
842 }
843
844 let hours = self.seconds / 3600;
845 let minutes = (self.seconds % 3600) / 60;
846 let secs = self.seconds % 60;
847
848 if hours != 0 || minutes != 0 || secs != 0 || self.nanoseconds != 0 {
849 write!(f, "T")?;
850 if hours != 0 {
851 write!(f, "{}H", hours)?;
852 }
853 if minutes != 0 {
854 write!(f, "{}M", minutes)?;
855 }
856 if secs != 0 {
857 write!(f, "{}S", secs)?;
858 } else if self.nanoseconds != 0 {
859 write!(f, "0.{:09}S", self.nanoseconds)?;
860 }
861 }
862
863 if self.months == 0 && self.days == 0 && self.seconds == 0 && self.nanoseconds == 0 {
865 write!(f, "0D")?;
866 }
867
868 Ok(())
869 }
870}
871
872fn parse_time_string(s: &str) -> Result<(u32, u32, u32, u32, Option<i32>), String> {
876 let (time_str, offset) = if let Some(stripped) = s.strip_suffix('Z') {
878 (stripped, Some(0i32))
879 } else if let Some(pos) = s.rfind('+') {
880 if pos >= 2 {
881 let off = parse_offset(&s[pos..])?;
882 (&s[..pos], Some(off))
883 } else {
884 (s, None)
885 }
886 } else {
887 let search_start = 5.min(s.len());
890 if let Some(rel_pos) = s[search_start..].rfind('-') {
891 let pos = search_start + rel_pos;
892 let off = parse_offset(&s[pos..])?;
893 (&s[..pos], Some(off))
894 } else {
895 (s, None)
896 }
897 };
898
899 let parts: Vec<&str> = time_str.split(':').collect();
900 if parts.len() < 2 || parts.len() > 3 {
901 return Err(format!("Invalid time: {s}"));
902 }
903
904 let hour = parts[0]
905 .parse::<u32>()
906 .map_err(|_| format!("Invalid time: {s}"))?;
907 let minute = parts[1]
908 .parse::<u32>()
909 .map_err(|_| format!("Invalid time: {s}"))?;
910
911 let (second, nanosecond) = if parts.len() == 3 {
912 parse_seconds_and_fraction(parts[2])?
913 } else {
914 (0, 0)
915 };
916
917 Ok((hour, minute, second, nanosecond, offset))
918}
919
920fn parse_seconds_and_fraction(s: &str) -> Result<(u32, u32), String> {
921 if let Some(dot_pos) = s.find('.') {
922 let sec = s[..dot_pos]
923 .parse::<u32>()
924 .map_err(|_| format!("Invalid seconds: {s}"))?;
925 let frac = &s[dot_pos + 1..];
926 let padded = format!("{:0<9}", frac);
928 let ns = padded[..9].parse::<u32>().unwrap_or(0);
929 Ok((sec, ns))
930 } else {
931 let sec = s
932 .parse::<u32>()
933 .map_err(|_| format!("Invalid seconds: {s}"))?;
934 Ok((sec, 0))
935 }
936}
937
938fn parse_offset(s: &str) -> Result<i32, String> {
939 let sign = if s.starts_with('+') {
940 1
941 } else if s.starts_with('-') {
942 -1
943 } else {
944 return Err(format!("Invalid offset: {s}"));
945 };
946 let rest = &s[1..];
947 let parts: Vec<&str> = rest.split(':').collect();
948 if parts.len() != 2 {
949 return Err(format!("Invalid offset: {s}"));
950 }
951 let h = parts[0]
952 .parse::<i32>()
953 .map_err(|_| format!("Invalid offset: {s}"))?;
954 let m = parts[1]
955 .parse::<i32>()
956 .map_err(|_| format!("Invalid offset: {s}"))?;
957 Ok(sign * (h * 3600 + m * 60))
958}
959
960fn format_offset(f: &mut fmt::Formatter<'_>, offset_seconds: i32) -> fmt::Result {
961 if offset_seconds == 0 {
962 write!(f, "Z")
963 } else {
964 let sign = if offset_seconds >= 0 { '+' } else { '-' };
965 let abs = offset_seconds.unsigned_abs();
966 let h = abs / 3600;
967 let m = (abs % 3600) / 60;
968 write!(f, "{}{:02}:{:02}", sign, h, m)
969 }
970}
971
972fn format_subsecond(f: &mut fmt::Formatter<'_>, nanosecond: u32) -> fmt::Result {
973 if nanosecond > 0 {
974 let ms = nanosecond / 1_000_000;
975 if ms > 0 && nanosecond.is_multiple_of(1_000_000) {
976 write!(f, ".{:03}", ms)
977 } else {
978 write!(f, ".{:09}", nanosecond)
979 }
980 } else {
981 Ok(())
982 }
983}