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