time/formatting/
iso8601.rs

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