1//! Helpers for implementing formatting for ISO 8601.
2
3use std::io;
4
5use crate::convert::*;
6use crate::format_description::well_known::iso8601::{
7 DateKind, EncodedConfig, OffsetPrecision, TimePrecision,
8};
9use crate::format_description::well_known::Iso8601;
10use crate::formatting::{format_float, format_number_pad_zero, write, write_if, write_if_else};
11use crate::{error, Date, Time, UtcOffset};
12
13/// Format the date portion of ISO 8601.
14pub(super) fn format_date<const CONFIG: EncodedConfig>(
15 output: &mut impl io::Write,
16 date: Date,
17) -> Result<usize, error::Format> {
18 let mut bytes = 0;
19
20 match Iso8601::<CONFIG>::DATE_KIND {
21 DateKind::Calendar => {
22 let (year, month, day) = date.to_calendar_date();
23 if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
24 bytes += write_if_else(output, year < 0, b"-", b"+")?;
25 bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?;
26 } else if !(0..=9999).contains(&year) {
27 return Err(error::Format::InvalidComponent("year"));
28 } else {
29 bytes += format_number_pad_zero::<4>(output, year as u32)?;
30 }
31 bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
32 bytes += format_number_pad_zero::<2>(output, month as u8)?;
33 bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
34 bytes += format_number_pad_zero::<2>(output, day)?;
35 }
36 DateKind::Week => {
37 let (year, week, day) = date.to_iso_week_date();
38 if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
39 bytes += write_if_else(output, year < 0, b"-", b"+")?;
40 bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?;
41 } else if !(0..=9999).contains(&year) {
42 return Err(error::Format::InvalidComponent("year"));
43 } else {
44 bytes += format_number_pad_zero::<4>(output, year as u32)?;
45 }
46 bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-W", b"W")?;
47 bytes += format_number_pad_zero::<2>(output, week)?;
48 bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
49 bytes += format_number_pad_zero::<1>(output, day.number_from_monday())?;
50 }
51 DateKind::Ordinal => {
52 let (year, day) = date.to_ordinal_date();
53 if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
54 bytes += write_if_else(output, year < 0, b"-", b"+")?;
55 bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?;
56 } else if !(0..=9999).contains(&year) {
57 return Err(error::Format::InvalidComponent("year"));
58 } else {
59 bytes += format_number_pad_zero::<4>(output, year as u32)?;
60 }
61 bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
62 bytes += format_number_pad_zero::<3>(output, day)?;
63 }
64 }
65
66 Ok(bytes)
67}
68
69/// Format the time portion of ISO 8601.
70pub(super) fn format_time<const CONFIG: EncodedConfig>(
71 output: &mut impl io::Write,
72 time: Time,
73) -> Result<usize, error::Format> {
74 let mut bytes = 0;
75
76 // The "T" can only be omitted in extended format where there is no date being formatted.
77 bytes += write_if(
78 output,
79 Iso8601::<CONFIG>::USE_SEPARATORS || Iso8601::<CONFIG>::FORMAT_DATE,
80 b"T",
81 )?;
82
83 let (hours, minutes, seconds, nanoseconds) = time.as_hms_nano();
84
85 match Iso8601::<CONFIG>::TIME_PRECISION {
86 TimePrecision::Hour { decimal_digits } => {
87 let hours = (hours as f64)
88 + (minutes as f64) / Minute.per(Hour) as f64
89 + (seconds as f64) / Second.per(Hour) as f64
90 + (nanoseconds as f64) / Nanosecond.per(Hour) as f64;
91 format_float(output, hours, 2, decimal_digits)?;
92 }
93 TimePrecision::Minute { decimal_digits } => {
94 bytes += format_number_pad_zero::<2>(output, hours)?;
95 bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
96 let minutes = (minutes as f64)
97 + (seconds as f64) / Second.per(Minute) as f64
98 + (nanoseconds as f64) / Nanosecond.per(Minute) as f64;
99 bytes += format_float(output, minutes, 2, decimal_digits)?;
100 }
101 TimePrecision::Second { decimal_digits } => {
102 bytes += format_number_pad_zero::<2>(output, hours)?;
103 bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
104 bytes += format_number_pad_zero::<2>(output, minutes)?;
105 bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
106 let seconds = (seconds as f64) + (nanoseconds as f64) / Nanosecond.per(Second) as f64;
107 bytes += format_float(output, seconds, 2, decimal_digits)?;
108 }
109 }
110
111 Ok(bytes)
112}
113
114/// Format the UTC offset portion of ISO 8601.
115pub(super) fn format_offset<const CONFIG: EncodedConfig>(
116 output: &mut impl io::Write,
117 offset: UtcOffset,
118) -> Result<usize, error::Format> {
119 if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() {
120 return Ok(write(output, bytes:b"Z")?);
121 }
122
123 let mut bytes: usize = 0;
124
125 let (hours: i8, minutes: i8, seconds: i8) = offset.as_hms();
126 if seconds != 0 {
127 return Err(error::Format::InvalidComponent("offset_second"));
128 }
129 bytes += write_if_else(output, pred:offset.is_negative(), true_bytes:b"-", false_bytes:b"+")?;
130 bytes += format_number_pad_zero::<2>(output, value:hours.unsigned_abs())?;
131
132 if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes != 0 {
133 return Err(error::Format::InvalidComponent("offset_minute"));
134 } else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute {
135 bytes += write_if(output, pred:Iso8601::<CONFIG>::USE_SEPARATORS, bytes:b":")?;
136 bytes += format_number_pad_zero::<2>(output, value:minutes.unsigned_abs())?;
137 }
138
139 Ok(bytes)
140}
141