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
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
42pub(crate) fn write(output: &mut (impl io::Write + ?Sized), bytes: &[u8]) -> io::Result<usize> {
44 output.write_all(bytes)?;
45 Ok(bytes.len())
46}
47
48pub(crate) fn write_if(
50 output: &mut (impl io::Write + ?Sized),
51 pred: bool,
52 bytes: &[u8],
53) -> io::Result<usize> {
54 if pred {
55 write(output, bytes)
56 } else {
57 Ok(0)
58 }
59}
60
61pub(crate) fn write_if_else(
63 output: &mut (impl io::Write + ?Sized),
64 pred: bool,
65 true_bytes: &[u8],
66 false_bytes: &[u8],
67) -> io::Result<usize> {
68 write(output, if pred { true_bytes } else { false_bytes })
69}
70
71pub(crate) fn format_float(
76 output: &mut (impl io::Write + ?Sized),
77 value: f64,
78 digits_before_decimal: u8,
79 digits_after_decimal: Option<NonZeroU8>,
80) -> io::Result<usize> {
81 match digits_after_decimal {
82 Some(digits_after_decimal) => {
83 let trunc_num = 10_f64.powi(digits_after_decimal.get().cast_signed().extend());
85 let value = f64::trunc(value * trunc_num) / trunc_num;
86
87 let digits_after_decimal = digits_after_decimal.get().extend();
88 let width = digits_before_decimal.extend::<usize>() + 1 + digits_after_decimal;
89 write!(output, "{value:0>width$.digits_after_decimal$}")?;
90 Ok(width)
91 }
92 None => {
93 let value = value.trunc() as u64;
94 let width = digits_before_decimal.extend();
95 write!(output, "{value:0>width$}")?;
96 Ok(width)
97 }
98 }
99}
100
101pub(crate) fn format_number<const WIDTH: u8>(
105 output: &mut (impl io::Write + ?Sized),
106 value: impl itoa::Integer + DigitCount + Copy,
107 padding: modifier::Padding,
108) -> Result<usize, io::Error> {
109 match padding {
110 modifier::Padding::Space => format_number_pad_space::<WIDTH>(output, value),
111 modifier::Padding::Zero => format_number_pad_zero::<WIDTH>(output, value),
112 modifier::Padding::None => format_number_pad_none(output, value),
113 }
114}
115
116pub(crate) fn format_number_pad_space<const WIDTH: u8>(
120 output: &mut (impl io::Write + ?Sized),
121 value: impl itoa::Integer + DigitCount + Copy,
122) -> Result<usize, io::Error> {
123 let mut bytes = 0;
124 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
125 bytes += write(output, b" ")?;
126 }
127 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
128 Ok(bytes)
129}
130
131pub(crate) fn format_number_pad_zero<const WIDTH: u8>(
135 output: &mut (impl io::Write + ?Sized),
136 value: impl itoa::Integer + DigitCount + Copy,
137) -> Result<usize, io::Error> {
138 let mut bytes = 0;
139 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
140 bytes += write(output, b"0")?;
141 }
142 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
143 Ok(bytes)
144}
145
146pub(crate) fn format_number_pad_none(
150 output: &mut (impl io::Write + ?Sized),
151 value: impl itoa::Integer + Copy,
152) -> Result<usize, io::Error> {
153 write(output, itoa::Buffer::new().format(value).as_bytes())
154}
155
156pub(crate) fn format_component(
160 output: &mut (impl io::Write + ?Sized),
161 component: Component,
162 date: Option<Date>,
163 time: Option<Time>,
164 offset: Option<UtcOffset>,
165) -> Result<usize, error::Format> {
166 use Component::*;
167 Ok(match (component, date, time, offset) {
168 (Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?,
169 (Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?,
170 (Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?,
171 (Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?,
172 (WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?,
173 (Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?,
174 (Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?,
175 (Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?,
176 (Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?,
177 (Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?,
178 (Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?,
179 (OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?,
180 (OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?,
181 (OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?,
182 (Ignore(_), ..) => 0,
183 (UnixTimestamp(modifier), Some(date), Some(time), Some(offset)) => {
184 fmt_unix_timestamp(output, date, time, offset, modifier)?
185 }
186 (End(modifier::End {}), ..) => 0,
187
188 #[allow(unreachable_patterns)]
193 (
194 Day(_) | Month(_) | Ordinal(_) | Weekday(_) | WeekNumber(_) | Year(_) | Hour(_)
195 | Minute(_) | Period(_) | Second(_) | Subsecond(_) | OffsetHour(_) | OffsetMinute(_)
196 | OffsetSecond(_) | Ignore(_) | UnixTimestamp(_) | End(_),
197 ..,
198 ) => return Err(error::Format::InsufficientTypeInformation),
199 })
200}
201
202fn fmt_day(
204 output: &mut (impl io::Write + ?Sized),
205 date: Date,
206 modifier::Day { padding }: modifier::Day,
207) -> Result<usize, io::Error> {
208 format_number::<2>(output, date.day(), padding)
209}
210
211fn fmt_month(
213 output: &mut (impl io::Write + ?Sized),
214 date: Date,
215 modifier::Month {
216 padding,
217 repr,
218 case_sensitive: _, }: modifier::Month,
220) -> Result<usize, io::Error> {
221 match repr {
222 modifier::MonthRepr::Numerical => {
223 format_number::<2>(output, u8::from(date.month()), padding)
224 }
225 modifier::MonthRepr::Long => write(
226 output,
227 MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1],
228 ),
229 modifier::MonthRepr::Short => write(
230 output,
231 &MONTH_NAMES[u8::from(date.month()).extend::<usize>() - 1][..3],
232 ),
233 }
234}
235
236fn fmt_ordinal(
238 output: &mut (impl io::Write + ?Sized),
239 date: Date,
240 modifier::Ordinal { padding }: modifier::Ordinal,
241) -> Result<usize, io::Error> {
242 format_number::<3>(output, date.ordinal(), padding)
243}
244
245fn fmt_weekday(
247 output: &mut (impl io::Write + ?Sized),
248 date: Date,
249 modifier::Weekday {
250 repr,
251 one_indexed,
252 case_sensitive: _, }: modifier::Weekday,
254) -> Result<usize, io::Error> {
255 match repr {
256 modifier::WeekdayRepr::Short => write(
257 output,
258 &WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()][..3],
259 ),
260 modifier::WeekdayRepr::Long => write(
261 output,
262 WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()],
263 ),
264 modifier::WeekdayRepr::Sunday => format_number::<1>(
265 output,
266 date.weekday().number_days_from_sunday() + u8::from(one_indexed),
267 modifier::Padding::None,
268 ),
269 modifier::WeekdayRepr::Monday => format_number::<1>(
270 output,
271 date.weekday().number_days_from_monday() + u8::from(one_indexed),
272 modifier::Padding::None,
273 ),
274 }
275}
276
277fn fmt_week_number(
279 output: &mut (impl io::Write + ?Sized),
280 date: Date,
281 modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
282) -> Result<usize, io::Error> {
283 format_number::<2>(
284 output,
285 match repr {
286 modifier::WeekNumberRepr::Iso => date.iso_week(),
287 modifier::WeekNumberRepr::Sunday => date.sunday_based_week(),
288 modifier::WeekNumberRepr::Monday => date.monday_based_week(),
289 },
290 padding,
291 )
292}
293
294fn fmt_year(
296 output: &mut (impl io::Write + ?Sized),
297 date: Date,
298 modifier::Year {
299 padding,
300 repr,
301 range,
302 iso_week_based,
303 sign_is_mandatory,
304 }: modifier::Year,
305) -> Result<usize, error::Format> {
306 let full_year = if iso_week_based {
307 date.iso_year_week().0
308 } else {
309 date.year()
310 };
311 let value = match repr {
312 modifier::YearRepr::Full => full_year,
313 modifier::YearRepr::Century => full_year / 100,
314 modifier::YearRepr::LastTwo => (full_year % 100).abs(),
315 };
316 let format_number = if cfg!(feature = "large-dates") && range == modifier::YearRange::Extended {
317 match repr {
318 modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6>,
319 modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5>,
320 modifier::YearRepr::Full => format_number::<4>,
321 modifier::YearRepr::Century if value.abs() >= 1_000 => format_number::<4>,
322 modifier::YearRepr::Century if value.abs() >= 100 => format_number::<3>,
323 modifier::YearRepr::Century => format_number::<2>,
324 modifier::YearRepr::LastTwo => format_number::<2>,
325 }
326 } else {
327 match repr {
328 modifier::YearRepr::Full | modifier::YearRepr::Century if full_year.abs() >= 10_000 => {
329 return Err(error::ComponentRange {
330 name: "year",
331 minimum: -9999,
332 maximum: 9999,
333 value: full_year.extend(),
334 conditional_message: Some("when `range:standard` is used"),
335 }
336 .into());
337 }
338 _ => {}
339 }
340 match repr {
341 modifier::YearRepr::Full => format_number::<4>,
342 modifier::YearRepr::Century => format_number::<2>,
343 modifier::YearRepr::LastTwo => format_number::<2>,
344 }
345 };
346 let mut bytes = 0;
347 if repr != modifier::YearRepr::LastTwo {
348 if full_year < 0 {
349 bytes += write(output, b"-")?;
350 } else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
351 bytes += write(output, b"+")?;
352 }
353 }
354 bytes += format_number(output, value.unsigned_abs(), padding)?;
355 Ok(bytes)
356}
357
358fn fmt_hour(
360 output: &mut (impl io::Write + ?Sized),
361 time: Time,
362 modifier::Hour {
363 padding,
364 is_12_hour_clock,
365 }: modifier::Hour,
366) -> Result<usize, io::Error> {
367 let value = match (time.hour(), is_12_hour_clock) {
368 (hour, false) => hour,
369 (0 | 12, true) => 12,
370 (hour, true) if hour < 12 => hour,
371 (hour, true) => hour - 12,
372 };
373 format_number::<2>(output, value, padding)
374}
375
376fn fmt_minute(
378 output: &mut (impl io::Write + ?Sized),
379 time: Time,
380 modifier::Minute { padding }: modifier::Minute,
381) -> Result<usize, io::Error> {
382 format_number::<2>(output, time.minute(), padding)
383}
384
385fn fmt_period(
387 output: &mut (impl io::Write + ?Sized),
388 time: Time,
389 modifier::Period {
390 is_uppercase,
391 case_sensitive: _, }: modifier::Period,
393) -> Result<usize, io::Error> {
394 match (time.hour() >= 12, is_uppercase) {
395 (false, false) => write(output, b"am"),
396 (false, true) => write(output, b"AM"),
397 (true, false) => write(output, b"pm"),
398 (true, true) => write(output, b"PM"),
399 }
400}
401
402fn fmt_second(
404 output: &mut (impl io::Write + ?Sized),
405 time: Time,
406 modifier::Second { padding }: modifier::Second,
407) -> Result<usize, io::Error> {
408 format_number::<2>(output, time.second(), padding)
409}
410
411fn fmt_subsecond(
413 output: &mut (impl io::Write + ?Sized),
414 time: Time,
415 modifier::Subsecond { digits }: modifier::Subsecond,
416) -> Result<usize, io::Error> {
417 use modifier::SubsecondDigits::*;
418 let nanos = time.nanosecond();
419
420 if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) {
421 format_number_pad_zero::<9>(output, nanos)
422 } else if digits == Eight || (digits == OneOrMore && (nanos / 10) % 10 != 0) {
423 format_number_pad_zero::<8>(output, nanos / 10)
424 } else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) {
425 format_number_pad_zero::<7>(output, nanos / 100)
426 } else if digits == Six || (digits == OneOrMore && (nanos / 1_000) % 10 != 0) {
427 format_number_pad_zero::<6>(output, nanos / 1_000)
428 } else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) {
429 format_number_pad_zero::<5>(output, nanos / 10_000)
430 } else if digits == Four || (digits == OneOrMore && (nanos / 100_000) % 10 != 0) {
431 format_number_pad_zero::<4>(output, nanos / 100_000)
432 } else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) {
433 format_number_pad_zero::<3>(output, nanos / 1_000_000)
434 } else if digits == Two || (digits == OneOrMore && (nanos / 10_000_000) % 10 != 0) {
435 format_number_pad_zero::<2>(output, nanos / 10_000_000)
436 } else {
437 format_number_pad_zero::<1>(output, nanos / 100_000_000)
438 }
439}
440
441fn fmt_offset_hour(
443 output: &mut (impl io::Write + ?Sized),
444 offset: UtcOffset,
445 modifier::OffsetHour {
446 padding,
447 sign_is_mandatory,
448 }: modifier::OffsetHour,
449) -> Result<usize, io::Error> {
450 let mut bytes = 0;
451 if offset.is_negative() {
452 bytes += write(output, b"-")?;
453 } else if sign_is_mandatory {
454 bytes += write(output, b"+")?;
455 }
456 bytes += format_number::<2>(output, offset.whole_hours().unsigned_abs(), padding)?;
457 Ok(bytes)
458}
459
460fn fmt_offset_minute(
462 output: &mut (impl io::Write + ?Sized),
463 offset: UtcOffset,
464 modifier::OffsetMinute { padding }: modifier::OffsetMinute,
465) -> Result<usize, io::Error> {
466 format_number::<2>(output, offset.minutes_past_hour().unsigned_abs(), padding)
467}
468
469fn fmt_offset_second(
471 output: &mut (impl io::Write + ?Sized),
472 offset: UtcOffset,
473 modifier::OffsetSecond { padding }: modifier::OffsetSecond,
474) -> Result<usize, io::Error> {
475 format_number::<2>(output, offset.seconds_past_minute().unsigned_abs(), padding)
476}
477
478fn fmt_unix_timestamp(
480 output: &mut (impl io::Write + ?Sized),
481 date: Date,
482 time: Time,
483 offset: UtcOffset,
484 modifier::UnixTimestamp {
485 precision,
486 sign_is_mandatory,
487 }: modifier::UnixTimestamp,
488) -> Result<usize, io::Error> {
489 let date_time = OffsetDateTime::new_in_offset(date, time, offset).to_offset(UtcOffset::UTC);
490
491 if date_time < OffsetDateTime::UNIX_EPOCH {
492 write(output, b"-")?;
493 } else if sign_is_mandatory {
494 write(output, b"+")?;
495 }
496
497 match precision {
498 modifier::UnixTimestampPrecision::Second => {
499 format_number_pad_none(output, date_time.unix_timestamp().unsigned_abs())
500 }
501 modifier::UnixTimestampPrecision::Millisecond => format_number_pad_none(
502 output,
503 (date_time.unix_timestamp_nanos()
504 / Nanosecond::per(Millisecond).cast_signed().extend::<i128>())
505 .unsigned_abs(),
506 ),
507 modifier::UnixTimestampPrecision::Microsecond => format_number_pad_none(
508 output,
509 (date_time.unix_timestamp_nanos()
510 / Nanosecond::per(Microsecond).cast_signed().extend::<i128>())
511 .unsigned_abs(),
512 ),
513 modifier::UnixTimestampPrecision::Nanosecond => {
514 format_number_pad_none(output, date_time.unix_timestamp_nanos().unsigned_abs())
515 }
516 }
517}