1use std::cmp::Ordering;
18
19use super::types::{TypeError, Value};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct XsdDateTime {
25 pub year: i32,
28 pub month: u8,
29 pub day: u8,
30 pub hour: u8,
31 pub minute: u8,
32 pub second: u8,
33 pub nanos: u32,
35 pub tz_min: Option<i16>,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub struct XsdDate {
41 pub year: i32,
42 pub month: u8,
43 pub day: u8,
44 pub tz_min: Option<i16>,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub struct XsdTime {
49 pub hour: u8,
50 pub minute: u8,
51 pub second: u8,
52 pub nanos: u32,
53 pub tz_min: Option<i16>,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub struct XsdGYearMonth { pub year: i32, pub month: u8, pub tz_min: Option<i16> }
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub struct XsdGYear { pub year: i32, pub tz_min: Option<i16> }
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
63pub struct XsdGMonthDay { pub month: u8, pub day: u8, pub tz_min: Option<i16> }
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub struct XsdGDay { pub day: u8, pub tz_min: Option<i16> }
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub struct XsdGMonth { pub month: u8, pub tz_min: Option<i16> }
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76pub struct XsdDuration {
77 pub months: i64,
78 pub seconds: i64,
80 pub nanos: u32,
81}
82
83struct Cur<'a> { s: &'a [u8], i: usize }
86
87impl<'a> Cur<'a> {
88 fn new(s: &'a str) -> Self { Self { s: s.as_bytes(), i: 0 } }
89 fn done(&self) -> bool { self.i >= self.s.len() }
90 fn peek(&self) -> Option<u8> { self.s.get(self.i).copied() }
91 fn bump(&mut self) -> Option<u8> {
92 let b = self.peek()?; self.i += 1; Some(b)
93 }
94 fn expect(&mut self, b: u8) -> Result<(), TypeError> {
95 if self.bump() == Some(b) {
96 Ok(())
97 } else {
98 Err(TypeError::type_mismatch(format!("expected {:?}", b as char)))
99 }
100 }
101 fn read_digits(&mut self, min: usize, max: usize) -> Result<i64, TypeError> {
103 let start = self.i;
104 while self.i < self.s.len()
105 && self.s[self.i].is_ascii_digit()
106 && self.i - start < max
107 {
108 self.i += 1;
109 }
110 let n = self.i - start;
111 if n < min {
112 return Err(TypeError::type_mismatch(
113 format!("expected at least {min} digits, got {n}")
114 ));
115 }
116 std::str::from_utf8(&self.s[start..self.i])
117 .unwrap()
118 .parse::<i64>()
119 .map_err(|e| TypeError::type_mismatch(format!("digit parse: {e}")))
120 }
121 fn at_end(&self) -> bool { self.done() }
122}
123
124fn read_year(c: &mut Cur) -> Result<i32, TypeError> {
127 let neg = c.peek() == Some(b'-');
128 if neg { c.bump(); }
129 let start = c.i;
131 while c.i < c.s.len() && c.s[c.i].is_ascii_digit() {
132 c.i += 1;
133 }
134 let n = c.i - start;
135 if n < 4 {
136 return Err(TypeError::type_mismatch(
137 format!("year requires at least 4 digits, got {n}")
138 ));
139 }
140 if n > 4 && c.s[start] == b'0' {
142 return Err(TypeError::type_mismatch(
143 "extended year cannot start with 0"
144 ));
145 }
146 let body: i64 = std::str::from_utf8(&c.s[start..c.i]).unwrap().parse().unwrap();
147 if body == 0 {
148 return Err(TypeError::type_mismatch("year zero is not allowed in XSD"));
149 }
150 let signed = if neg { -body } else { body };
151 if signed < i32::MIN as i64 || signed > i32::MAX as i64 {
152 return Err(TypeError::type_mismatch("year out of i32 range"));
153 }
154 Ok(signed as i32)
155}
156
157fn read_month(c: &mut Cur) -> Result<u8, TypeError> {
158 let n = c.read_digits(2, 2)?;
159 if !(1..=12).contains(&n) {
160 return Err(TypeError::type_mismatch(format!("month {n} out of range")));
161 }
162 Ok(n as u8)
163}
164
165fn read_day(c: &mut Cur) -> Result<u8, TypeError> {
166 let n = c.read_digits(2, 2)?;
167 if !(1..=31).contains(&n) {
168 return Err(TypeError::type_mismatch(format!("day {n} out of range")));
169 }
170 Ok(n as u8)
171}
172
173fn read_hh(c: &mut Cur) -> Result<u8, TypeError> {
174 let n = c.read_digits(2, 2)?;
175 if !(0..=24).contains(&n) {
176 return Err(TypeError::type_mismatch(format!("hour {n} out of range")));
177 }
178 Ok(n as u8)
179}
180
181fn read_mm(c: &mut Cur) -> Result<u8, TypeError> {
182 let n = c.read_digits(2, 2)?;
183 if !(0..=59).contains(&n) {
184 return Err(TypeError::type_mismatch(format!("minute {n} out of range")));
185 }
186 Ok(n as u8)
187}
188
189fn read_ss_with_nanos(c: &mut Cur) -> Result<(u8, u32), TypeError> {
190 let s = c.read_digits(2, 2)?;
191 if !(0..=60).contains(&s) {
192 return Err(TypeError::type_mismatch(format!("second {s} out of range")));
193 }
194 let nanos = if c.peek() == Some(b'.') {
195 c.bump();
196 let start = c.i;
197 while c.i < c.s.len() && c.s[c.i].is_ascii_digit() {
198 c.i += 1;
199 }
200 let n = c.i - start;
201 if n == 0 {
202 return Err(TypeError::type_mismatch("expected fractional digits after '.'"));
203 }
204 let take = n.min(9);
206 let bytes = &c.s[start..start + take];
207 let val: u32 = std::str::from_utf8(bytes).unwrap().parse().unwrap();
208 let scale = 10u32.pow(9 - take as u32);
210 val * scale
211 } else { 0 };
212 Ok((s as u8, nanos))
213}
214
215fn read_tz(c: &mut Cur) -> Result<Option<i16>, TypeError> {
218 if c.done() { return Ok(None); }
219 match c.bump().unwrap() {
220 b'Z' => Ok(Some(0)),
221 sign @ (b'+' | b'-') => {
222 let h = c.read_digits(2, 2)?;
223 c.expect(b':')?;
224 let m = c.read_digits(2, 2)?;
225 if h > 14 || m > 59 || (h == 14 && m != 0) {
226 return Err(TypeError::type_mismatch(
227 format!("timezone {sign:?}{h:02}:{m:02} out of range")
228 ));
229 }
230 let total = (h as i16) * 60 + (m as i16);
231 Ok(Some(if sign == b'-' { -total } else { total }))
232 }
233 other => Err(TypeError::type_mismatch(
234 format!("expected timezone (Z or +/-HH:MM), got {:?}", other as char)
235 )),
236 }
237}
238
239fn finish(c: &mut Cur) -> Result<(), TypeError> {
240 if c.at_end() { Ok(()) }
241 else { Err(TypeError::type_mismatch(format!(
242 "trailing junk at offset {}", c.i
243 ))) }
244}
245
246fn validate_day_in_month(year: i32, month: u8, day: u8) -> Result<(), TypeError> {
247 let max = days_in_month(year, month);
248 if day > max {
249 return Err(TypeError::type_mismatch(
250 format!("day {day} out of range for {year}-{month:02}")
251 ));
252 }
253 Ok(())
254}
255
256fn days_in_month(year: i32, month: u8) -> u8 {
257 match month {
258 1|3|5|7|8|10|12 => 31,
259 4|6|9|11 => 30,
260 2 => if is_leap_year(year) { 29 } else { 28 },
261 _ => 0,
262 }
263}
264
265fn is_leap_year(y: i32) -> bool {
266 (y % 4 == 0 && y % 100 != 0) || y % 400 == 0
267}
268
269pub fn parse_date_time(s: &str) -> Result<Value, TypeError> {
272 let mut c = Cur::new(s);
273 let year = read_year(&mut c)?;
274 c.expect(b'-')?;
275 let month = read_month(&mut c)?;
276 c.expect(b'-')?;
277 let day = read_day(&mut c)?;
278 validate_day_in_month(year, month, day)?;
279 c.expect(b'T')?;
280 let hour = read_hh(&mut c)?;
281 c.expect(b':')?;
282 let minute = read_mm(&mut c)?;
283 c.expect(b':')?;
284 let (second, nanos) = read_ss_with_nanos(&mut c)?;
285 if hour == 24 && (minute != 0 || second != 0 || nanos != 0) {
286 return Err(TypeError::type_mismatch("24:xx:xx invalid (only 24:00:00 allowed)"));
287 }
288 let tz_min = read_tz(&mut c)?;
289 finish(&mut c)?;
290 Ok(Value::DateTime(XsdDateTime { year, month, day, hour, minute, second, nanos, tz_min }))
291}
292
293pub fn parse_date(s: &str) -> Result<Value, TypeError> {
294 let mut c = Cur::new(s);
295 let year = read_year(&mut c)?;
296 c.expect(b'-')?;
297 let month = read_month(&mut c)?;
298 c.expect(b'-')?;
299 let day = read_day(&mut c)?;
300 validate_day_in_month(year, month, day)?;
301 let tz_min = read_tz(&mut c)?;
302 finish(&mut c)?;
303 Ok(Value::Date(XsdDate { year, month, day, tz_min }))
304}
305
306pub fn parse_time(s: &str) -> Result<Value, TypeError> {
307 let mut c = Cur::new(s);
308 let hour = read_hh(&mut c)?;
309 c.expect(b':')?;
310 let minute = read_mm(&mut c)?;
311 c.expect(b':')?;
312 let (second, nanos) = read_ss_with_nanos(&mut c)?;
313 if hour == 24 && (minute != 0 || second != 0 || nanos != 0) {
314 return Err(TypeError::type_mismatch("24:xx:xx invalid"));
315 }
316 let tz_min = read_tz(&mut c)?;
317 finish(&mut c)?;
318 Ok(Value::Time(XsdTime { hour, minute, second, nanos, tz_min }))
319}
320
321pub fn parse_g_year_month(s: &str) -> Result<Value, TypeError> {
322 let mut c = Cur::new(s);
323 let year = read_year(&mut c)?;
324 c.expect(b'-')?;
325 let month = read_month(&mut c)?;
326 let tz_min = read_tz(&mut c)?;
327 finish(&mut c)?;
328 Ok(Value::GYearMonth(XsdGYearMonth { year, month, tz_min }))
329}
330
331pub fn parse_g_year(s: &str) -> Result<Value, TypeError> {
332 let mut c = Cur::new(s);
333 let year = read_year(&mut c)?;
334 let tz_min = read_tz(&mut c)?;
335 finish(&mut c)?;
336 Ok(Value::GYear(XsdGYear { year, tz_min }))
337}
338
339pub fn parse_g_month_day(s: &str) -> Result<Value, TypeError> {
340 let mut c = Cur::new(s);
341 c.expect(b'-')?; c.expect(b'-')?;
342 let month = read_month(&mut c)?;
343 c.expect(b'-')?;
344 let day = read_day(&mut c)?;
345 if day > days_in_month(2000 , month) && !(month == 2 && day == 29) {
346 return Err(TypeError::type_mismatch(
347 format!("day {day} out of range for month {month:02}")
348 ));
349 }
350 let tz_min = read_tz(&mut c)?;
351 finish(&mut c)?;
352 Ok(Value::GMonthDay(XsdGMonthDay { month, day, tz_min }))
353}
354
355pub fn parse_g_day(s: &str) -> Result<Value, TypeError> {
356 let mut c = Cur::new(s);
357 c.expect(b'-')?; c.expect(b'-')?; c.expect(b'-')?;
358 let day = read_day(&mut c)?;
359 let tz_min = read_tz(&mut c)?;
360 finish(&mut c)?;
361 Ok(Value::GDay(XsdGDay { day, tz_min }))
362}
363
364pub fn parse_g_month(s: &str) -> Result<Value, TypeError> {
365 let mut c = Cur::new(s);
366 c.expect(b'-')?; c.expect(b'-')?;
367 let month = read_month(&mut c)?;
368 if c.peek() == Some(b'-') && c.s.get(c.i + 1).copied() == Some(b'-') {
373 c.bump(); c.bump();
374 }
375 let tz_min = read_tz(&mut c)?;
376 finish(&mut c)?;
377 Ok(Value::GMonth(XsdGMonth { month, tz_min }))
378}
379
380pub fn parse_duration(s: &str) -> Result<Value, TypeError> {
383 let mut c = Cur::new(s);
384 let neg = c.peek() == Some(b'-');
385 if neg { c.bump(); }
386 c.expect(b'P')?;
387
388 let mut years = 0i64;
389 let mut months = 0i64;
390 let mut days = 0i64;
391 let mut hours = 0i64;
392 let mut mins = 0i64;
393 let mut secs = 0i64;
394 let mut nanos = 0u32;
395
396 let mut saw_any_date = false;
397 let mut saw_any_time = false;
398
399 while let Some(b) = c.peek() {
401 if b == b'T' { break; }
402 let n = c.read_digits(1, 18)?;
403 match c.bump() {
404 Some(b'Y') => { years = n; saw_any_date = true; }
405 Some(b'M') => { months = n; saw_any_date = true; }
406 Some(b'D') => { days = n; saw_any_date = true; }
407 Some(b'T') | None => return Err(TypeError::type_mismatch(
408 "duration date part missing designator (Y/M/D)"
409 )),
410 Some(other) => return Err(TypeError::type_mismatch(
411 format!("unexpected {:?} in duration date part", other as char)
412 )),
413 }
414 }
415
416 if c.peek() == Some(b'T') {
418 c.bump();
419 if c.done() {
420 return Err(TypeError::type_mismatch(
421 "duration T must be followed by at least one time component"
422 ));
423 }
424 while !c.done() {
425 let n = c.read_digits(1, 18)?;
428 let frac_nanos = if c.peek() == Some(b'.') {
430 c.bump();
431 let start = c.i;
432 while c.i < c.s.len() && c.s[c.i].is_ascii_digit() { c.i += 1; }
433 let take = (c.i - start).min(9);
434 if take == 0 {
440 return Err(TypeError::type_mismatch(
441 "duration: '.' must be followed by at least one fraction digit",
442 ));
443 }
444 let bytes = &c.s[start..start + take];
445 let val: u32 = std::str::from_utf8(bytes).unwrap().parse().unwrap();
450 let scale = 10u32.pow(9 - take as u32);
451 val * scale
452 } else { 0 };
453 match c.bump() {
454 Some(b'H') => { hours = n; saw_any_time = true; }
455 Some(b'M') => { mins = n; saw_any_time = true; }
456 Some(b'S') => { secs = n; nanos = frac_nanos; saw_any_time = true; }
457 Some(other) => return Err(TypeError::type_mismatch(
458 format!("unexpected {:?} in duration time part", other as char)
459 )),
460 None => return Err(TypeError::type_mismatch(
461 "duration time component missing designator"
462 )),
463 }
464 }
465 }
466
467 if !saw_any_date && !saw_any_time {
468 return Err(TypeError::type_mismatch("empty duration"));
469 }
470
471 let total_months = years.saturating_mul(12).saturating_add(months);
472 let total_seconds = days.saturating_mul(86400)
473 .saturating_add(hours.saturating_mul(3600))
474 .saturating_add(mins.saturating_mul(60))
475 .saturating_add(secs);
476
477 let (m, s, ns) = if neg {
478 (-total_months, -total_seconds, nanos)
479 } else {
480 (total_months, total_seconds, nanos)
481 };
482
483 Ok(Value::Duration(XsdDuration { months: m, seconds: s, nanos: ns }))
484}
485
486impl XsdDateTime {
489 pub fn to_utc_seconds(&self) -> Option<(i128, u32)> {
493 let tz = self.tz_min.unwrap_or(0);
494 let base = days_from_civil(self.year, self.month, self.day);
495 let mut seconds = (base as i128) * 86400
496 + (self.hour as i128) * 3600
497 + (self.minute as i128) * 60
498 + self.second as i128;
499 seconds -= (tz as i128) * 60;
500 Some((seconds, self.nanos))
501 }
502}
503
504impl Ord for XsdDateTime {
505 fn cmp(&self, other: &Self) -> Ordering {
506 match (self.to_utc_seconds(), other.to_utc_seconds()) {
507 (Some(a), Some(b)) => a.cmp(&b),
508 _ => Ordering::Equal,
509 }
510 }
511}
512impl PartialOrd for XsdDateTime { fn partial_cmp(&self, o: &Self) -> Option<Ordering> { Some(self.cmp(o)) } }
513
514fn days_from_civil(y: i32, m: u8, d: u8) -> i64 {
517 let y = y as i64 - if m <= 2 { 1 } else { 0 };
518 let era = if y >= 0 { y } else { y - 399 } / 400;
519 let yoe = (y - era * 400) as i64; let m = m as i64;
521 let d = d as i64;
522 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
523 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
524 era * 146097 + doe - 719468
525}
526
527#[cfg(test)]
530mod tests {
531 use super::*;
532
533 fn ok<T>(r: Result<T, TypeError>) -> T {
534 r.unwrap_or_else(|e| panic!("{e:?}"))
535 }
536
537 #[test]
540 fn datetime_basic_no_tz() {
541 let v = ok(parse_date_time("2024-03-15T10:30:00"));
542 match v {
543 Value::DateTime(d) => {
544 assert_eq!((d.year, d.month, d.day), (2024, 3, 15));
545 assert_eq!((d.hour, d.minute, d.second), (10, 30, 0));
546 assert_eq!(d.tz_min, None);
547 }
548 _ => panic!(),
549 }
550 }
551
552 #[test]
553 fn datetime_with_z() {
554 let v = ok(parse_date_time("2024-03-15T10:30:00Z"));
555 match v {
556 Value::DateTime(d) => assert_eq!(d.tz_min, Some(0)),
557 _ => panic!(),
558 }
559 }
560
561 #[test]
562 fn datetime_with_offset() {
563 let v = ok(parse_date_time("2024-03-15T10:30:00-05:00"));
564 match v {
565 Value::DateTime(d) => assert_eq!(d.tz_min, Some(-300)),
566 _ => panic!(),
567 }
568 }
569
570 #[test]
571 fn datetime_with_fractional_seconds() {
572 let v = ok(parse_date_time("2024-03-15T10:30:45.123Z"));
573 match v {
574 Value::DateTime(d) => assert_eq!(d.nanos, 123_000_000),
575 _ => panic!(),
576 }
577 }
578
579 #[test]
580 fn datetime_negative_year_allowed() {
581 assert!(parse_date_time("-0044-03-15T00:00:00Z").is_ok());
582 }
583
584 #[test]
585 fn datetime_year_zero_rejected() {
586 assert!(parse_date_time("0000-01-01T00:00:00Z").is_err());
587 }
588
589 #[test]
590 fn datetime_extended_year() {
591 assert!(parse_date_time("12345-01-01T00:00:00Z").is_ok());
592 assert!(parse_date_time("01234-01-01T00:00:00Z").is_err());
594 }
595
596 #[test]
597 fn datetime_24_00_00_allowed() {
598 assert!(parse_date_time("2024-03-15T24:00:00Z").is_ok());
599 assert!(parse_date_time("2024-03-15T24:30:00Z").is_err());
601 }
602
603 #[test]
604 fn datetime_invalid_day() {
605 assert!(parse_date_time("2024-02-30T00:00:00Z").is_err());
606 assert!(parse_date_time("2023-02-29T00:00:00Z").is_err()); assert!(parse_date_time("2024-02-29T00:00:00Z").is_ok()); }
609
610 #[test]
611 fn datetime_tz_range() {
612 assert!(parse_date_time("2024-01-01T00:00:00+14:00").is_ok());
613 assert!(parse_date_time("2024-01-01T00:00:00-14:00").is_ok());
614 assert!(parse_date_time("2024-01-01T00:00:00+15:00").is_err());
615 }
616
617 #[test]
620 fn date_basic() {
621 ok(parse_date("2024-03-15"));
622 ok(parse_date("2024-03-15Z"));
623 ok(parse_date("2024-03-15-08:00"));
624 }
625
626 #[test]
627 fn date_rejects_time_part() {
628 assert!(parse_date("2024-03-15T00:00:00").is_err());
629 }
630
631 #[test]
634 fn time_basic() {
635 ok(parse_time("10:30:00"));
636 ok(parse_time("23:59:59.999Z"));
637 }
638
639 #[test]
640 fn time_rejects_24_anything_but_zero() {
641 assert!(parse_time("24:00:00").is_ok());
642 assert!(parse_time("24:00:01").is_err());
643 assert!(parse_time("25:00:00").is_err());
644 }
645
646 #[test]
649 fn g_year_month() {
650 ok(parse_g_year_month("2024-03"));
651 ok(parse_g_year_month("2024-03Z"));
652 assert!(parse_g_year_month("2024-13").is_err());
653 }
654
655 #[test]
656 fn g_year() {
657 ok(parse_g_year("2024"));
658 ok(parse_g_year("-0044"));
659 ok(parse_g_year("12345Z"));
660 }
661
662 #[test]
663 fn g_month_day() {
664 ok(parse_g_month_day("--03-15"));
665 ok(parse_g_month_day("--02-29")); assert!(parse_g_month_day("03-15").is_err()); assert!(parse_g_month_day("--13-01").is_err()); }
669
670 #[test]
671 fn g_day() {
672 ok(parse_g_day("---15"));
673 ok(parse_g_day("---01Z"));
674 assert!(parse_g_day("--15").is_err()); }
676
677 #[test]
678 fn g_month() {
679 ok(parse_g_month("--12"));
680 ok(parse_g_month("--01-08:00"));
681 assert!(parse_g_month("12").is_err());
682 }
683
684 #[test]
687 fn duration_basic() {
688 let v = ok(parse_duration("P1Y2M3DT4H5M6S"));
689 match v {
690 Value::Duration(d) => {
691 assert_eq!(d.months, 14);
692 assert_eq!(d.seconds, 3*86400 + 4*3600 + 5*60 + 6);
693 assert_eq!(d.nanos, 0);
694 }
695 _ => panic!(),
696 }
697 }
698
699 #[test]
700 fn duration_negative() {
701 let v = ok(parse_duration("-P1D"));
702 match v {
703 Value::Duration(d) => assert_eq!(d.seconds, -86400),
704 _ => panic!(),
705 }
706 }
707
708 #[test]
709 fn duration_partial() {
710 ok(parse_duration("PT1H"));
711 ok(parse_duration("P1Y"));
712 ok(parse_duration("PT0.5S"));
713 }
714
715 #[test]
716 fn duration_rejects_empty() {
717 assert!(parse_duration("P").is_err());
718 assert!(parse_duration("PT").is_err());
719 assert!(parse_duration("").is_err());
720 }
721
722 #[test]
723 fn duration_fractional_seconds() {
724 let v = ok(parse_duration("PT0.001S"));
725 match v {
726 Value::Duration(d) => {
727 assert_eq!(d.seconds, 0);
728 assert_eq!(d.nanos, 1_000_000);
729 }
730 _ => panic!(),
731 }
732 }
733
734 #[test]
737 fn datetime_ordering_with_tz() {
738 let earlier = match parse_date_time("2024-01-01T00:00:00Z").unwrap() {
739 Value::DateTime(d) => d, _ => unreachable!()
740 };
741 let later = match parse_date_time("2024-01-02T00:00:00Z").unwrap() {
742 Value::DateTime(d) => d, _ => unreachable!()
743 };
744 assert!(earlier < later);
745 }
746
747 #[test]
748 fn datetime_ordering_normalizes_tz() {
749 let utc_noon = match parse_date_time("2024-01-01T12:00:00Z").unwrap() {
750 Value::DateTime(d) => d, _ => unreachable!()
751 };
752 let other = match parse_date_time("2024-01-01T09:00:00-03:00").unwrap() {
754 Value::DateTime(d) => d, _ => unreachable!()
755 };
756 assert_eq!(utc_noon.cmp(&other), Ordering::Equal);
757 }
758}