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