1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//! The format described in ISO 8601.

mod adt_hack;

use core::num::NonZeroU8;

#[doc(hidden, no_inline)]
pub use self::adt_hack::DoNotRelyOnWhatThisIs;
pub use self::adt_hack::EncodedConfig;

/// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html).
///
/// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions.
///
/// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`].
/// Passing any other value is **unspecified behavior**.
///
/// Example: 1997-11-21T09:55:06.000000000-06:00
///
/// # Examples
#[cfg_attr(feature = "formatting", doc = "```rust")]
#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
/// # use time::format_description::well_known::Iso8601;
/// # use time_macros::datetime;
/// assert_eq!(
///     datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?,
///     "1997-11-12T09:55:06.000000000-06:00"
/// );
/// # Ok::<_, time::Error>(())
/// ```
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Iso8601<const CONFIG: EncodedConfig = { Config::DEFAULT.encode() }>;

impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Iso8601")
            .field("config", &Config::decode(CONFIG))
            .finish()
    }
}

/// Define associated constants for `Iso8601`.
macro_rules! define_assoc_consts {
    ($($(#[$doc:meta])* $vis:vis const $const_name:ident = $format:expr;)*) => {$(
        const $const_name: EncodedConfig = $format.encode();
        impl Iso8601<$const_name> {
            $(#[$doc])*
            $vis const $const_name: Self = Self;
        }
    )*};
}

define_assoc_consts! {
    /// An [`Iso8601`] with the default configuration.
    ///
    /// The following is the default behavior:
    ///
    /// - The configuration can be used for both formatting and parsing.
    /// - The date, time, and UTC offset are all formatted.
    /// - Separators (such as `-` and `:`) are included.
    /// - The year contains four digits, such that the year must be between 0 and 9999.
    /// - The date uses the calendar format.
    /// - The time has precision to the second and nine decimal digits.
    /// - The UTC offset has precision to the minute.
    ///
    /// If you need different behavior, use another associated constant. For full customization, use
    /// [`Config::DEFAULT`] and [`Config`]'s methods to create a custom configuration.
    pub const DEFAULT = Config::DEFAULT;
    /// An [`Iso8601`] that can only be used for parsing. Using this to format a value is
    /// unspecified behavior.
    pub const PARSING = Config::PARSING;
    /// An [`Iso8601`] that handles only the date, but is otherwise the same as [`Config::DEFAULT`].
    pub const DATE = Config::DEFAULT.set_formatted_components(FormattedComponents::Date);
    /// An [`Iso8601`] that handles only the time, but is otherwise the same as [`Config::DEFAULT`].
    pub const TIME = Config::DEFAULT.set_formatted_components(FormattedComponents::Time);
    /// An [`Iso8601`] that handles only the UTC offset, but is otherwise the same as
    /// [`Config::DEFAULT`].
    pub const OFFSET = Config::DEFAULT.set_formatted_components(FormattedComponents::Offset);
    /// An [`Iso8601`] that handles the date and time, but is otherwise the same as
    /// [`Config::DEFAULT`].
    pub const DATE_TIME = Config::DEFAULT.set_formatted_components(FormattedComponents::DateTime);
    /// An [`Iso8601`] that handles the date, time, and UTC offset. This is the same as
    /// [`Config::DEFAULT`].
    pub const DATE_TIME_OFFSET = Config::DEFAULT;
    /// An [`Iso8601`] that handles the time and UTC offset, but is otherwise the same as
    /// [`Config::DEFAULT`].
    pub const TIME_OFFSET = Config::DEFAULT
        .set_formatted_components(FormattedComponents::TimeOffset);
}

/// Which components to format.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormattedComponents {
    /// The configuration can only be used for parsing. Using this to format a value is
    /// unspecified behavior.
    None,
    /// Format only the date.
    Date,
    /// Format only the time.
    Time,
    /// Format only the UTC offset.
    Offset,
    /// Format the date and time.
    DateTime,
    /// Format the date, time, and UTC offset.
    DateTimeOffset,
    /// Format the time and UTC offset.
    TimeOffset,
}

/// Which format to use for the date.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DateKind {
    /// Use the year-month-day format.
    Calendar,
    /// Use the year-week-weekday format.
    Week,
    /// Use the week-ordinal format.
    Ordinal,
}

