1pub(crate) mod formattable;
4mod iso8601;
5
6use core::num::NonZero;
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::{Component, modifier};
15use crate::{Date, OffsetDateTime, Time, UtcOffset, error};
16
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
32const WEEKDAY_NAMES: [&[u8]; 7] = [
33 b"Monday",
34 b"Tuesday",
35 b"Wednesday",
36 b"Thursday",
37 b"Friday",
38 b"Saturday",
39 b"Sunday",
40];
41
42#[inline]
44pub(crate) fn write(output: &mut (impl io::Write + ?Sized), bytes: &[u8]) -> io::Result<usize> {
45 output.write_all(bytes)?;
46 Ok(bytes.len())
47}
48
49#[inline]
51pub(crate) fn write_if(
52 output: &mut (impl io::Write + ?Sized),
53 pred: bool,
54 bytes: &[u8],
55) -> io::Result<usize> {
56 if pred { write(output, bytes) } else { Ok(0) }
57}
58
59#[inline]
61pub(crate) fn write_if_else(
62 output: &mut (impl io::Write + ?Sized),
63 pred: bool,
64 true_bytes: &[u8],
65 false_bytes: &[u8],
66) -> io::Result<usize> {
67 write(output, if pred { true_bytes } else { false_bytes })
68}
69
70#[inline]
75fn f64_10_pow_x(x: NonZero<u8>) -> f64 {
76 match x.get() {
77 1 => 10.,
78 2 => 100.,
79 3 => 1_000.,
80 4 => 10_000.,
81 5 => 100_000.,
82 6 => 1_000_000.,
83 7 => 10_000_000.,
84 8 => 100_000_000.,
85 9 => 1_000_000_000.,
86 x => 10_f64.powi(x.cast_signed().extend()),
87 }
88}
89
90#[inline]
95pub(crate) fn format_float(
96 output: &mut (impl io::Write + ?Sized),
97 mut value: f64,
98 digits_before_decimal: u8,
99 digits_after_decimal: Option<NonZero<u8>>,
100) -> io::Result<usize> {
101 match digits_after_decimal {
102 Some(digits_after_decimal) => {
103 if digits_after_decimal.get() < 9 {
116 let trunc_num = f64_10_pow_x(digits_after_decimal);
117 value = f64::trunc(value * trunc_num) / trunc_num;
118 }
119
120 let digits_after_decimal = digits_after_decimal.get().extend();
121 let width = digits_before_decimal.extend::<usize>() + 1 + digits_after_decimal;
122 write!(output, "{value:0>width$.digits_after_decimal$}")?;
123 Ok(width)
124 }
125 None => {
126 let value = value.trunc() as u64;
127 let width = digits_before_decimal.extend();
128 write!(output, "{value:0>width$}")?;
129 Ok(width)
130 }
131 }
132}
133
134#[inline]
138pub(crate) fn format_number<const WIDTH: u8>(
139 output: &mut (impl io::Write + ?Sized),
140 value: impl itoa::Integer + DigitCount + Copy,
141 padding: modifier::Padding,
142) -> Result<usize, io::Error> {
143 match padding {
144 modifier::Padding::Space => format_number_pad_space::<WIDTH>(output, value),
145 modifier::Padding::Zero => format_number_pad_zero::<WIDTH>(output, value),
146 modifier::Padding::None => format_number_pad_none(output, value),
147 }
148}
149
150#[inline]
154pub(crate) fn format_number_pad_space<const WIDTH: u8>(
155 output: &mut (impl io::Write + ?Sized),
156 value: impl itoa::Integer + DigitCount + Copy,
157) -> Result<usize, io::Error> {
158 let mut bytes = 0;
159 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
160 bytes += write(output, b" ")?;
161 }
162 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
163 Ok(bytes)
164}
165
166#[inline]
170pub(crate) fn format_number_pad_zero<const WIDTH: u8>(
171 output: &mut (impl io::Write + ?Sized),
172 value: impl itoa::Integer + DigitCount + Copy,
173) -> Result<usize, io::Error> {
174 let mut bytes = 0;
175 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
176 bytes += write(output, b"0")?;
177 }
178 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
179 Ok(bytes)
180}
181
182#[inline]
186pub(crate) fn format_number_pad_none(
187 output: &mut (impl io::Write + ?Sized),
188 value: impl itoa::Integer + Copy,
189) -> Result<usize, io::Error> {
190 write(output, itoa::Buffer::new().format(value).as_bytes())
191}
192
193#[inline]
197pub(crate) fn format_component(
198 output: &mut (impl io::Write + ?Sized),
199 component: Component,
200 date: Option<Date>,
201 time: Option<Time>,
202 offset: Option<UtcOffset>,
203) -> Result<usize, error::Format> {
204 use Component::*;
205 Ok(match (component, date, time, offset) {
206 (Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?,
207 (Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?,
208 (Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?,
209 (Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?,
210 (WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?,
211 (Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?,
212 (Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?,
213 (Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?,
214 (Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?,
215 (Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?,
216 (Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?,
217 (OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?,
218 (OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?,
219 (OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?,
220 (Ignore(_), ..) => 0,
221 (UnixTimestamp(modifier), Some(date), Some(time), Some(offset)) => {
222 fmt_unix_timestamp(output, date, time, offset, modifier)?
223 }
224 (End(modifier::End {}), ..) => 0,
225
226 #[allow(unreachable_patterns)]
231 (
232 Day(_) | Month(_) | Ordinal(_) | Weekday(_) | WeekNumber(_) | Year(_) | Hour(_)
233 | Minute(_) | Period(_) | Second(_) | Subsecond(_) | OffsetHour(_) | OffsetMinute(_)
234 | OffsetSecond(_) | Ignore(_) | UnixTimestamp(_) | End(_),
235 ..,
236 ) => return Err(error::Format::InsufficientTypeInformation),
237 })
238}
239
240#[inline]
242fn fmt_day(
243 output: &mut (impl io::Write + ?Sized),
244 date: Date,
245 modifier::Day { padding }: modifier::Day,
246) -> Result<usize, io::Error> {
247 format_number::<2>(output, date.day(), padding)
248}
249
250#[inline]
252fn fmt_month(
253 output: &mut (impl io::Write + ?Sized),
254 date: Date,
255 modifier::Month {
256 padding,
257 repr,
258 case_sensitive: _, }: modifier::Month,
260) -> Result<usize, io::Error> {
261 match repr {
262 modifier::MonthRepr::Numerical => {
263 format_number::<2>(output, u8::from(date.month()), padding)
264 }
265 modifier::MonthRepr::Long => write(
266 output,
267 MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1],
268 ),
269 modifier::MonthRepr::Short => write(
270 output,
271 &MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1][..3],
272 ),
273 }
274}
275
276#[inline]
278fn fmt_ordinal(
279 output: &mut (impl io::Write + ?Sized),
280 date: Date,
281 modifier::Ordinal { padding }: modifier::Ordinal,
282) -> Result<usize, io::Error> {
283 format_number::<3>(output, date.ordinal(), padding)
284}
285
286#[inline]
288fn fmt_weekday(
289 output: &mut (impl io::Write + ?Sized),
290 date: Date,
291 modifier::Weekday {
292 repr,
293 one_indexed,
294 case_sensitive: _, }: modifier::Weekday,
296) -> Result<usize, io::Error> {
297 match repr {
298 modifier::WeekdayRepr::Short => write(
299 output,
300 &WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()][..3],
301 ),
302 modifier::WeekdayRepr::Long => write(
303 output,
304 WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()],
305 ),
306 modifier::WeekdayRepr::Sunday => format_number::<1>(
307 output,
308 date.weekday().number_days_from_sunday() + u8::from(one_indexed),
309 modifier::Padding::None,
310 ),
311 modifier::WeekdayRepr::Monday => format_number::<1>(
312 output,
313 date.weekday().number_days_from_monday() + u8::from(one_indexed),
314 modifier::Padding::None,
315 ),
316 }
317}
318
319#[inline]
321fn fmt_week_number(
322 output: &mut (impl io::Write + ?Sized),
323 date: Date,
324 modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
325) -> Result<usize, io::Error> {
326 format_number::<2>(
327 output,
328 match repr {
329 modifier::WeekNumberRepr::Iso => date.iso_week(),
330 modifier::WeekNumberRepr::Sunday => date.sunday_based_week(),
331 modifier::WeekNumberRepr::Monday => date.monday_based_week(),
332 },
333 padding,
334 )
335}
336
337fn fmt_year(
339 output: &mut (impl io::Write + ?Sized),
340 date: Date,
341 modifier::Year {
342 padding,
343 repr,
344 range,
345 iso_week_based,
346 sign_is_mandatory,
347 }: modifier::Year,
348) -> Result<usize, error::Format> {
349 let full_year = if iso_week_based {
350 date.iso_year_week().0
351 } else {
352 date.year()
353 };
354 let value = match repr {
355 modifier::YearRepr::Full => full_year,
356 modifier::YearRepr::Century => full_year / 100,
357 modifier::YearRepr::LastTwo => (full_year % 100).abs(),
358 };
359 let format_number = if cfg!(feature = "large-dates") && range == modifier::YearRange::Extended {
360 match repr {
361 modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6>,
362 modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5>,
363 modifier::YearRepr::Full => format_number::<4>,
364 modifier::YearRepr::Century if value.abs() >= 1_000 => format_number::<4>,
365 modifier::YearRepr::Century if value.abs() >= 100 => format_number::<3>,
366 modifier::YearRepr::Century => format_number::<2>,
367 modifier::YearRepr::LastTwo => format_number::<2>,
368 }
369 } else {
370 match repr {
371 modifier::YearRepr::Full | modifier::YearRepr::Century if full_year.abs() >= 10_000 => {
372 return Err(error::ComponentRange::conditional("year").into());
373 }
374 _ => {}
375 }
376 match repr {
377 modifier::YearRepr::Full => format_number::<4>,
378 modifier::YearRepr::Century => format_number::<2>,
379 modifier::YearRepr::LastTwo => format_number::<2>,
380 }
381 };
382 let mut bytes = 0;
383 if repr != modifier::YearRepr::LastTwo {
384 if full_year < 0 {
385 bytes += write(output, b"-")?;
386 } else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
387 bytes += write(output, b"+")?;
388 }
389 }
390 bytes += format_number(output, value.unsigned_abs(), padding)?;
391 Ok(bytes)
392}
393
394#[inline]
396fn fmt_hour(
397 output: &mut (impl io::Write + ?Sized),
398 time: Time,
399 modifier::Hour {
400 padding,
401 is_12_hour_clock,
402 }: modifier::Hour,
403) -> Result<usize, io::Error> {
404 let value = match (time.hour(), is_12_hour_clock) {
405 (hour, false) => hour,
406 (0 | 12, true) => 12,
407 (hour, true) if hour < 12 => hour,
408 (hour, true) => hour - 12,
409 };
410 format_number::<2>(output, value, padding)
411}
412
413#[inline]
415fn fmt_minute(
416 output: &mut (impl io::Write + ?Sized),
417 time: Time,
418 modifier::Minute { padding }: modifier::Minute,
419) -> Result<usize, io::Error> {
420 format_number::<2>(output, time.minute(), padding)
421}
422
423#[inline]
425fn fmt_period(
426 output: &mut (impl io::Write + ?Sized),
427 time: Time,
428 modifier::Period {
429 is_uppercase,
430 case_sensitive: _, }: modifier::Period,
432) -> Result<usize, io::Error> {
433 match (time.hour() >= 12, is_uppercase) {
434 (false, false) => write(output, b"am"),
435 (false, true) => write(output, b"AM"),
436 (true, false) => write(output, b"pm"),
437 (true, true) => write(output, b"PM"),
438 }
439}
440
441#[inline]
443fn fmt_second(
444 output: &mut (impl io::Write + ?Sized),
445 time: Time,
446 modifier::Second { padding }: modifier::Second,
447) -> Result<usize, io::Error> {
448 format_number::<2>(output, time.second(), padding)
449}
450
451#[inline]
453fn fmt_subsecond(
454 output: &mut (impl io::Write + ?Sized),
455 time: Time,
456 modifier::Subsecond { digits }: modifier::Subsecond,
457) -> Result<usize, io::Error> {
458 use modifier::SubsecondDigits::*;
459 let nanos = time.nanosecond();
460
461 if digits == Nine || (digits == OneOrMore && !nanos.is_multiple_of(10)) {
462 format_number_pad_zero::<9>(output, nanos)
463 } else if digits == Eight || (digits == OneOrMore && !(nanos / 10).is_multiple_of(10)) {
464 format_number_pad_zero::<8>(output, nanos / 10)
465 } else if digits == Seven || (digits == OneOrMore && !(nanos / 100).is_multiple_of(10)) {
466 format_number_pad_zero::<7>(output, nanos / 100)
467 } else if digits == Six || (digits == OneOrMore && !(nanos / 1_000).is_multiple_of(10)) {
468 format_number_pad_zero::<6>(output, nanos / 1_000)
469 } else if digits == Five || (digits == OneOrMore && !(nanos / 10_000).is_multiple_of(10)) {
470 format_number_pad_zero::<5>(output, nanos / 10_000)
471 } else if digits == Four || (digits == OneOrMore && !(nanos / 100_000).is_multiple_of(10)) {
472 format_number_pad_zero::<4>(output, nanos / 100_000)
473 } else if digits == Three || (digits == OneOrMore && !(nanos / 1_000_000).is_multiple_of(10)) {
474 format_number_pad_zero::<3>(output, nanos / 1_000_000)
475 } else if digits == Two || (digits == OneOrMore && !(nanos / 10_000_000).is_multiple_of(10)) {
476 format_number_pad_zero::<2>(output, nanos / 10_000_000)
477 } else {
478 format_number_pad_zero::<1>(output, nanos / 100_000_000)
479 }
480}
481
482#[inline]
484fn fmt_offset_hour(
485 output: &mut (impl io::Write + ?Sized),
486 offset: UtcOffset,
487 modifier::OffsetHour {
488 padding,
489 sign_is_mandatory,
490 }: modifier::OffsetHour,
491) -> Result<usize, io::Error> {
492 let mut bytes = 0;
493 if offset.is_negative() {
494 bytes += write(output, b"-")?;
495 } else if sign_is_mandatory {
496 bytes += write(output, b"+")?;
497 }
498 bytes += format_number::<2>(output, offset.whole_hours().unsigned_abs(), padding)?;
499 Ok(bytes)
500}
501
502#[inline]
504fn fmt_offset_minute(
505 output: &mut (impl io::Write + ?Sized),
506 offset: UtcOffset,
507 modifier::OffsetMinute { padding }: modifier::OffsetMinute,
508) -> Result<usize, io::Error> {
509 format_number::<2>(output, offset.minutes_past_hour().unsigned_abs(), padding)
510}
511
512#[inline]
514fn fmt_offset_second(
515 output: &mut (impl io::Write + ?Sized),
516 offset: UtcOffset,
517 modifier::OffsetSecond { padding }: modifier::OffsetSecond,
518) -> Result<usize, io::Error> {
519 format_number::<2>(output, offset.seconds_past_minute().unsigned_abs(), padding)
520}
521
522#[inline]
524fn fmt_unix_timestamp(
525 output: &mut (impl io::Write + ?Sized),
526 date: Date,
527 time: Time,
528 offset: UtcOffset,
529 modifier::UnixTimestamp {
530 precision,
531 sign_is_mandatory,
532 }: modifier::UnixTimestamp,
533) -> Result<usize, io::Error> {
534 let date_time = OffsetDateTime::new_in_offset(date, time, offset).to_offset(UtcOffset::UTC);
535
536 if date_time < OffsetDateTime::UNIX_EPOCH {
537 write(output, b"-")?;
538 } else if sign_is_mandatory {
539 write(output, b"+")?;
540 }
541
542 match precision {
543 modifier::UnixTimestampPrecision::Second => {
544 format_number_pad_none(output, date_time.unix_timestamp().unsigned_abs())
545 }
546 modifier::UnixTimestampPrecision::Millisecond => format_number_pad_none(
547 output,
548 (date_time.unix_timestamp_nanos() / Nanosecond::per_t::<i128>(Millisecond))
549 .unsigned_abs(),
550 ),
551 modifier::UnixTimestampPrecision::Microsecond => format_number_pad_none(
552 output,
553 (date_time.unix_timestamp_nanos() / Nanosecond::per_t::<i128>(Microsecond))
554 .unsigned_abs(),
555 ),
556 modifier::UnixTimestampPrecision::Nanosecond => {
557 format_number_pad_none(output, date_time.unix_timestamp_nanos().unsigned_abs())
558 }
559 }
560}