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