use crate::types::Language;
pub(super) fn language_rank(lang: Language) -> u8 {
match lang {
Language::SMIv2 => 2,
Language::SMIv1 => 1,
Language::SPPI => 1,
Language::Unknown => 0,
}
}
pub(super) fn normalize_timestamp(ts: &str) -> String {
let bytes = ts.as_bytes();
if !matches!(bytes.len(), 11 | 13)
|| bytes[bytes.len() - 1] != b'Z'
|| !bytes[..bytes.len() - 1].iter().all(u8::is_ascii_digit)
{
return String::new();
}
let digit = |i: usize| u32::from(bytes[i] - b'0');
let (year, offset) = if bytes.len() == 11 {
let year = digit(0) * 10 + digit(1);
(if year >= 70 { 1900 + year } else { 2000 + year }, 2)
} else {
(
digit(0) * 1000 + digit(1) * 100 + digit(2) * 10 + digit(3),
4,
)
};
let month = digit(offset) * 10 + digit(offset + 1);
let day = digit(offset + 2) * 10 + digit(offset + 3);
let hour = digit(offset + 4) * 10 + digit(offset + 5);
let minute = digit(offset + 6) * 10 + digit(offset + 7);
if !(1..=12).contains(&month)
|| day == 0
|| day > days_in_month(year, month)
|| hour > 23
|| minute > 59
{
return String::new();
}
if bytes.len() == 11 {
let prefix = if year < 2000 { "19" } else { "20" };
format!("{prefix}{ts}")
} else {
ts.to_string()
}
}
fn days_in_month(year: u32, month: u32) -> u32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 if (year.is_multiple_of(4) && !year.is_multiple_of(100)) || year.is_multiple_of(400) => {
29
}
2 => 28,
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::normalize_timestamp;
#[test]
fn normalizes_only_valid_ascii_ext_utc_time() {
assert_eq!(normalize_timestamp("0210180000Z"), "200210180000Z");
assert_eq!(normalize_timestamp("7001010000Z"), "197001010000Z");
assert_eq!(normalize_timestamp("200002290000Z"), "200002290000Z");
for timestamp in [
"",
"200210180000",
"200213180000Z",
"200102290000Z",
"aƩ1234567Z",
] {
assert_eq!(normalize_timestamp(timestamp), "", "{timestamp:?}");
}
}
}