1pub(crate) mod formattable;
4mod iso8601;
5use core::num::NonZeroU8;
6use std::io;
7
8use num_conv::prelude::*;
9
10pub use self::formattable::Formattable;
11use crate::convert::*;
12use crate::ext::DigitCount;
13use crate::format_description::{modifier, Component};
14use crate::{error, Date, OffsetDateTime, Time, UtcOffset};
15
16#[allow(clippy::missing_docs_in_private_items)]
17const MONTH_NAMES: [&[u8]; 12] = [
18 b"January",
19 b"February",
20 b"March",
21 b"April",
22 b"May",
23 b"June",
24 b"July",
25 b"August",
26 b"September",
27 b"October",
28 b"November",
29 b"December",
30];
31
32#[allow(clippy::missing_docs_in_private_items)]
33const WEEKDAY_NAMES: [&[u8]; 7] = [
34 b"Monday",
35 b"Tuesday",
36 b"Wednesday",
37 b"Thursday",
38 b"Friday",
39 b"Saturday",
40 b"Sunday",
41];
42
43pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
45 output.write_all(bytes)?;
46 Ok(bytes.len())
47}
48
49pub(crate) fn write_if(output: &mut impl io::Write, pred: bool, bytes: &[u8]) -> io::Result<usize> {
51 if pred { write(output, bytes) } else { Ok(0) }
52}
53
54pub(crate) fn write_if_else(
56 output: &mut impl io::Write,
57 pred: bool,
58 true_bytes: &[u8],
59 false_bytes: &[u8],
60) -> io::Result<usize> {
61 write(output, if pred { true_bytes } else { false_bytes })
62}
63
64pub(crate) fn format_float(
69 output: &mut impl io::Write,
70 value: f64,
71 digits_before_decimal: u8,
72 digits_after_decimal: Option<NonZeroU8>,
73) -> io::Result<usize> {
74 match digits_after_decimal {
75 Some(digits_after_decimal) => {
76 let digits_after_decimal = digits_after_decimal.get().extend();
77 let width = digits_before_decimal.extend::<usize>() + 1 + digits_after_decimal;
78 write!(output, "{value:0>width$.digits_after_decimal$}")?;
79 Ok(width)
80 }
81 None => {
82 let value = value.trunc() as u64;
83 let width = digits_before_decimal.extend();
84 write!(output, "{value:0>width$}")?;
85 Ok(width)
86 }
87 }
88}
89
90pub(crate) fn format_number<const WIDTH: u8>(
94 output: &mut impl io::Write,
95 value: impl itoa::Integer + DigitCount + Copy,
96 padding: modifier::Padding,
97) -> Result<usize, io::Error> {
98 match padding {
99 modifier::Padding::Space => format_number_pad_space::<WIDTH>(output, value),
100 modifier::Padding::Zero => format_number_pad_zero::<WIDTH>(output, value),
101 modifier::Padding::None => format_number_pad_none(output, value),
102 }
103}
104
105pub(crate) fn format_number_pad_space<const WIDTH: u8>(
109 output: &mut impl io::Write,
110 value: impl itoa::Integer + DigitCount + Copy,
111) -> Result<usize, io::Error> {
112 let mut bytes = 0;
113 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
114 bytes += write(output, b" ")?;
115 }
116 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
117 Ok(bytes)
118}
119
120pub(crate) fn format_number_pad_zero<const WIDTH: u8>(
124 output: &mut impl io::Write,
125 value: impl itoa::Integer + DigitCount + Copy,
126) -> Result<usize, io::Error> {
127 let mut bytes = 0;
128 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
129 bytes += write(output, b"0")?;
130 }
131 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
132 Ok(bytes)
133}
134
135pub(crate) fn format_number_pad_none(
139 output: &mut impl io::Write,
140 value: impl itoa::Integer + Copy,
141) -> Result<usize, io::Error> {
142 write(output, itoa::Buffer::new().format(value).as_bytes())
143}
144
145pub(crate) fn format_component(
149 output: &mut impl io::Write,
150 component: Component,
151 date: Option<Date>,
152 time: Option<Time>,
153 offset: Option<UtcOffset>,
154) -> Result<usize, error::Format> {
155 use Component::*;
156 Ok(match (component, date, time, offset) {
157 (Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?,
158 (Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?,
159 (Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?,
160 (Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?,
161 (WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?,
162 (Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?,
163 (Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?,
164 (Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?,
165 (Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?,
166 (Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?,
167 (Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?,
168 (OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?,
169 (OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?,
170 (OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?,
171 (Ignore(_), ..) => 0,
172 (UnixTimestamp(modifier), Some(date), Some(time), Some(offset)) => {
173 fmt_unix_timestamp(output, date, time, offset, modifier)?
174 }
175 (End(modifier::End {}), ..) => 0,
176
177 #[allow(unreachable_patterns)]
182 (
183 Day(_) | Month(_) | Ordinal(_) | Weekday(_) | WeekNumber(_) | Year(_) | Hour(_)
184 | Minute(_) | Period(_) | Second(_) | Subsecond(_) | OffsetHour(_) | OffsetMinute(_)
185 | OffsetSecond(_) | Ignore(_) | UnixTimestamp(_) | End(_),
186 ..,
187 ) => return Err(error::Format::InsufficientTypeInformation),
188 })
189}
190
191fn fmt_day(
194 output: &mut impl io::Write,
195 date: Date,
196 modifier::Day { padding }: modifier::Day,
197) -> Result<usize, io::Error> {
198 format_number::<2>(output, date.day(), padding)
199}
200
201fn fmt_month(
203 output: &mut impl io::Write,
204 date: Date,
205 modifier::Month {
206 padding,
207 repr,
208 case_sensitive: _, }: modifier::Month,
210) -> Result<usize, io::Error> {
211 match repr {
212 modifier::MonthRepr::Numerical => {
213 format_number::<2>(output, u8::from(date.month()), padding)
214 }
215 modifier::MonthRepr::Long => write(
216 output,
217 MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1],
218 ),
219 modifier::MonthRepr::Short => write(
220 output,
221 &MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1][..3],
222 ),
223 }
224}
225
226fn fmt_ordinal(
228 output: &mut impl io::Write,
229 date: Date,
230 modifier::Ordinal { padding }: modifier::Ordinal,
231) -> Result<usize, io::Error> {
232 format_number::<3>(output, date.ordinal(), padding)
233}
234
235fn fmt_weekday(
237 output: &mut impl io::Write,
238 date: Date,
239 modifier::Weekday {
240 repr,
241 one_indexed,
242 case_sensitive: _, }: modifier::Weekday,
244) -> Result<usize, io::Error> {
245 match repr {
246 modifier::WeekdayRepr::Short => write(
247 output,
248 &WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()][..3],
249 ),
250 modifier::WeekdayRepr::Long => write(
251 output,
252 WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()],
253 ),
254 modifier::WeekdayRepr::Sunday => format_number::<1>(
255 output,
256 date.weekday().number_days_from_sunday() + u8::from(one_indexed),
257 modifier::Padding::None,
258 ),
259 modifier::WeekdayRepr::Monday => format_number::<1>(
260 output,
261 date.weekday().number_days_from_monday() + u8::from(one_indexed),
262 modifier::Padding::None,
263 ),
264 }
265}
266
267fn fmt_week_number(
269 output: &mut impl io::Write,
270 date: Date,
271 modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
272) -> Result<usize, io::Error> {
273 format_number::<2>(
274 output,
275 match repr {
276 modifier::WeekNumberRepr::Iso => date.iso_week(),
277 modifier::WeekNumberRepr::Sunday => date.sunday_based_week(),
278 modifier::WeekNumberRepr::Monday => date.monday_based_week(),
279 },
280 padding,
281 )
282}
283
284fn fmt_year(
286 output: &mut impl io::Write,
287 date: Date,
288 modifier::Year {
289 padding,
290 repr,
291 iso_week_based,
292 sign_is_mandatory,
293 }: modifier::Year,
294) -> Result<usize, io::Error> {
295 let full_year = if iso_week_based {
296 date.iso_year_week().0
297 } else {
298 date.year()
299 };
300 let value = match repr {
301 modifier::YearRepr::Full => full_year,
302 modifier::YearRepr::LastTwo => (full_year % 100).abs(),
303 };
304 let format_number = match repr {
305 #[cfg(feature = "large-dates")]
306 modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6>,
307 #[cfg(feature = "large-dates")]
308 modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5>,
309 modifier::YearRepr::Full => format_number::<4>,
310 modifier::YearRepr::LastTwo => format_number::<2>,
311 };
312 let mut bytes = 0;
313 if repr != modifier::YearRepr::LastTwo {
314 if full_year < 0 {
315 bytes += write(output, b"-")?;
316 } else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
317 bytes += write(output, b"+")?;
318 }
319 }
320 bytes += format_number(output, value.unsigned_abs(), padding)?;
321 Ok(bytes)
322}
323fn fmt_hour(
328 output: &mut impl io::Write,
329 time: Time,
330 modifier::Hour {
331 padding,
332 is_12_hour_clock,
333 }: modifier::Hour,
334) -> Result<usize, io::Error> {
335 let value = match (time.hour(), is_12_hour_clock) {
336 (hour, false) => hour,
337 (0 | 12, true) => 12,
338 (hour, true) if hour < 12 => hour,
339 (hour, true) => hour - 12,
340 };
341 format_number::<2>(output, value, padding)
342}
343
344fn fmt_minute(
346 output: &mut impl io::Write,
347 time: Time,
348 modifier::Minute { padding }: modifier::Minute,
349) -> Result<usize, io::Error> {
350 format_number::<2>(output, time.minute(), padding)
351}
352
353fn fmt_period(
355 output: &mut impl io::Write,
356 time: Time,
357 modifier::Period {
358 is_uppercase,
359 case_sensitive: _, }: modifier::Period,
361) -> Result<usize, io::Error> {
362 match (time.hour() >= 12, is_uppercase) {
363 (false, false) => write(output, b"am"),
364 (false, true) => write(output, b"AM"),
365 (true, false) => write(output, b"pm"),
366 (true, true) => write(output, b"PM"),
367 }
368}
369
370fn fmt_second(
372 output: &mut impl io::Write,
373 time: Time,
374 modifier::Second { padding }: modifier::Second,
375) -> Result<usize, io::Error> {
376 format_number::<2>(output, time.second(), padding)
377}
378
379fn fmt_subsecond<W: io::Write>(
381 output: &mut W,
382 time: Time,
383 modifier::Subsecond { digits }: modifier::Subsecond,
384) -> Result<usize, io::Error> {
385 use modifier::SubsecondDigits::*;
386 let nanos = time.nanosecond();
387
388 if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) {
389 format_number_pad_zero::<9>(output, nanos)
390 } else if digits == Eight || (digits == OneOrMore && (nanos / 10) % 10 != 0) {
391 format_number_pad_zero::<8>(output, nanos / 10)
392 } else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) {
393 format_number_pad_zero::<7>(output, nanos / 100)
394 } else if digits == Six || (digits == OneOrMore && (nanos / 1_000) % 10 != 0) {
395 format_number_pad_zero::<6>(output, nanos / 1_000)
396 } else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) {
397 format_number_pad_zero::<5>(output, nanos / 10_000)
398 } else if digits == Four || (digits == OneOrMore && (nanos / 100_000) % 10 != 0) {
399 format_number_pad_zero::<4>(output, nanos / 100_000)
400 } else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) {
401 format_number_pad_zero::<3>(output, nanos / 1_000_000)
402 } else if digits == Two || (digits == OneOrMore && (nanos / 10_000_000) % 10 != 0) {
403 format_number_pad_zero::<2>(output, nanos / 10_000_000)
404 } else {
405 format_number_pad_zero::<1>(output, nanos / 100_000_000)
406 }
407}
408fn fmt_offset_hour(
413 output: &mut impl io::Write,
414 offset: UtcOffset,
415 modifier::OffsetHour {
416 padding,
417 sign_is_mandatory,
418 }: modifier::OffsetHour,
419) -> Result<usize, io::Error> {
420 let mut bytes = 0;
421 if offset.is_negative() {
422 bytes += write(output, b"-")?;
423 } else if sign_is_mandatory {
424 bytes += write(output, b"+")?;
425 }
426 bytes += format_number::<2>(output, offset.whole_hours().unsigned_abs(), padding)?;
427 Ok(bytes)
428}
429
430fn fmt_offset_minute(
432 output: &mut impl io::Write,
433 offset: UtcOffset,
434 modifier::OffsetMinute { padding }: modifier::OffsetMinute,
435) -> Result<usize, io::Error> {
436 format_number::<2>(output, offset.minutes_past_hour().unsigned_abs(), padding)
437}
438
439fn fmt_offset_second(
441 output: &mut impl io::Write,
442 offset: UtcOffset,
443 modifier::OffsetSecond { padding }: modifier::OffsetSecond,
444) -> Result<usize, io::Error> {
445 format_number::<2>(output, offset.seconds_past_minute().unsigned_abs(), padding)
446}
447fn fmt_unix_timestamp(
451 output: &mut impl io::Write,
452 date: Date,
453 time: Time,
454 offset: UtcOffset,
455 modifier::UnixTimestamp {
456 precision,
457 sign_is_mandatory,
458 }: modifier::UnixTimestamp,
459) -> Result<usize, io::Error> {
460 let date_time = OffsetDateTime::new_in_offset(date, time, offset).to_offset(UtcOffset::UTC);
461
462 if date_time < OffsetDateTime::UNIX_EPOCH {
463 write(output, b"-")?;
464 } else if sign_is_mandatory {
465 write(output, b"+")?;
466 }
467
468 match precision {
469 modifier::UnixTimestampPrecision::Second => {
470 format_number_pad_none(output, date_time.unix_timestamp().unsigned_abs())
471 }
472 modifier::UnixTimestampPrecision::Millisecond => format_number_pad_none(
473 output,
474 (date_time.unix_timestamp_nanos()
475 / Nanosecond::per(Millisecond).cast_signed().extend::<i128>())
476 .unsigned_abs(),
477 ),
478 modifier::UnixTimestampPrecision::Microsecond => format_number_pad_none(
479 output,
480 (date_time.unix_timestamp_nanos()
481 / Nanosecond::per(Microsecond).cast_signed().extend::<i128>())
482 .unsigned_abs(),
483 ),
484 modifier::UnixTimestampPrecision::Nanosecond => {
485 format_number_pad_none(output, date_time.unix_timestamp_nanos().unsigned_abs())
486 }
487 }
488}