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 struct BasicDateTimeOfDay;
26
27pub 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#[derive(Default, Debug, Copy, Clone)]
59pub struct BasicCalendarDate;
60
61pub 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 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 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#[derive(Default, Debug, Copy, Clone)]
153pub struct BasicTimeOfDay;
154
155pub 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
233pub struct ISO8601Duration;
235
236pub 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
255pub struct ExtendedDateFormat;
257
258pub 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 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
300pub struct ExtendedTimeFormat;
302
303pub 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
357pub struct ExtendedDateTimeFormat;
359pub 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
387pub struct ISO8601DateTime;
389
390pub 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
408pub struct ISO8601Date;
410
411pub 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
430pub struct ISO8601Time;
432pub 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
444pub struct ISO8601WeekNumber;
446pub 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}