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