Skip to main content

irox_time/format/
iso8601.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3
4//!
5//! Implementations of [`Format`] and [`FormatParser`] based on the ISO8601 specification
6//!
7
8extern 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    /// Formats this date as a extended ISO8601 Date & Time, `2023-12-31T05:10:25Z`
26    fn format_iso8601_extended(&self) -> String;
27
28    #[must_use]
29    /// Formats this date as a basic ISO8601 Date & Time, `20231231T051025Z`, suitable for filenames
30    fn format_iso8601_basic(&self) -> String;
31
32    /// Attempts to parse the provided string as either a [`crate::format::iso8601::BasicDateTimeOfDay`] or a [`crate::format::iso8601::ExtendedDateTimeFormat`]
33    fn try_from_iso8601(val: &str) -> Result<Self, FormatError>
34    where
35        Self: Sized;
36}
37
38///
39/// IS0 8601-1:2019 Basic Date and Time of Day Format, section 5.4.2 Equivalent to `YYYYMMddTHHmmssZ`/`20231231T051025Z`
40pub struct BasicDateTimeOfDay;
41
42///
43/// IS0 8601-1:2019 Basic Date and Time of Day Format, section 5.4.2 Equivalent to `YYYYMMddTHHmmssZ`/`20231231T051025Z`
44pub 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///
72/// IS0 8601-1:2019 Basic Calendar Date Format, of section 5.2.2. Equivalent to `YYYYMMdd`/`20231231`
73#[derive(Default, Debug, Copy, Clone)]
74pub struct BasicCalendarDate;
75
76///
77/// IS0 8601-1:2019 Basic Calendar Date Format, of section 5.2.2. Equivalent to `YYYYMMdd`/`20231231`
78pub 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            // assume year:dayofyear
101            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        // expecting a string of length 8
124
125        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///
166/// IS0 8601-1:2019 Basic Time Of Day Format, of section 5.3.3. Equivalent to `THHmmssZ`/`T051025Z`
167#[derive(Default, Debug, Copy, Clone)]
168pub struct BasicTimeOfDay;
169
170///
171/// IS0 8601-1:2019 Basic Time Of Day Format, of section 5.3.3. Equivalent to `THHmmssZ`/`T051025Z`
172pub 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
248/// IS0 8601-1:2019 Duration Format, of section 5.5.2. Equivalent to `PddDThhHmmMssS`/`P10DT05H10M25S`
249pub struct ISO8601Duration;
250
251/// IS0 8601-1:2019 Duration Format, of section 5.5.2. Equivalent to `PddDThhHmmMssS`/`P10DT05H10M25S`
252pub 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
297/// ISO 8601-1:2019 Extended Date Format, of section 5.2.2. Equivalent to `YYYY-mm-dd`/`2023-12-31`
298pub struct ExtendedDateFormat;
299
300/// ISO 8601-1:2019 Extended Date Format, of section 5.2.2. Equivalent to `YYYY-mm-dd`/`2023-12-31`
301pub 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            // assume that it's year-dayofyear.
326            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
342/// ISO 8601-1:2019 Extended Time Format, of section 5.3.3. Equivalent to `THH:mm:ssZ`/`T05:10:25Z`
343pub struct ExtendedTimeFormat;
344
345/// ISO 8601-1:2019 Extended Time Format, of section 5.3.3. Equivalent to `THH:mm:ssZ`/`T05:10:25Z`
346pub 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
399/// ISO 8601-1:2019 Extended Date Time Format, of section 5.4.2. Equivalent to `YYYY-MM-DDTHH:mm:ssZ`/`2023-12-31T05:10:25Z`
400pub struct ExtendedDateTimeFormat;
401/// ISO 8601-1:2019 Extended Date Time Format, of section 5.4.2. Equivalent to `YYYY-MM-DDTHH:mm:ssZ`/`2023-12-31T05:10:25Z`
402pub 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
429/// ISO 8601-1:2019 Date Time Format of section 5.4.2.  Will read either the basic or extended formats, produces the extended format.
430pub struct ISO8601DateTime;
431
432/// ISO 8601-1:2019 Date Time Format of section 5.4.2.  Will read either the basic or extended formats, produces the extended format.
433pub 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
450/// ISO 8601-1:2019 Date Format of section 5.2.2.  Will read either the basic or extended formats, produces the extended format.
451pub struct ISO8601Date;
452
453/// ISO 8601-1:2019 Date Format of section 5.2.2.  Will read either the basic or extended formats, produces the extended format.
454pub 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
472/// ISO 8601-1:2019 Time Format of section 5.3.3.  Will read either the basic or extended formats, produces the extended format.
473pub struct ISO8601Time;
474/// ISO 8601-1:2019 Time Format of section 5.3.3.  Will read either the basic or extended formats, produces the extended format.
475pub 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
486/// ISO 8601-1:2019 Basic Week Number format of section 5.2.4.2. Equivalent to: `YYYYWww`/`2023W52` (Week 52)
487pub struct ISO8601WeekNumber;
488/// ISO 8601-1:2019 Basic Week Number format of section 5.2.4.2. Equivalent to: `YYYYWww`/`2023W52` (Week 52)
489pub 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}