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