Skip to content

Commit a494102

Browse files
committed
Timestamp: Support dot-separated dates
1 parent 436d99c commit a494102

2 files changed

Lines changed: 49 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Fixed
1010

1111
- **IFF**: Undersized ID3v2 chunks will no longer error outside of strict mode ([PR](https://github.com/Serial-ATA/lofty-rs/pull/644))
12+
- **Timestamp**: Support dot-separated dates (e.g. `2024.06.03`) ([issue](https://github.com/Serial-ATA/lofty-rs/issues/647)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/648))
1213

1314
## [0.24.0] - 2026-04-12
1415

lofty/src/tag/items/timestamp.rs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ impl Timestamp {
9595
/// The maximum length of a timestamp in bytes
9696
pub const MAX_LENGTH: usize = 19;
9797

98-
const SEPARATORS: [u8; 3] = [b'-', b'T', b':'];
98+
const SEPARATORS: [u8; 4] = [b'-', b'.', b'T', b':'];
9999

100100
/// Read a [`Timestamp`]
101101
///
@@ -178,23 +178,29 @@ impl Timestamp {
178178
loop {
179179
timestamp.month = read_segment!(Self::segment::<2>(
180180
reader,
181-
timestamp_contains_separators.then_some(b'-'),
181+
timestamp_contains_separators.then_some(&Self::SEPARATORS[..2]),
182182
parse_mode
183183
));
184184
timestamp.day = read_segment!(Self::segment::<2>(
185185
reader,
186-
timestamp_contains_separators.then_some(b'-'),
186+
timestamp_contains_separators.then_some(&Self::SEPARATORS[..2]),
187+
parse_mode
188+
));
189+
timestamp.hour = read_segment!(Self::segment::<2>(
190+
reader,
191+
Some(core::slice::from_ref(&Self::SEPARATORS[2])),
187192
parse_mode
188193
));
189-
timestamp.hour = read_segment!(Self::segment::<2>(reader, Some(b'T'), parse_mode));
190194
timestamp.minute = read_segment!(Self::segment::<2>(
191195
reader,
192-
timestamp_contains_separators.then_some(b':'),
196+
timestamp_contains_separators
197+
.then_some(core::slice::from_ref(&Self::SEPARATORS[3])),
193198
parse_mode
194199
));
195200
timestamp.second = read_segment!(Self::segment::<2>(
196201
reader,
197-
timestamp_contains_separators.then_some(b':'),
202+
timestamp_contains_separators
203+
.then_some(core::slice::from_ref(&Self::SEPARATORS[3])),
198204
parse_mode
199205
));
200206
break;
@@ -205,7 +211,7 @@ impl Timestamp {
205211

206212
fn segment<const SIZE: usize>(
207213
content: &mut &[u8],
208-
sep: Option<u8>,
214+
sep: Option<&[u8]>,
209215
parse_mode: ParsingMode,
210216
) -> Result<(u16, usize)> {
211217
const STOP_PARSING: (u16, usize) = (0, 0);
@@ -216,11 +222,20 @@ impl Timestamp {
216222

217223
if let Some(sep) = sep {
218224
let byte = content.read_u8()?;
219-
if byte != sep {
220-
if parse_mode == ParsingMode::Strict {
221-
err!(BadTimestamp("Expected a separator"))
222-
}
223-
return Ok(STOP_PARSING);
225+
match sep.iter().position(|s| *s == byte) {
226+
// The first separator in the list is the only *strictly* valid one (by ISO 8601 standards).
227+
// Some encoders may prefer other separators, which are harmless to pass through in
228+
// other modes.
229+
Some(pos) if pos > 0 && parse_mode == ParsingMode::Strict => {
230+
err!(BadTimestamp("Unexpected separator"))
231+
},
232+
Some(_) => {},
233+
None => {
234+
if parse_mode == ParsingMode::Strict {
235+
err!(BadTimestamp("Expected a separator"))
236+
}
237+
return Ok(STOP_PARSING);
238+
},
224239
}
225240
}
226241

@@ -626,4 +641,25 @@ mod tests {
626641
})
627642
);
628643
}
644+
645+
#[test_log::test]
646+
fn timestamp_dot_separators() {
647+
let timestamp = "2024.06.03";
648+
649+
let parsed_timestamp_strict =
650+
Timestamp::parse(&mut timestamp.as_bytes(), ParsingMode::Strict);
651+
assert!(parsed_timestamp_strict.is_err());
652+
653+
let parsed_timestamp_best_attempt =
654+
Timestamp::parse(&mut timestamp.as_bytes(), ParsingMode::BestAttempt).unwrap();
655+
assert_eq!(
656+
parsed_timestamp_best_attempt,
657+
Some(Timestamp {
658+
year: 2024,
659+
month: Some(6),
660+
day: Some(3),
661+
..Timestamp::default()
662+
})
663+
);
664+
}
629665
}

0 commit comments

Comments
 (0)