1extern crate alloc;
9use alloc::string::String;
10
11use core::str::FromStr;
12
13use irox_tools::fmt::DecimalFormatF64;
14use irox_tools::format;
15use irox_tools::iterators::Itertools;
16use irox_units::units::duration::{Duration, SEC_TO_NANOS};
17
18use crate::datetime::UTCDateTime;
19use crate::format::{Format, FormatError, FormatParser};
20use crate::gregorian::Date;
21use crate::Time;
22
23pub trait ISO8601Format {
24 #[must_use]
25 fn format_iso8601_extended(&self) -> String;
27
28 #[must_use]
29 fn format_iso8601_basic(&self) -> String;
31
32 fn try_from_iso8601(val: &str) -> Result<Self, FormatError>
34 where
35 Self: Sized;
36}
37
38pub struct BasicDateTimeOfDay;
41
42pub const BASIC_DATE_TIME_OF_DAY: BasicDateTimeOfDay = BasicDateTimeOfDay {};
45
46impl Format<UTCDateTime> for BasicDateTimeOfDay {
47 fn format(&self, date: &UTCDateTime) -> String {
48 format!(
49 "{}{}",
50 BasicCalendarDate::format(&date.get_date()),
51 BasicTimeOfDay::format(&date.get_time())
52 )
53 }
54}
55
56impl FormatParser<UTCDateTime> for BasicDateTimeOfDay {
57 fn try_from(&self, data: &str) -> Result<UTCDateTime, FormatError> {
58 let mut iter = data.split(&['T', 't', '_', ' ']);
59 let Some(date) = iter.next() else {
60 return FormatError::err_str("Expecting date portion");
61 };
62 let Some(time) = iter.next() else {
63 return FormatError::err_str("Expecting time portion");
64 };
65 let date = Date::parse_from(&BASIC_CALENDAR_DATE, date)?;
66 let time = Time::parse_from(&BASIC_TIME_OF_DAY, time)?;
67 Ok(UTCDateTime { date, time })
68 }
69}
70
71#[derive(Default, Debug, Copy, Clone)]
74pub struct BasicCalendarDate;
75
76pub const BASIC_CALENDAR_DATE: BasicCalendarDate = BasicCalendarDate {};
79
80impl BasicCalendarDate {
81 pub fn format(date: &Date) -> String {
82 BasicCalendarDate.format(date)
83 }
84}
85
86impl Format<Date> for BasicCalendarDate {
87 fn format(&self, date: &Date) -> String {
88 format!(
89 "{:04}{:02}{:02}",
90 date.year(),
91 date.month_of_year() as u8,
92 date.day_of_month() + 1,
93 )
94 }
95}
96
97impl FormatParser<Date> for BasicCalendarDate {
98 fn try_from(&self, data: &str) -> Result<Date, FormatError> {
99 if data.len() < 8 {
100 let mut iter = data.chars();
102 let year_str = iter.collect_next_chunk(4);
103 let day_str = iter.collect_next_chunk(3);
104 if year_str.len() != 4 {
105 return FormatError::err(format!(
106 "Expecting year to be length 4, but was {}",
107 year_str.len()
108 ));
109 }
110 if day_str.len() != 3 {
111 return FormatError::err(format!(
112 "Expecting day to be length 3, but was {}",
113 day_str.len()
114 ));
115 }
116 let year_str = String::from_iter(year_str);
117 let year = i32::from_str(&year_str)?;
118 let day_str = String::from_iter(day_str);
119 let day = u16::from_str(&day_str)?;
120
121 return Ok(Date::new(year, day - 1)?);
122 }
123 let mut iter = data.chars();
126 let year_str = iter.collect_next_chunk(4);
127 let month_str = iter.collect_next_chunk(2);
128 let day_str = iter.collect_next_chunk(2);
129
130 if year_str.len() != 4 {
131 return FormatError::err(format!(
132 "Expecting year to be length 4, but was {}",
133 year_str.len()
134 ));
135 }
136 if month_str.len() != 2 {
137 return FormatError::err(format!(
138 "Expecting month to be length 2, but was {}",
139 month_str.len()
140 ));
141 }
142 if day_str.len() != 2 {
143 return FormatError::err(format!(
144 "Expecting day to be length 2, but was {}",
145 day_str.len()
146 ));
147 }
148 let year_str = String::from_iter(year_str);
149 let year = i32::from_str(&year_str)?;
150 let month_str = String::from_iter(month_str);
151 let month = u8::from_str(&month_str)?;
152 let day_str = String::from_iter(day_str);
153 let day = u8::from_str(&day_str)?;
154
155 Ok(Date::try_from_values(year, month, day)?)
156 }
157}
158
159impl Format<UTCDateTime> for BasicCalendarDate {
160 fn format(&self, date: &UTCDateTime) -> String {
161 BasicCalendarDate.format(&date.date)
162 }
163}
164
165#[derive(Default, Debug, Copy, Clone)]
168pub struct BasicTimeOfDay;
169
170pub const BASIC_TIME_OF_DAY: BasicTimeOfDay = BasicTimeOfDay {};
173
174impl Format<Time> for BasicTimeOfDay {
175 fn format(&self, date: &Time) -> String {
176 let (h, m, s) = date.as_hms();
177 if date.nanoseconds == 0 {
178 format!("T{h:02}{m:02}{s:02}Z")
179 } else {
180 let s = s as f64 + date.get_secondsfrac();
181 format!("T{h:02}{m:02}{}", DecimalFormatF64(2, 9, s))
182 }
183 }
184}
185
186impl Format<UTCDateTime> for BasicTimeOfDay {
187 fn format(&self, date: &UTCDateTime) -> String {
188 BasicTimeOfDay::format(&date.time)
189 }
190}
191
192impl FormatParser<Time> for BasicTimeOfDay {
193 fn try_from(&self, data: &str) -> Result<Time, FormatError> {
194 let data = if data.ends_with(['z', 'Z']) {
195 data.split_at(data.len() - 1).0
196 } else {
197 data
198 };
199 let data = if data.starts_with(['T', 't']) {
200 data.split_at(1).1
201 } else {
202 data
203 };
204 let mut iter = data.chars();
205 let hour_string = iter.collect_next_chunk(2);
206 let minute_string = iter.collect_next_chunk(2);
207 let second_string = iter.collect_next_chunk(10);
208
209 if hour_string.len() != 2 {
210 return FormatError::err(format!(
211 "Expecting hours to be length 2, but was {}",
212 hour_string.len()
213 ));
214 }
215 if minute_string.len() != 2 {
216 return FormatError::err(format!(
217 "Expecting minutes to be length 2, but was {}",
218 minute_string.len()
219 ));
220 }
221 if second_string.len() < 2 {
222 return FormatError::err(format!(
223 "Expecting seconds to be at least length 2, but was {}",
224 second_string.len()
225 ));
226 }
227
228 let hours = u32::from_str(String::from_iter(hour_string).as_str())?;
229 let minutes = u32::from_str(String::from_iter(minute_string).as_str())?;
230 let seconds = f64::from_str(String::from_iter(second_string).as_str())?;
231
232 let second_of_day = hours * 3600 + minutes * 60 + seconds as u32;
233 let nanoseconds = (irox_tools::f64::FloatExt::fract(seconds) * SEC_TO_NANOS) as u32;
234
235 Ok(Time {
236 second_of_day,
237 nanoseconds,
238 })
239 }
240}
241
242impl BasicTimeOfDay {
243 pub fn format(time: &Time) -> String {
244 BasicTimeOfDay.format(time)
245 }
246}
247
248pub struct ISO8601Duration;
250
251pub const DURATION: ISO8601Duration = ISO8601Duration;
253
254impl Format<Duration> for ISO8601Duration {
255 fn format(&self, date: &Duration) -> String {
256 let (days, hours, minutes, seconds) = date.as_dhms();
257 if days > 0 {
258 return format!("P{days}DT{hours:02}H{minutes:02}M{seconds:02}S");
259 }
260 if hours > 0 {
261 return format!("PT{hours}H{minutes:02}M{seconds:02}S");
262 }
263 if minutes > 0 {
264 return format!("PT{minutes}M{seconds:02}S");
265 }
266 format!("PT{seconds}S")
267 }
268}
269impl FormatParser<Duration> for ISO8601Duration {
270 fn try_from(&self, data: &str) -> Result<Duration, FormatError> {
271 if !data.starts_with('P') {
272 return FormatError::err_str("Duration must start with 'P'");
273 }
274 let (_, _data) = data.split_at(1);
275
276 todo!()
277 }
278}
279
280impl ISO8601Format for Duration {
281 fn format_iso8601_extended(&self) -> String {
282 DURATION.format(self)
283 }
284
285 fn format_iso8601_basic(&self) -> String {
286 DURATION.format(self)
287 }
288
289 fn try_from_iso8601(val: &str) -> Result<Self, FormatError>
290 where
291 Self: Sized,
292 {
293 DURATION.try_from(val)
294 }
295}
296
297pub struct ExtendedDateFormat;
299
300pub const EXTENDED_DATE_FORMAT: ExtendedDateFormat = ExtendedDateFormat;
302
303impl Format<Date> for ExtendedDateFormat {
304 fn format(&self, date: &Date) -> String {
305 format!(
306 "{:04}-{:02}-{:02}",
307 date.year(),
308 date.month_of_year() as u8,
309 date.day_of_month() + 1,
310 )
311 }
312}
313
314impl FormatParser<Date> for ExtendedDateFormat {
315 fn try_from(&self, data: &str) -> Result<Date, FormatError> {
316 let mut splits = data.split('-');
317 let Some(year) = splits.next() else {
318 return FormatError::err_str("Expecting first part to be year, but didn't exist.");
319 };
320 let year = i32::from_str(year)?;
321 let Some(month) = splits.next() else {
322 return FormatError::err_str("Expecting second part, but didn't exist.");
323 };
324 let Some(day) = splits.next() else {
325 let day = u16::from_str(month)?;
327 return Ok(Date::new(year, day - 1)?);
328 };
329 let month = u8::from_str(month)?;
330 let day = u8::from_str(day)?;
331 let date = Date::try_from_values(year, month, day)?;
332 Ok(date)
333 }
334}
335
336impl Format<UTCDateTime> for ExtendedDateFormat {
337 fn format(&self, date: &UTCDateTime) -> String {
338 ExtendedDateFormat.format(&date.date)
339 }
340}
341
342pub struct ExtendedTimeFormat;
344
345pub const EXTENDED_TIME_FORMAT: ExtendedTimeFormat = ExtendedTimeFormat;
347
348impl Format<Time> for ExtendedTimeFormat {
349 fn format(&self, date: &Time) -> String {
350 let (h, m, s) = date.as_hms();
351 if date.nanoseconds == 0 {
352 format!("T{h:02}:{m:02}:{s:02}Z")
353 } else {
354 let s = s as f64 + date.get_secondsfrac();
355 format!("T{h:02}:{m:02}:{}Z", DecimalFormatF64(2, 9, s))
356 }
357 }
358}
359
360impl Format<UTCDateTime> for ExtendedTimeFormat {
361 fn format(&self, date: &UTCDateTime) -> String {
362 ExtendedTimeFormat.format(&date.time)
363 }
364}
365
366impl FormatParser<Time> for ExtendedTimeFormat {
367 fn try_from(&self, data: &str) -> Result<Time, FormatError> {
368 let data = if data.starts_with(['T', 't']) {
369 data.split_at(1).1
370 } else {
371 data
372 };
373 let data = if data.ends_with(['z', 'Z']) {
374 data.split_at(data.len() - 1).0
375 } else {
376 data
377 };
378 let mut split = data.split(':');
379 let Some(hour) = split.next() else {
380 return FormatError::err_str("Expecting first part.");
381 };
382 let Some(minute) = split.next() else {
383 return FormatError::err_str("Expecting second part.");
384 };
385 let Some(second) = split.next() else {
386 return FormatError::err_str("Expecting third part.");
387 };
388 let Some(second) = second.split(['-', '+']).next() else {
389 return FormatError::err_str("Expecting to remove TZ info");
390 };
391 let hours = u8::from_str(hour)?;
392 let minutes = u8::from_str(minute)?;
393 let seconds = f64::from_str(second)?;
394 let time = Time::from_hms_f64(hours, minutes, seconds)?;
395 Ok(time)
396 }
397}
398
399pub struct ExtendedDateTimeFormat;
401pub const EXTENDED_DATE_TIME_FORMAT: ExtendedDateTimeFormat = ExtendedDateTimeFormat;
403
404impl Format<UTCDateTime> for ExtendedDateTimeFormat {
405 fn format(&self, date: &UTCDateTime) -> String {
406 format!(
407 "{}{}",
408 ExtendedDateFormat.format(&date.get_date()),
409 ExtendedTimeFormat.format(&date.get_time())
410 )
411 }
412}
413
414impl FormatParser<UTCDateTime> for ExtendedDateTimeFormat {
415 fn try_from(&self, data: &str) -> Result<UTCDateTime, FormatError> {
416 let mut split = data.split(['T', 't', '_', ' ']);
417 let Some(date) = split.next() else {
418 return FormatError::err_str("Missing date.");
419 };
420 let Some(time) = split.next() else {
421 return FormatError::err_str("Missing time.");
422 };
423 let date = ExtendedDateFormat.try_from(date)?;
424 let time = ExtendedTimeFormat.try_from(time)?;
425 Ok(UTCDateTime::new(date, time))
426 }
427}
428
429pub struct ISO8601DateTime;
431
432pub const ISO8601_DATE_TIME: ISO8601DateTime = ISO8601DateTime;
434
435impl FormatParser<UTCDateTime> for ISO8601DateTime {
436 fn try_from(&self, data: &str) -> Result<UTCDateTime, FormatError> {
437 if data.contains(':') {
438 ExtendedDateTimeFormat.try_from(data)
439 } else {
440 BasicDateTimeOfDay.try_from(data)
441 }
442 }
443}
444impl Format<UTCDateTime> for ISO8601DateTime {
445 fn format(&self, date: &UTCDateTime) -> String {
446 ExtendedDateTimeFormat.format(date)
447 }
448}
449
450pub struct ISO8601Date;
452
453pub const ISO8601_DATE: ISO8601Date = ISO8601Date;
455
456impl FormatParser<Date> for ISO8601Date {
457 fn try_from(&self, data: &str) -> Result<Date, FormatError> {
458 if data.contains('-') {
459 ExtendedDateFormat.try_from(data)
460 } else {
461 BasicCalendarDate.try_from(data)
462 }
463 }
464}
465
466impl Format<Date> for ISO8601Date {
467 fn format(&self, date: &Date) -> String {
468 ExtendedDateFormat.format(date)
469 }
470}
471
472pub struct ISO8601Time;
474pub const ISO8601_TIME: ISO8601Time = ISO8601Time;
476impl FormatParser<Time> for ISO8601Time {
477 fn try_from(&self, data: &str) -> Result<Time, FormatError> {
478 if data.contains(':') {
479 ExtendedTimeFormat.try_from(data)
480 } else {
481 BasicTimeOfDay.try_from(data)
482 }
483 }
484}
485
486pub struct ISO8601WeekNumber;
488pub const ISO8601_WEEK_NUMBER: ISO8601WeekNumber = ISO8601WeekNumber;
490impl Format<Date> for ISO8601WeekNumber {
491 fn format(&self, date: &Date) -> String {
492 let (year, wkno) = date.week_number();
493 format!("{year}W{wkno:02}")
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use alloc::vec;
500 use irox_tools::ansi_colors::{FORMAT_COLOR_FG_GREEN, FORMAT_COLOR_FG_RED, FORMAT_RESET};
501 use irox_tools::format;
502 use irox_units::bounds::GreaterThanEqualToValueError;
503
504 use crate::datetime::UTCDateTime;
505 use crate::epoch::{
506 COMMON_ERA_EPOCH, GPS_EPOCH, GREGORIAN_EPOCH, NTP_EPOCH, PRIME_EPOCH, UNIX_EPOCH,
507 WINDOWS_NT_EPOCH,
508 };
509 use crate::format::iso8601::{
510 ExtendedDateFormat, ExtendedDateTimeFormat, ExtendedTimeFormat, ISO8601Date,
511 ISO8601DateTime, ISO8601Time, BASIC_CALENDAR_DATE, BASIC_TIME_OF_DAY,
512 EXTENDED_DATE_TIME_FORMAT, ISO8601_DATE_TIME, ISO8601_WEEK_NUMBER,
513 };
514 use crate::format::{Format, FormatError, FormatParser};
515 use crate::gregorian::Date;
516 use crate::Time;
517
518 #[test]
519 pub fn test_basic_date() -> Result<(), FormatError> {
520 let tests = vec![
521 ("19700101", UNIX_EPOCH),
522 ("19800106", GPS_EPOCH),
523 ("19000101", NTP_EPOCH),
524 ("19000101", PRIME_EPOCH),
525 ("15821015", GREGORIAN_EPOCH),
526 ("00010101", COMMON_ERA_EPOCH),
527 ("16010101", WINDOWS_NT_EPOCH),
528 ];
529 for (string, format) in tests {
530 assert_eq!(
531 string,
532 format
533 .get_gregorian_date()
534 .format(&BASIC_CALENDAR_DATE)
535 .as_str()
536 );
537 assert_eq!(
538 format.get_gregorian_date(),
539 Date::parse_from(&BASIC_CALENDAR_DATE, string)?
540 );
541 }
542 Ok(())
543 }
544
545 #[test]
546 pub fn test_basic_time() -> Result<(), FormatError> {
547 for hour in 0..24 {
548 for minute in 0..60 {
549 for second in 0..60 {
550 let time_sec = hour * 3600 + minute * 60 + second;
551 let time = Time::new(time_sec, 0)?;
552
553 assert_eq!(
554 format!("T{hour:02}{minute:02}{second:02}Z"),
555 time.format(&BASIC_TIME_OF_DAY)
556 );
557 }
558 }
559 }
560 Ok(())
561 }
562
563 #[test]
564 pub fn test_basic_datetime() -> Result<(), FormatError> {
565 Ok(())
566 }
567
568 #[test]
569 pub fn test_extended_time() -> Result<(), FormatError> {
570 let time = Time::from_hms(23, 20, 30)?;
571
572 assert_eq!("T23:20:30Z", ExtendedTimeFormat.format(&time));
573 let parsed = ExtendedTimeFormat.try_from("T23:20:30Z")?;
574 assert_eq!(time, parsed);
575 let parsed = ExtendedTimeFormat.try_from("23:20:30Z")?;
576 assert_eq!(time, parsed);
577
578 Ok(())
579 }
580
581 #[test]
582 pub fn test_extended_date() -> Result<(), FormatError> {
583 let date = Date::try_from_values(1985, 04, 12)?;
584 assert_eq!("1985-04-12", ExtendedDateFormat.format(&date));
585
586 let parsed = ExtendedDateFormat.try_from("1985-04-12")?;
587 assert_eq!(date, parsed);
588
589 Ok(())
590 }
591
592 #[test]
593 pub fn test_extended_datetime() -> Result<(), FormatError> {
594 let date = Date::try_from_values(1985, 04, 12)?;
595 let time = Time::from_hms(23, 20, 30)?;
596 let dt = UTCDateTime::new(date, time);
597
598 assert_eq!("1985-04-12T23:20:30Z", ExtendedDateTimeFormat.format(&dt));
599
600 let parsed = ExtendedDateTimeFormat.try_from("1985-04-12T23:20:30Z")?;
601 assert_eq!(dt, parsed);
602
603 let parsed = ExtendedDateTimeFormat.try_from("1985-04-12T23:20:30")?;
604 assert_eq!(dt, parsed);
605
606 Ok(())
607 }
608
609 #[test]
610 pub fn test_both_formats() -> Result<(), FormatError> {
611 let date = Date::try_from_values(1985, 04, 12)?;
612 let time = Time::from_hms(23, 20, 30)?;
613 let dt = UTCDateTime::new(date, time);
614
615 let parsed = ISO8601DateTime.try_from("1985-04-12T23:20:30Z")?;
616 assert_eq!(dt, parsed);
617 let parsed = ISO8601DateTime.try_from("19850412T232030Z")?;
618 assert_eq!(dt, parsed);
619 let parsed = ISO8601DateTime.try_from("19850412T232030Z")?;
620 assert_eq!(dt, parsed);
621 Ok(())
622 }
623
624 macro_rules! run_cases {
625 ($cases:ident, $parser:ident) => {
626 let mut any_failures = false;
627 for case in $cases {
628 let res = $parser.try_from(case.0);
629 let res = match res {
630 Ok(res) => res,
631 Err(e) => {
632 println!(
633 "{}ERROR PARSING{} : {} // {e:?}",
634 FORMAT_COLOR_FG_RED, FORMAT_RESET, case.0
635 );
636 any_failures = true;
637 continue;
638 }
639 };
640 if res != case.1 {
641 println!(
642 "{}ERROR EQUALITY{}: {}, {} != {}",
643 FORMAT_COLOR_FG_RED, FORMAT_RESET, case.0, case.1, res
644 );
645 any_failures = true;
646 } else {
647 println!(
648 "{}PASSED{}: {}",
649 FORMAT_COLOR_FG_GREEN, FORMAT_RESET, case.0
650 );
651 }
652 }
653 assert_ne!(true, any_failures);
654 };
655 }
656
657 #[test]
658 pub fn compat_report_dates() -> Result<(), FormatError> {
659 let test_cases = [
660 ("2023-10-13", Date::try_from_values(2023, 10, 13)?),
661 ("2023-286", Date::new(2023, 285)?),
662 ("20231013", Date::try_from_values(2023, 10, 13)?),
663 ("2023286", Date::new(2023, 285)?),
664 ];
665
666 run_cases!(test_cases, ISO8601Date);
667
668 Ok(())
669 }
670
671 #[test]
672 pub fn compat_report_times() -> Result<(), FormatError> {
673 let test_cases = [
674 ("02:56:16Z", Time::from_hms(02, 56, 16)?),
675 ("02:56:16.6Z", Time::from_hms_f64(02, 56, 16.6)?),
676 ("02:56:16.61Z", Time::from_hms_f64(02, 56, 16.61)?),
677 ("02:56:16.615Z", Time::from_hms_f64(02, 56, 16.615)?),
678 ("02:56:16.615283Z", Time::from_hms_f64(02, 56, 16.615283)?),
679 ("22:56:16", Time::from_hms(22, 56, 16)?),
680 ("22:56:16.6", Time::from_hms_f64(22, 56, 16.6)?),
681 ("22:56:16.61", Time::from_hms_f64(22, 56, 16.61)?),
682 ("22:56:16.615", Time::from_hms_f64(22, 56, 16.615)?),
683 ("22:56:16.615283", Time::from_hms_f64(22, 56, 16.615283)?),
684 ("T22:56:16", Time::from_hms(22, 56, 16)?),
685 ("T22:56:16.6", Time::from_hms_f64(22, 56, 16.6)?),
686 ("T22:56:16.61", Time::from_hms_f64(22, 56, 16.61)?),
687 ("T22:56:16.615", Time::from_hms_f64(22, 56, 16.615)?),
688 ("T22:56:16.615283", Time::from_hms_f64(22, 56, 16.615283)?),
689 ("T02:56:16Z", Time::from_hms(02, 56, 16)?),
690 ("T02:56:16.6Z", Time::from_hms_f64(02, 56, 16.6)?),
691 ("T02:56:16.61Z", Time::from_hms_f64(02, 56, 16.61)?),
692 ("T02:56:16.615Z", Time::from_hms_f64(02, 56, 16.615)?),
693 ("T02:56:16.615283Z", Time::from_hms_f64(02, 56, 16.615283)?),
694 ("225616", Time::from_hms(22, 56, 16)?),
695 ("225616.6", Time::from_hms_f64(22, 56, 16.6)?),
696 ("225616.61", Time::from_hms_f64(22, 56, 16.61)?),
697 ("225616.615", Time::from_hms_f64(22, 56, 16.615)?),
698 ("225616.615283", Time::from_hms_f64(22, 56, 16.615283)?),
699 ("025616Z", Time::from_hms(02, 56, 16)?),
700 ("025616.6Z", Time::from_hms_f64(02, 56, 16.6)?),
701 ("025616.61Z", Time::from_hms_f64(02, 56, 16.61)?),
702 ("025616.615Z", Time::from_hms_f64(02, 56, 16.615)?),
703 ("025616.615283Z", Time::from_hms_f64(02, 56, 16.615283)?),
704 ("T225616", Time::from_hms(22, 56, 16)?),
705 ("T225616.6", Time::from_hms_f64(22, 56, 16.6)?),
706 ("T225616.61", Time::from_hms_f64(22, 56, 16.61)?),
707 ("T225616.615", Time::from_hms_f64(22, 56, 16.615)?),
708 ("T225616.615283", Time::from_hms_f64(22, 56, 16.615283)?),
709 ("T025616Z", Time::from_hms(02, 56, 16)?),
710 ("T025616.6Z", Time::from_hms_f64(02, 56, 16.6)?),
711 ("T025616.61Z", Time::from_hms_f64(02, 56, 16.61)?),
712 ("T025616.615Z", Time::from_hms_f64(02, 56, 16.615)?),
713 ("T025616.615283Z", Time::from_hms_f64(02, 56, 16.615283)?),
714 ];
715
716 run_cases!(test_cases, ISO8601Time);
717 Ok(())
718 }
719
720 #[test]
721 pub fn compat_report_datetime() -> Result<(), FormatError> {
722 let test_cases = [
723 (
724 "2023-10-14T02:56:16Z",
725 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
726 ),
727 (
728 "2023-10-14T02:56:16.6Z",
729 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.6)?,
730 ),
731 (
732 "2023-10-14T02:56:16.61Z",
733 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.61)?,
734 ),
735 (
736 "2023-10-14T02:56:16.615Z",
737 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
738 ),
739 (
740 "2023-10-14T02:56:16.615283Z",
741 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
742 ),
743 (
744 "2023-10-14t02:56:16z",
745 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
746 ),
747 (
748 "2023-10-14t02:56:16.615z",
749 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
750 ),
751 (
752 "2023-10-14 02:56:16Z",
753 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
754 ),
755 (
756 "2023-10-14_02:56:16Z",
757 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
758 ),
759 (
760 "2023-10-14 02:56:16z",
761 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
762 ),
763 (
764 "2023-10-14_02:56:16z",
765 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
766 ),
767 (
768 "2023-10-14 02:56:16.6Z",
769 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.6)?,
770 ),
771 (
772 "2023-10-14 02:56:16.61Z",
773 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.61)?,
774 ),
775 (
776 "2023-10-14 02:56:16.615Z",
777 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
778 ),
779 (
780 "2023-10-14_02:56:16.615Z",
781 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
782 ),
783 (
784 "2023-10-14 02:56:16.615283Z",
785 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
786 ),
787 (
788 "2023-10-14_02:56:16.615283Z",
789 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
790 ),
791 (
792 "2023-10-14 02:56:16.615z",
793 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
794 ),
795 (
796 "2023-10-14_02:56:16.615z",
797 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
798 ),
799 (
800 "2023-10-14 02:56:16.615283z",
801 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
802 ),
803 (
804 "2023-10-14_02:56:16.615283z",
805 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
806 ),
807 (
808 "2023-10-13T22:56:16",
809 UTCDateTime::try_from_values(2023, 10, 13, 22, 56, 16)?,
810 ),
811 (
812 "2023-10-13T22:56:16.6",
813 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.6)?,
814 ),
815 (
816 "2023-10-13T22:56:16.61",
817 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.61)?,
818 ),
819 (
820 "2023-10-13T22:56:16.615",
821 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615)?,
822 ),
823 (
824 "2023-10-13T22:56:16.615283",
825 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615283)?,
826 ),
827 (
828 "2023-286T22:56:16",
829 UTCDateTime::try_from_values(2023, 10, 13, 22, 56, 16)?,
830 ),
831 (
832 "2023-286T22:56:16.6",
833 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.6)?,
834 ),
835 (
836 "2023-286T22:56:16.61",
837 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.61)?,
838 ),
839 (
840 "2023-286T22:56:16.615",
841 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615)?,
842 ),
843 (
844 "2023-286T22:56:16.615283",
845 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615283)?,
846 ),
847 (
848 "2023-287T02:56:16Z",
849 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
850 ),
851 (
852 "2023-287T02:56:16.6Z",
853 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.6)?,
854 ),
855 (
856 "2023-287T02:56:16.61Z",
857 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.61)?,
858 ),
859 (
860 "2023-287T02:56:16.615Z",
861 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
862 ),
863 (
864 "2023-287T02:56:16.615283Z",
865 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
866 ),
867 (
868 "20231013T225616",
869 UTCDateTime::try_from_values(2023, 10, 13, 22, 56, 16)?,
870 ),
871 (
872 "20231013T225616.6",
873 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.6)?,
874 ),
875 (
876 "20231013T225616.61",
877 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.61)?,
878 ),
879 (
880 "20231013T225616.615",
881 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615)?,
882 ),
883 (
884 "20231013T225616.615283",
885 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615283)?,
886 ),
887 (
888 "20231014T025616Z",
889 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
890 ),
891 (
892 "20231014T025616.6Z",
893 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.6)?,
894 ),
895 (
896 "20231014T025616.61Z",
897 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.61)?,
898 ),
899 (
900 "20231014T025616.615Z",
901 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
902 ),
903 (
904 "20231014T025616.615283Z",
905 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
906 ),
907 (
908 "2023286T225616",
909 UTCDateTime::try_from_values(2023, 10, 13, 22, 56, 16)?,
910 ),
911 (
912 "2023286T225616.6",
913 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.6)?,
914 ),
915 (
916 "2023286T225616.61",
917 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.61)?,
918 ),
919 (
920 "2023286T225616.615",
921 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615)?,
922 ),
923 (
924 "2023286T225616.615283",
925 UTCDateTime::try_from_values_f64(2023, 10, 13, 22, 56, 16.615283)?,
926 ),
927 (
928 "2023287T025616Z",
929 UTCDateTime::try_from_values(2023, 10, 14, 02, 56, 16)?,
930 ),
931 (
932 "2023287T025616.6Z",
933 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.6)?,
934 ),
935 (
936 "2023287T025616.61Z",
937 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.61)?,
938 ),
939 (
940 "2023287T025616.615Z",
941 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615)?,
942 ),
943 (
944 "2023287T025616.615283Z",
945 UTCDateTime::try_from_values_f64(2023, 10, 14, 02, 56, 16.615283)?,
946 ),
947 ];
948
949 run_cases!(test_cases, ISO8601DateTime);
950
951 Ok(())
952 }
953
954 #[test]
955 pub fn test_leading_zeros() -> Result<(), FormatError> {
956 let time = UTCDateTime::try_from_values(2023, 01, 04, 01, 01, 01)?;
957 assert_eq!(
958 "2023-01-04T01:01:01Z",
959 format!("{}", time.format(&EXTENDED_DATE_TIME_FORMAT))
960 );
961
962 let time = UTCDateTime::try_from_values_f64(2023, 01, 04, 01, 01, 01.01)?;
963 assert_eq!(
964 "2023-01-04T01:01:01.010000000Z",
965 format!("{}", time.format(&EXTENDED_DATE_TIME_FORMAT))
966 );
967
968 Ok(())
969 }
970
971 #[test]
972 pub fn test_april_fools_day() -> Result<(), FormatError> {
973 let time = UTCDateTime::try_from_values(2023, 04, 01, 01, 01, 01)?;
974 assert_eq!(
975 "2023-04-01T01:01:01Z",
976 format!("{}", time.format(&EXTENDED_DATE_TIME_FORMAT))
977 );
978 let time = UTCDateTime::try_from_values(2023, 04, 02, 01, 01, 01)?;
979 assert_eq!(
980 "2023-04-02T01:01:01Z",
981 format!("{}", time.format(&EXTENDED_DATE_TIME_FORMAT))
982 );
983 let time = ISO8601_DATE_TIME.try_from("2023-04-01T01:01:01Z")?;
984 assert_eq!(
985 "2023-04-01T01:01:01Z",
986 time.format(&EXTENDED_DATE_TIME_FORMAT)
987 );
988
989 let time = UTCDateTime::try_from_values_f64(2023, 04, 01, 01, 01, 01.01)?;
990 assert_eq!(
991 "2023-04-01T01:01:01.010000000Z",
992 format!("{}", time.format(&EXTENDED_DATE_TIME_FORMAT))
993 );
994 Ok(())
995 }
996
997 #[test]
998 pub fn test_week_numbers() -> Result<(), GreaterThanEqualToValueError<u8>> {
999 let test_cases = vec![
1000 (Date::try_from_values(1977, 01, 01)?, "1976W53"),
1001 (Date::try_from_values(1977, 01, 02)?, "1976W53"),
1002 (Date::try_from_values(1977, 01, 03)?, "1977W01"),
1003 (Date::try_from_values(2000, 01, 02)?, "1999W52"),
1004 (Date::try_from_values(2000, 01, 03)?, "2000W01"),
1005 (Date::try_from_values(2000, 03, 05)?, "2000W09"),
1006 (Date::try_from_values(2000, 03, 06)?, "2000W10"),
1007 (Date::try_from_values(2000, 10, 29)?, "2000W43"),
1008 (Date::try_from_values(2000, 10, 30)?, "2000W44"),
1009 (Date::try_from_values(2019, 12, 29)?, "2019W52"),
1010 (Date::try_from_values(2019, 12, 30)?, "2020W01"),
1011 (Date::try_from_values(2019, 12, 31)?, "2020W01"),
1012 (Date::try_from_values(2020, 01, 01)?, "2020W01"),
1013 (Date::try_from_values(2020, 01, 06)?, "2020W02"),
1014 (Date::try_from_values(2021, 03, 31)?, "2021W13"),
1015 (Date::try_from_values(2021, 04, 01)?, "2021W13"),
1016 (Date::try_from_values(2021, 04, 04)?, "2021W13"),
1017 (Date::try_from_values(2021, 04, 05)?, "2021W14"),
1018 (Date::try_from_values(2023, 04, 28)?, "2023W17"),
1019 (Date::try_from_values(2023, 10, 31)?, "2023W44"),
1020 ];
1021 for (d, e) in test_cases {
1022 assert_eq!(e, d.format(&ISO8601_WEEK_NUMBER));
1023 }
1024 Ok(())
1025 }
1026}