1 | //! Helpers for implementing formatting for ISO 8601. |
2 | |
3 | use std::io; |
4 | |
5 | use num_conv::prelude::*; |
6 | |
7 | use crate::convert::*; |
8 | use crate::format_description::well_known::iso8601::{ |
9 | DateKind, EncodedConfig, OffsetPrecision, TimePrecision, |
10 | }; |
11 | use crate::format_description::well_known::Iso8601; |
12 | use crate::formatting::{format_float, format_number_pad_zero, write, write_if, write_if_else}; |
13 | use crate::{error, Date, Time, UtcOffset}; |
14 | |
15 | /// Format the date portion of ISO 8601. |
16 | pub(super) fn format_date<const CONFIG: EncodedConfig>( |
17 | output: &mut impl io::Write, |
18 | date: Date, |
19 | ) -> Result<usize, error::Format> { |
20 | let mut bytes = 0; |
21 | |
22 | match Iso8601::<CONFIG>::DATE_KIND { |
23 | DateKind::Calendar => { |
24 | let (year, month, day) = date.to_calendar_date(); |
25 | if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { |
26 | bytes += write_if_else(output, year < 0, b"-" , b"+" )?; |
27 | bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?; |
28 | } else if !(0..=9999).contains(&year) { |
29 | return Err(error::Format::InvalidComponent("year" )); |
30 | } else { |
31 | bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?; |
32 | } |
33 | bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-" )?; |
34 | bytes += format_number_pad_zero::<2>(output, u8::from(month))?; |
35 | bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-" )?; |
36 | bytes += format_number_pad_zero::<2>(output, day)?; |
37 | } |
38 | DateKind::Week => { |
39 | let (year, week, day) = date.to_iso_week_date(); |
40 | if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { |
41 | bytes += write_if_else(output, year < 0, b"-" , b"+" )?; |
42 | bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?; |
43 | } else if !(0..=9999).contains(&year) { |
44 | return Err(error::Format::InvalidComponent("year" )); |
45 | } else { |
46 | bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?; |
47 | } |
48 | bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-W" , b"W" )?; |
49 | bytes += format_number_pad_zero::<2>(output, week)?; |
50 | bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-" )?; |
51 | bytes += format_number_pad_zero::<1>(output, day.number_from_monday())?; |
52 | } |
53 | DateKind::Ordinal => { |
54 | let (year, day) = date.to_ordinal_date(); |
55 | if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS { |
56 | bytes += write_if_else(output, year < 0, b"-" , b"+" )?; |
57 | bytes += format_number_pad_zero::<6>(output, year.unsigned_abs())?; |
58 | } else if !(0..=9999).contains(&year) { |
59 | return Err(error::Format::InvalidComponent("year" )); |
60 | } else { |
61 | bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?; |
62 | } |
63 | bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-" )?; |
64 | bytes += format_number_pad_zero::<3>(output, day)?; |
65 | } |
66 | } |
67 | |
68 | Ok(bytes) |
69 | } |
70 | |
71 | /// Format the time portion of ISO 8601. |
72 | pub(super) fn format_time<const CONFIG: EncodedConfig>( |
73 | output: &mut impl io::Write, |
74 | time: Time, |
75 | ) -> Result<usize, error::Format> { |
76 | let mut bytes = 0; |
77 | |
78 | // The "T" can only be omitted in extended format where there is no date being formatted. |
79 | bytes += write_if( |
80 | output, |
81 | Iso8601::<CONFIG>::USE_SEPARATORS || Iso8601::<CONFIG>::FORMAT_DATE, |
82 | b"T" , |
83 | )?; |
84 | |
85 | let (hours, minutes, seconds, nanoseconds) = time.as_hms_nano(); |
86 | |
87 | match Iso8601::<CONFIG>::TIME_PRECISION { |
88 | TimePrecision::Hour { decimal_digits } => { |
89 | let hours = (hours as f64) |
90 | + (minutes as f64) / Minute::per(Hour) as f64 |
91 | + (seconds as f64) / Second::per(Hour) as f64 |
92 | + (nanoseconds as f64) / Nanosecond::per(Hour) as f64; |
93 | format_float(output, hours, 2, decimal_digits)?; |
94 | } |
95 | TimePrecision::Minute { decimal_digits } => { |
96 | bytes += format_number_pad_zero::<2>(output, hours)?; |
97 | bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":" )?; |
98 | let minutes = (minutes as f64) |
99 | + (seconds as f64) / Second::per(Minute) as f64 |
100 | + (nanoseconds as f64) / Nanosecond::per(Minute) as f64; |
101 | bytes += format_float(output, minutes, 2, decimal_digits)?; |
102 | } |
103 | TimePrecision::Second { decimal_digits } => { |
104 | bytes += format_number_pad_zero::<2>(output, hours)?; |
105 | bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":" )?; |
106 | bytes += format_number_pad_zero::<2>(output, minutes)?; |
107 | bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":" )?; |
108 | let seconds = (seconds as f64) + (nanoseconds as f64) / Nanosecond::per(Second) as f64; |
109 | bytes += format_float(output, seconds, 2, decimal_digits)?; |
110 | } |
111 | } |
112 | |
113 | Ok(bytes) |
114 | } |
115 | |
116 | /// Format the UTC offset portion of ISO 8601. |
117 | pub(super) fn format_offset<const CONFIG: EncodedConfig>( |
118 | output: &mut impl io::Write, |
119 | offset: UtcOffset, |
120 | ) -> Result<usize, error::Format> { |
121 | if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() { |
122 | return Ok(write(output, bytes:b"Z" )?); |
123 | } |
124 | |
125 | let mut bytes: usize = 0; |
126 | |
127 | let (hours: i8, minutes: i8, seconds: i8) = offset.as_hms(); |
128 | if seconds != 0 { |
129 | return Err(error::Format::InvalidComponent("offset_second" )); |
130 | } |
131 | bytes += write_if_else(output, pred:offset.is_negative(), true_bytes:b"-" , false_bytes:b"+" )?; |
132 | bytes += format_number_pad_zero::<2>(output, value:hours.unsigned_abs())?; |
133 | |
134 | if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes != 0 { |
135 | return Err(error::Format::InvalidComponent("offset_minute" )); |
136 | } else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute { |
137 | bytes += write_if(output, pred:Iso8601::<CONFIG>::USE_SEPARATORS, bytes:b":" )?; |
138 | bytes += format_number_pad_zero::<2>(output, value:minutes.unsigned_abs())?; |
139 | } |
140 | |
141 | Ok(bytes) |
142 | } |
143 | |