time/formatting/
iso8601.rs1use core::cmp::min;
4use std::io;
5
6use deranged::{ru8, ru16, ru32};
7use num_conv::prelude::*;
8
9use crate::error;
10use crate::format_description::modifier::Padding;
11use crate::format_description::well_known::Iso8601;
12use crate::format_description::well_known::iso8601::{
13 DateKind, EncodedConfig, OffsetPrecision, TimePrecision,
14};
15use crate::formatting::{ComponentProvider, Output};
16use crate::internal_macros::try_likely_ok;
17use crate::unit::*;
18
19impl<W> Output<W>
20where
21 W: io::Write + ?Sized,
22{
23 pub(super) fn format_iso8601_date<V, const CONFIG: EncodedConfig>(
25 &mut self,
26 value: &V,
27 state: &mut V::State,
28 ) -> Result<(), error::Format>
29 where
30 V: ComponentProvider,
31 {
32 match Iso8601::<CONFIG>::DATE_KIND {
33 DateKind::Calendar => {
34 let year = value.calendar_year(state).get();
35
36 if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
37 try_likely_ok!(self.write_if_else(year < 0, "-", "+"));
38 try_likely_ok!(self.format_six_digits_pad_zero(unsafe {
41 ru32::new_unchecked(year.unsigned_abs())
42 }));
43 } else {
44 let year = try_likely_ok!(
45 ru16::new(year.cast_unsigned().truncate())
46 .ok_or(error::Format::InvalidComponent("year"))
47 );
48 try_likely_ok!(self.format_four_digits_pad_zero(year));
49 }
50 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, "-"));
51 try_likely_ok!(self.format_two_digits(
52 unsafe { ru8::new_unchecked(u8::from(value.month(state))) },
54 Padding::Zero,
55 ));
56 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, "-"));
57 try_likely_ok!(self.format_two_digits(value.day(state).expand(), Padding::Zero));
58 }
59 DateKind::Week => {
60 let year = value.iso_year(state).get();
61
62 if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
63 try_likely_ok!(self.write_if_else(year < 0, "-", "+"));
64 try_likely_ok!(self.format_six_digits_pad_zero(unsafe {
67 ru32::new_unchecked(year.unsigned_abs())
68 }));
69 } else {
70 let year = try_likely_ok!(
71 ru16::new(year.cast_unsigned().truncate())
72 .ok_or(error::Format::InvalidComponent("year"))
73 );
74 try_likely_ok!(self.format_four_digits_pad_zero(year));
75 }
76 try_likely_ok!(self.write_if_else(Iso8601::<CONFIG>::USE_SEPARATORS, "-W", "W"));
77 try_likely_ok!(
78 self.format_two_digits(value.iso_week_number(state).expand(), Padding::Zero)
79 );
80 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, "-"));
81 try_likely_ok!(self.format_single_digit(unsafe {
83 ru8::new_unchecked(value.weekday(state).number_from_monday())
84 }));
85 }
86 DateKind::Ordinal => {
87 let year = value.calendar_year(state).get();
88
89 if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
90 try_likely_ok!(self.write_if_else(year < 0, "-", "+"));
91 try_likely_ok!(self.format_six_digits_pad_zero(unsafe {
94 ru32::new_unchecked(year.unsigned_abs())
95 }));
96 } else {
97 let year = try_likely_ok!(
98 ru16::new(year.cast_unsigned().truncate())
99 .ok_or(error::Format::InvalidComponent("year"))
100 );
101 try_likely_ok!(self.format_four_digits_pad_zero(year));
102 }
103 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, "-"));
104 try_likely_ok!(
105 self.format_three_digits(value.ordinal(state).expand(), Padding::Zero)
106 );
107 }
108 }
109
110 Ok(())
111 }
112
113 #[inline]
115 pub(super) fn format_iso8601_time<V, const CONFIG: EncodedConfig>(
116 &mut self,
117 value: &V,
118 state: &mut V::State,
119 ) -> Result<(), error::Format>
120 where
121 V: ComponentProvider,
122 {
123 try_likely_ok!(self.write_if(
125 !Iso8601::<CONFIG>::USE_SEPARATORS || Iso8601::<CONFIG>::FORMAT_DATE,
126 "T",
127 ));
128
129 match Iso8601::<CONFIG>::TIME_PRECISION {
130 TimePrecision::Hour { decimal_digits } => {
131 let hours = (value.hour(state).get() as f64)
132 + (value.minute(state).get() as f64) / Minute::per_t::<f64>(Hour)
133 + (value.second(state).get() as f64) / Second::per_t::<f64>(Hour)
134 + (value.nanosecond(state).get() as f64) / Nanosecond::per_t::<f64>(Hour);
135 try_likely_ok!(self.format_float(hours, 2, decimal_digits));
136 }
137 TimePrecision::Minute { decimal_digits } => {
138 try_likely_ok!(self.format_two_digits(value.hour(state).expand(), Padding::Zero));
139 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, ":"));
140 let minutes = (value.minute(state).get() as f64)
141 + (value.second(state).get() as f64) / Second::per_t::<f64>(Minute)
142 + (value.nanosecond(state).get() as f64) / Nanosecond::per_t::<f64>(Minute);
143 try_likely_ok!(self.format_float(minutes, 2, decimal_digits));
144 }
145 TimePrecision::Second { decimal_digits } => {
146 try_likely_ok!(self.format_two_digits(value.hour(state).expand(), Padding::Zero));
147 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, ":"));
148 try_likely_ok!(self.format_two_digits(value.minute(state).expand(), Padding::Zero));
149 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, ":"));
150 try_likely_ok!(self.format_two_digits(value.second(state).expand(), Padding::Zero));
151 if let Some(digits) = decimal_digits {
152 const POW_TABLE: [u64; 9] = [
153 1,
154 10,
155 100,
156 1_000,
157 10_000,
158 100_000,
159 1_000_000,
160 10_000_000,
161 100_000_000,
162 ];
163
164 try_likely_ok!(self.write("."));
165 let nano = value.nanosecond(state).get() as u64;
166 let sub_digits = min(digits.get(), 9);
167 let truncated = nano / POW_TABLE[9 - sub_digits as usize];
168 try_likely_ok!(self.format_int_padded(truncated, sub_digits));
169 for _ in 9..digits.get() {
170 try_likely_ok!(self.write("0"));
171 }
172 }
173 }
174 }
175
176 Ok(())
177 }
178
179 #[inline]
181 pub(super) fn format_iso8601_offset<V, const CONFIG: EncodedConfig>(
182 &mut self,
183 value: &V,
184 state: &mut V::State,
185 ) -> Result<(), error::Format>
186 where
187 V: ComponentProvider,
188 {
189 if Iso8601::<CONFIG>::FORMAT_TIME && value.offset_is_utc(state) {
190 return self.write("Z").map_err(Into::into);
191 }
192
193 if value.offset_second(state).get() != 0 {
194 return Err(error::Format::InvalidComponent("offset_second"));
195 }
196 try_likely_ok!(self.write_if_else(value.offset_is_negative(state), "-", "+"));
197 try_likely_ok!(self.format_two_digits(
198 unsafe { ru8::new_unchecked(value.offset_hour(state).get().unsigned_abs()) },
200 Padding::Zero,
201 ));
202
203 let minutes = value.offset_minute(state);
204
205 if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes.get() != 0 {
206 return Err(error::Format::InvalidComponent("offset_minute"));
207 } else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute {
208 try_likely_ok!(self.write_if(Iso8601::<CONFIG>::USE_SEPARATORS, ":"));
209 try_likely_ok!(self.format_two_digits(
210 unsafe { ru8::new_unchecked(minutes.get().unsigned_abs()) },
212 Padding::Zero,
213 ));
214 }
215
216 Ok(())
217 }
218}