/// The precision and number of decimal digits present for the time.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimePrecision {
    /// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the
    /// specified number of decimal digits, if any.
    Hour {
        #[allow(missing_docs)]
        decimal_digits: Option<NonZeroU8>,
    },
    /// Format the hour and minute. Seconds and nanoseconds will be represented with the specified
    /// number of decimal digits, if any.
    Minute {
        #[allow(missing_docs)]
        decimal_digits: Option<NonZeroU8>,
    },
    /// Format the hour, minute, and second. Nanoseconds will be represented with the specified
    /// number of decimal digits, if any.
    Second {
        #[allow(missing_docs)]
        decimal_digits: Option<NonZeroU8>,
    },
}

/// The precision for the UTC offset.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OffsetPrecision {
    /// Format only the offset hour. Requires the offset minute to be zero.
    Hour,
    /// Format both the offset hour and minute.
    Minute,
}

/// Configuration for [`Iso8601`].
// This is only used as a const generic, so there's no need to have a number of implementations on
// it.
#[allow(missing_copy_implementations)]
#[doc(alias = "EncodedConfig")] // People will likely search for `EncodedConfig`, so show them this.
#[derive(Debug)]
pub struct Config {
    /// Which components, if any, will be formatted.
    pub(crate) formatted_components: FormattedComponents,
    /// Whether the format contains separators (such as `-` or `:`).
    pub(crate) use_separators: bool,
    /// Whether the year is six digits.
    pub(crate) year_is_six_digits: bool,
    /// The format used for the date.
    pub(crate) date_kind: DateKind,
    /// The precision and number of decimal digits present for the time.
    pub(crate) time_precision: TimePrecision,
    /// The precision for the UTC offset.
    pub(crate) offset_precision: OffsetPrecision,
}

impl Config {
    /// A configuration for the [`Iso8601`] format.
    ///
    /// The following is the default behavior:
    ///
    /// - The configuration can be used for both formatting and parsing.
    /// - The date, time, and UTC offset are all formatted.
    /// - Separators (such as `-` and `:`) are included.
    /// - The year contains four digits, such that the year must be between 0 and 9999.
    /// - The date uses the calendar format.
    /// - The time has precision to the second and nine decimal digits.
    /// - The UTC offset has precision to the minute.
    ///
    /// If you need different behavior, use the setter methods on this struct.
    pub const DEFAULT: Self = Self {
        formatted_components: FormattedComponents::DateTimeOffset,
        use_separators: true,
        year_is_six_digits: false,
        date_kind: DateKind::Calendar,
        time_precision: TimePrecision::Second {
            decimal_digits: NonZeroU8::new(9),
        },
        offset_precision: OffsetPrecision::Minute,
    };

    /// A configuration that can only be used for parsing. Using this to format a value is
    /// unspecified behavior.
    const PARSING: Self = Self {
        formatted_components: FormattedComponents::None,
        use_separators: false,
        year_is_six_digits: false,
        date_kind: DateKind::Calendar,
        time_precision: TimePrecision::Hour {
            decimal_digits: None,
        },
        offset_precision: OffsetPrecision::Hour,
    };

    /// Set whether the format the date, time, and/or UTC offset.
    pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self {
        Self {
            formatted_components,
            ..self
        }
    }

    /// Set whether the format contains separators (such as `-` or `:`).
    pub const fn set_use_separators(self, use_separators: bool) -> Self {
        Self {
            use_separators,
            ..self
        }
    }

    /// Set whether the year is six digits.
    pub const fn set_year_is_six_digits(self, year_is_six_digits: bool) -> Self {
        Self {
            year_is_six_digits,
            ..self
        }
    }

    /// Set the format used for the date.
    pub const fn set_date_kind(self, date_kind: DateKind) -> Self {
        Self { date_kind, ..self }
    }

    /// Set the precision and number of decimal digits present for the time.
    pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self {
        Self {
            time_precision,
            ..self
        }
    }

    /// Set the precision for the UTC offset.
    pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self {
        Self {
            offset_precision,
            ..self
        }
    }
}