cargo_cargofmt/formatting/
datetime.rs1use std::borrow::Cow;
2
3use crate::toml::ScalarKind;
4use crate::toml::TokenKind;
5
6#[tracing::instrument]
11pub fn normalize_datetime_separators(tokens: &mut crate::toml::TomlTokens<'_>) {
12 const DATE_LEN: usize = 10;
14
15 for i in tokens.indices() {
16 let token = &mut tokens.tokens[i];
17 if token.kind == TokenKind::Scalar && token.scalar == Some(ScalarKind::DateTime) {
18 let raw_bytes = token.raw.as_bytes();
19 if raw_bytes.len() > DATE_LEN && matches!(raw_bytes[DATE_LEN], b' ' | b't') {
20 let mut new_raw = token.raw.to_string();
21 new_raw.replace_range(DATE_LEN..=DATE_LEN, "T");
22 token.raw = Cow::Owned(new_raw);
23 }
24 }
25 }
26}
27
28#[cfg(test)]
29mod test {
30 use snapbox::assert_data_eq;
31 use snapbox::str;
32 use snapbox::IntoData;
33
34 #[track_caller]
35 fn valid(input: &str, expected: impl IntoData) {
36 let mut tokens = crate::toml::TomlTokens::parse(input);
37 super::normalize_datetime_separators(&mut tokens);
38 let actual = tokens.to_string();
39
40 assert_data_eq!(&actual, expected);
41
42 let (_, errors) = toml::de::DeTable::parse_recoverable(&actual);
43 if !errors.is_empty() {
44 use std::fmt::Write as _;
45 let mut result = String::new();
46 writeln!(&mut result, "---").unwrap();
47 for error in errors {
48 writeln!(&mut result, "{error}").unwrap();
49 writeln!(&mut result, "---").unwrap();
50 }
51 panic!("failed to parse\n---\n{actual}\n{result}");
52 }
53 }
54
55 #[test]
56 fn empty() {
57 valid("", str![]);
58 }
59
60 #[test]
61 fn no_datetime() {
62 valid(
63 r#"
64name = "test"
65version = 1
66"#,
67 str![[r#"
68
69name = "test"
70version = 1
71
72"#]],
73 );
74 }
75
76 #[test]
77 fn datetime_with_t_unchanged() {
78 valid(
79 r#"
80created = 2025-12-26T10:30:00
81"#,
82 str![[r#"
83
84created = 2025-12-26T10:30:00
85
86"#]],
87 );
88 }
89
90 #[test]
91 fn datetime_with_space_normalized() {
92 valid(
93 r#"
94created = 2025-12-26 10:30:00
95"#,
96 str![[r#"
97
98created = 2025-12-26T10:30:00
99
100"#]],
101 );
102 }
103
104 #[test]
105 fn local_date_only_unchanged() {
106 valid(
107 r#"
108date = 2025-12-26
109"#,
110 str![[r#"
111
112date = 2025-12-26
113
114"#]],
115 );
116 }
117
118 #[test]
119 fn local_time_only_unchanged() {
120 valid(
121 r#"
122time = 10:30:00
123"#,
124 str![[r#"
125
126time = 10:30:00
127
128"#]],
129 );
130 }
131
132 #[test]
133 fn offset_datetime_with_space_normalized() {
134 valid(
135 r#"
136utc = 2025-12-26 10:30:00Z
137"#,
138 str![[r#"
139
140utc = 2025-12-26T10:30:00Z
141
142"#]],
143 );
144 }
145
146 #[test]
147 fn offset_datetime_with_timezone_normalized() {
148 valid(
149 r#"
150eastern = 2025-12-26 10:30:00-05:00
151"#,
152 str![[r#"
153
154eastern = 2025-12-26T10:30:00-05:00
155
156"#]],
157 );
158 }
159
160 #[test]
161 fn datetime_in_array() {
162 valid(
163 r#"
164dates = [
165 2025-12-26 10:30:00,
166 2025-12-27 11:00:00,
167]
168"#,
169 str![[r#"
170
171dates = [
172 2025-12-26T10:30:00,
173 2025-12-27T11:00:00,
174]
175
176"#]],
177 );
178 }
179
180 #[test]
181 fn datetime_in_inline_table() {
182 valid(
183 r#"
184event = { start = 2025-12-26 10:30:00, end = 2025-12-26 12:00:00 }
185"#,
186 str![[r#"
187
188event = { start = 2025-12-26T10:30:00, end = 2025-12-26T12:00:00 }
189
190"#]],
191 );
192 }
193
194 #[test]
195 fn mixed_datetime_formats() {
196 valid(
197 r#"
198with_t = 2025-12-26T10:30:00
199with_space = 2025-12-26 10:30:00
200date_only = 2025-12-26
201time_only = 10:30:00
202"#,
203 str![[r#"
204
205with_t = 2025-12-26T10:30:00
206with_space = 2025-12-26T10:30:00
207date_only = 2025-12-26
208time_only = 10:30:00
209
210"#]],
211 );
212 }
213
214 #[test]
215 fn datetime_with_fractional_seconds() {
216 valid(
217 r#"
218precise = 2025-12-26 10:30:00.123456
219"#,
220 str![[r#"
221
222precise = 2025-12-26T10:30:00.123456
223
224"#]],
225 );
226 }
227
228 #[test]
229 fn datetime_with_lowercase_t_normalized() {
230 valid(
233 r#"
234created = 2025-12-26t10:30:00
235"#,
236 str![[r#"
237
238created = 2025-12-26T10:30:00
239
240"#]],
241 );
242 }
243
244 #[test]
245 fn offset_datetime_with_positive_timezone() {
246 valid(
247 r#"
248tokyo = 2025-12-26 10:30:00+09:00
249"#,
250 str![[r#"
251
252tokyo = 2025-12-26T10:30:00+09:00
253
254"#]],
255 );
256 }
257}