]> git.proxmox.com Git - rustc.git/blob - vendor/time/src/formatting/mod.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / vendor / time / src / formatting / mod.rs
1 //! Formatting for various types.
2
3 pub(crate) mod formattable;
4 mod iso8601;
5
6 use core::num::NonZeroU8;
7 use std::io;
8
9 pub use self::formattable::Formattable;
10 use crate::format_description::{modifier, Component};
11 use crate::{error, Date, Time, UtcOffset};
12
13 #[allow(clippy::missing_docs_in_private_items)]
14 const MONTH_NAMES: [&[u8]; 12] = [
15 b"January",
16 b"February",
17 b"March",
18 b"April",
19 b"May",
20 b"June",
21 b"July",
22 b"August",
23 b"September",
24 b"October",
25 b"November",
26 b"December",
27 ];
28
29 #[allow(clippy::missing_docs_in_private_items)]
30 const WEEKDAY_NAMES: [&[u8]; 7] = [
31 b"Monday",
32 b"Tuesday",
33 b"Wednesday",
34 b"Thursday",
35 b"Friday",
36 b"Saturday",
37 b"Sunday",
38 ];
39
40 // region: extension trait
41 /// A trait that indicates the formatted width of the value can be determined.
42 ///
43 /// Note that this should not be implemented for any signed integers. This forces the caller to
44 /// write the sign if desired.
45 pub(crate) trait DigitCount {
46 /// The number of digits in the stringified value.
47 fn num_digits(self) -> u8;
48 }
49 impl DigitCount for u8 {
50 fn num_digits(self) -> u8 {
51 // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
52 if self < 10 {
53 1
54 } else if self < 100 {
55 2
56 } else {
57 3
58 }
59 }
60 }
61 impl DigitCount for u16 {
62 fn num_digits(self) -> u8 {
63 // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
64 if self < 10 {
65 1
66 } else if self < 100 {
67 2
68 } else if self < 1_000 {
69 3
70 } else if self < 10_000 {
71 4
72 } else {
73 5
74 }
75 }
76 }
77
78 impl DigitCount for u32 {
79 fn num_digits(self) -> u8 {
80 /// Lookup table
81 const TABLE: &[u64] = &[
82 0x0001_0000_0000,
83 0x0001_0000_0000,
84 0x0001_0000_0000,
85 0x0001_FFFF_FFF6,
86 0x0002_0000_0000,
87 0x0002_0000_0000,
88 0x0002_FFFF_FF9C,
89 0x0003_0000_0000,
90 0x0003_0000_0000,
91 0x0003_FFFF_FC18,
92 0x0004_0000_0000,
93 0x0004_0000_0000,
94 0x0004_0000_0000,
95 0x0004_FFFF_D8F0,
96 0x0005_0000_0000,
97 0x0005_0000_0000,
98 0x0005_FFFE_7960,
99 0x0006_0000_0000,
100 0x0006_0000_0000,
101 0x0006_FFF0_BDC0,
102 0x0007_0000_0000,
103 0x0007_0000_0000,
104 0x0007_0000_0000,
105 0x0007_FF67_6980,
106 0x0008_0000_0000,
107 0x0008_0000_0000,
108 0x0008_FA0A_1F00,
109 0x0009_0000_0000,
110 0x0009_0000_0000,
111 0x0009_C465_3600,
112 0x000A_0000_0000,
113 0x000A_0000_0000,
114 ];
115 ((self as u64 + TABLE[31_u32.saturating_sub(self.leading_zeros()) as usize]) >> 32) as _
116 }
117 }
118 // endregion extension trait
119
120 /// Write all bytes to the output, returning the number of bytes written.
121 pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
122 output.write_all(bytes)?;
123 Ok(bytes.len())
124 }
125
126 /// If `pred` is true, write all bytes to the output, returning the number of bytes written.
127 pub(crate) fn write_if(output: &mut impl io::Write, pred: bool, bytes: &[u8]) -> io::Result<usize> {
128 if pred { write(output, bytes) } else { Ok(0) }
129 }
130
131 /// If `pred` is true, write `true_bytes` to the output. Otherwise, write `false_bytes`.
132 pub(crate) fn write_if_else(
133 output: &mut impl io::Write,
134 pred: bool,
135 true_bytes: &[u8],
136 false_bytes: &[u8],
137 ) -> io::Result<usize> {
138 write(output, if pred { true_bytes } else { false_bytes })
139 }
140
141 /// Write the floating point number to the output, returning the number of bytes written.
142 ///
143 /// This method accepts the number of digits before and after the decimal. The value will be padded
144 /// with zeroes to the left if necessary.
145 pub(crate) fn format_float(
146 output: &mut impl io::Write,
147 value: f64,
148 digits_before_decimal: u8,
149 digits_after_decimal: Option<NonZeroU8>,
150 ) -> io::Result<usize> {
151 match digits_after_decimal {
152 Some(digits_after_decimal) => {
153 let digits_after_decimal = digits_after_decimal.get() as usize;
154 let width = digits_before_decimal as usize + 1 + digits_after_decimal;
155 write!(
156 output,
157 "{value:0>width$.digits_after_decimal$}",
158 value = value,
159 width = width,
160 digits_after_decimal = digits_after_decimal,
161 )?;
162 Ok(width)
163 }
164 None => {
165 let value = value.trunc() as u64;
166 let width = digits_before_decimal as usize;
167 write!(output, "{value:0>width$?}", value = value, width = width)?;
168 Ok(width)
169 }
170 }
171 }
172
173 /// Format a number with the provided padding and width.
174 ///
175 /// The sign must be written by the caller.
176 pub(crate) fn format_number<const WIDTH: u8, W: io::Write, V: itoa::Integer + DigitCount + Copy>(
177 output: &mut W,
178 value: V,
179 padding: modifier::Padding,
180 ) -> Result<usize, io::Error> {
181 match padding {
182 modifier::Padding::Space => format_number_pad_space::<WIDTH, _, _>(output, value),
183 modifier::Padding::Zero => format_number_pad_zero::<WIDTH, _, _>(output, value),
184 modifier::Padding::None => write(output, itoa::Buffer::new().format(value).as_bytes()),
185 }
186 }
187
188 /// Format a number with the provided width and spaces as padding.
189 ///
190 /// The sign must be written by the caller.
191 pub(crate) fn format_number_pad_space<
192 const WIDTH: u8,
193 W: io::Write,
194 V: itoa::Integer + DigitCount + Copy,
195 >(
196 output: &mut W,
197 value: V,
198 ) -> Result<usize, io::Error> {
199 let mut bytes = 0;
200 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
201 bytes += write(output, b" ")?;
202 }
203 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
204 Ok(bytes)
205 }
206
207 /// Format a number with the provided width and zeros as padding.
208 ///
209 /// The sign must be written by the caller.
210 pub(crate) fn format_number_pad_zero<
211 const WIDTH: u8,
212 W: io::Write,
213 V: itoa::Integer + DigitCount + Copy,
214 >(
215 output: &mut W,
216 value: V,
217 ) -> Result<usize, io::Error> {
218 let mut bytes = 0;
219 for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
220 bytes += write(output, b"0")?;
221 }
222 bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
223 Ok(bytes)
224 }
225
226 /// Format the provided component into the designated output. An `Err` will be returned if the
227 /// component requires information that it does not provide or if the value cannot be output to the
228 /// stream.
229 pub(crate) fn format_component(
230 output: &mut impl io::Write,
231 component: Component,
232 date: Option<Date>,
233 time: Option<Time>,
234 offset: Option<UtcOffset>,
235 ) -> Result<usize, error::Format> {
236 use Component::*;
237 Ok(match (component, date, time, offset) {
238 (Day(modifier), Some(date), ..) => fmt_day(output, date, modifier)?,
239 (Month(modifier), Some(date), ..) => fmt_month(output, date, modifier)?,
240 (Ordinal(modifier), Some(date), ..) => fmt_ordinal(output, date, modifier)?,
241 (Weekday(modifier), Some(date), ..) => fmt_weekday(output, date, modifier)?,
242 (WeekNumber(modifier), Some(date), ..) => fmt_week_number(output, date, modifier)?,
243 (Year(modifier), Some(date), ..) => fmt_year(output, date, modifier)?,
244 (Hour(modifier), _, Some(time), _) => fmt_hour(output, time, modifier)?,
245 (Minute(modifier), _, Some(time), _) => fmt_minute(output, time, modifier)?,
246 (Period(modifier), _, Some(time), _) => fmt_period(output, time, modifier)?,
247 (Second(modifier), _, Some(time), _) => fmt_second(output, time, modifier)?,
248 (Subsecond(modifier), _, Some(time), _) => fmt_subsecond(output, time, modifier)?,
249 (OffsetHour(modifier), .., Some(offset)) => fmt_offset_hour(output, offset, modifier)?,
250 (OffsetMinute(modifier), .., Some(offset)) => fmt_offset_minute(output, offset, modifier)?,
251 (OffsetSecond(modifier), .., Some(offset)) => fmt_offset_second(output, offset, modifier)?,
252 _ => return Err(error::Format::InsufficientTypeInformation),
253 })
254 }
255
256 // region: date formatters
257 /// Format the day into the designated output.
258 fn fmt_day(
259 output: &mut impl io::Write,
260 date: Date,
261 modifier::Day { padding }: modifier::Day,
262 ) -> Result<usize, io::Error> {
263 format_number::<2, _, _>(output, date.day(), padding)
264 }
265
266 /// Format the month into the designated output.
267 fn fmt_month(
268 output: &mut impl io::Write,
269 date: Date,
270 modifier::Month {
271 padding,
272 repr,
273 case_sensitive: _, // no effect on formatting
274 }: modifier::Month,
275 ) -> Result<usize, io::Error> {
276 match repr {
277 modifier::MonthRepr::Numerical => {
278 format_number::<2, _, _>(output, date.month() as u8, padding)
279 }
280 modifier::MonthRepr::Long => write(output, MONTH_NAMES[date.month() as usize - 1]),
281 modifier::MonthRepr::Short => write(output, &MONTH_NAMES[date.month() as usize - 1][..3]),
282 }
283 }
284
285 /// Format the ordinal into the designated output.
286 fn fmt_ordinal(
287 output: &mut impl io::Write,
288 date: Date,
289 modifier::Ordinal { padding }: modifier::Ordinal,
290 ) -> Result<usize, io::Error> {
291 format_number::<3, _, _>(output, date.ordinal(), padding)
292 }
293
294 /// Format the weekday into the designated output.
295 fn fmt_weekday(
296 output: &mut impl io::Write,
297 date: Date,
298 modifier::Weekday {
299 repr,
300 one_indexed,
301 case_sensitive: _, // no effect on formatting
302 }: modifier::Weekday,
303 ) -> Result<usize, io::Error> {
304 match repr {
305 modifier::WeekdayRepr::Short => write(
306 output,
307 &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
308 ),
309 modifier::WeekdayRepr::Long => write(
310 output,
311 WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize],
312 ),
313 modifier::WeekdayRepr::Sunday => format_number::<1, _, _>(
314 output,
315 date.weekday().number_days_from_sunday() + one_indexed as u8,
316 modifier::Padding::None,
317 ),
318 modifier::WeekdayRepr::Monday => format_number::<1, _, _>(
319 output,
320 date.weekday().number_days_from_monday() + one_indexed as u8,
321 modifier::Padding::None,
322 ),
323 }
324 }
325
326 /// Format the week number into the designated output.
327 fn fmt_week_number(
328 output: &mut impl io::Write,
329 date: Date,
330 modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
331 ) -> Result<usize, io::Error> {
332 format_number::<2, _, _>(
333 output,
334 match repr {
335 modifier::WeekNumberRepr::Iso => date.iso_week(),
336 modifier::WeekNumberRepr::Sunday => date.sunday_based_week(),
337 modifier::WeekNumberRepr::Monday => date.monday_based_week(),
338 },
339 padding,
340 )
341 }
342
343 /// Format the year into the designated output.
344 fn fmt_year(
345 output: &mut impl io::Write,
346 date: Date,
347 modifier::Year {
348 padding,
349 repr,
350 iso_week_based,
351 sign_is_mandatory,
352 }: modifier::Year,
353 ) -> Result<usize, io::Error> {
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::LastTwo => (full_year % 100).abs(),
362 };
363 let format_number = match repr {
364 #[cfg(feature = "large-dates")]
365 modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6, _, _>,
366 #[cfg(feature = "large-dates")]
367 modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5, _, _>,
368 modifier::YearRepr::Full => format_number::<4, _, _>,
369 modifier::YearRepr::LastTwo => format_number::<2, _, _>,
370 };
371 let mut bytes = 0;
372 if repr != modifier::YearRepr::LastTwo {
373 if full_year < 0 {
374 bytes += write(output, b"-")?;
375 } else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
376 bytes += write(output, b"+")?;
377 }
378 }
379 bytes += format_number(output, value.unsigned_abs(), padding)?;
380 Ok(bytes)
381 }
382 // endregion date formatters
383
384 // region: time formatters
385 /// Format the hour into the designated output.
386 fn fmt_hour(
387 output: &mut impl io::Write,
388 time: Time,
389 modifier::Hour {
390 padding,
391 is_12_hour_clock,
392 }: modifier::Hour,
393 ) -> Result<usize, io::Error> {
394 let value = match (time.hour(), is_12_hour_clock) {
395 (hour, false) => hour,
396 (0 | 12, true) => 12,
397 (hour, true) if hour < 12 => hour,
398 (hour, true) => hour - 12,
399 };
400 format_number::<2, _, _>(output, value, padding)
401 }
402
403 /// Format the minute into the designated output.
404 fn fmt_minute(
405 output: &mut impl io::Write,
406 time: Time,
407 modifier::Minute { padding }: modifier::Minute,
408 ) -> Result<usize, io::Error> {
409 format_number::<2, _, _>(output, time.minute(), padding)
410 }
411
412 /// Format the period into the designated output.
413 fn fmt_period(
414 output: &mut impl io::Write,
415 time: Time,
416 modifier::Period {
417 is_uppercase,
418 case_sensitive: _, // no effect on formatting
419 }: modifier::Period,
420 ) -> Result<usize, io::Error> {
421 match (time.hour() >= 12, is_uppercase) {
422 (false, false) => write(output, b"am"),
423 (false, true) => write(output, b"AM"),
424 (true, false) => write(output, b"pm"),
425 (true, true) => write(output, b"PM"),
426 }
427 }
428
429 /// Format the second into the designated output.
430 fn fmt_second(
431 output: &mut impl io::Write,
432 time: Time,
433 modifier::Second { padding }: modifier::Second,
434 ) -> Result<usize, io::Error> {
435 format_number::<2, _, _>(output, time.second(), padding)
436 }
437
438 /// Format the subsecond into the designated output.
439 fn fmt_subsecond<W: io::Write>(
440 output: &mut W,
441 time: Time,
442 modifier::Subsecond { digits }: modifier::Subsecond,
443 ) -> Result<usize, io::Error> {
444 use modifier::SubsecondDigits::*;
445 let nanos = time.nanosecond();
446
447 if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) {
448 format_number_pad_zero::<9, _, _>(output, nanos)
449 } else if digits == Eight || (digits == OneOrMore && (nanos / 10) % 10 != 0) {
450 format_number_pad_zero::<8, _, _>(output, nanos / 10)
451 } else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) {
452 format_number_pad_zero::<7, _, _>(output, nanos / 100)
453 } else if digits == Six || (digits == OneOrMore && (nanos / 1_000) % 10 != 0) {
454 format_number_pad_zero::<6, _, _>(output, nanos / 1_000)
455 } else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) {
456 format_number_pad_zero::<5, _, _>(output, nanos / 10_000)
457 } else if digits == Four || (digits == OneOrMore && (nanos / 100_000) % 10 != 0) {
458 format_number_pad_zero::<4, _, _>(output, nanos / 100_000)
459 } else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) {
460 format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000)
461 } else if digits == Two || (digits == OneOrMore && (nanos / 10_000_000) % 10 != 0) {
462 format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000)
463 } else {
464 format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000)
465 }
466 }
467 // endregion time formatters
468
469 // region: offset formatters
470 /// Format the offset hour into the designated output.
471 fn fmt_offset_hour(
472 output: &mut impl io::Write,
473 offset: UtcOffset,
474 modifier::OffsetHour {
475 padding,
476 sign_is_mandatory,
477 }: modifier::OffsetHour,
478 ) -> Result<usize, io::Error> {
479 let mut bytes = 0;
480 if offset.is_negative() {
481 bytes += write(output, b"-")?;
482 } else if sign_is_mandatory {
483 bytes += write(output, b"+")?;
484 }
485 bytes += format_number::<2, _, _>(output, offset.whole_hours().unsigned_abs(), padding)?;
486 Ok(bytes)
487 }
488
489 /// Format the offset minute into the designated output.
490 fn fmt_offset_minute(
491 output: &mut impl io::Write,
492 offset: UtcOffset,
493 modifier::OffsetMinute { padding }: modifier::OffsetMinute,
494 ) -> Result<usize, io::Error> {
495 format_number::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs(), padding)
496 }
497
498 /// Format the offset second into the designated output.
499 fn fmt_offset_second(
500 output: &mut impl io::Write,
501 offset: UtcOffset,
502 modifier::OffsetSecond { padding }: modifier::OffsetSecond,
503 ) -> Result<usize, io::Error> {
504 format_number::<2, _, _>(output, offset.seconds_past_minute().unsigned_abs(), padding)
505 }
506 // endregion offset formatters