Skip to main content

time/formatting/
iso8601.rs

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