use alloc::{
format,
string::{String, ToString},
};
pub(crate) fn date_to_json(text: &str) -> String {
match text.len() == 8 && text.bytes().all(|b| b.is_ascii_digit()) {
true => format!("{}-{}-{}", &text[..4], &text[4..6], &text[6..8]),
false => text.to_string(),
}
}
pub(crate) fn date_from_json(text: &str) -> Option<String> {
let bytes = text.as_bytes();
(bytes.len() == 10 && bytes[4] == b'-' && bytes[7] == b'-')
.then(|| format!("{}{}{}", &text[..4], &text[5..7], &text[8..10]))
}
pub(crate) fn time_to_json(text: &str) -> String {
let (body, zulu) = split_zulu(text);
match body.len() == 6 && body.bytes().all(|b| b.is_ascii_digit()) {
true => format!("{}:{}:{}{zulu}", &body[..2], &body[2..4], &body[4..6]),
false => text.to_string(),
}
}
pub(crate) fn time_from_json(text: &str) -> Option<String> {
let (body, zulu) = split_zulu(text);
let bytes = body.as_bytes();
(bytes.len() == 8 && bytes[2] == b':' && bytes[5] == b':')
.then(|| format!("{}{}{}{zulu}", &body[..2], &body[3..5], &body[6..8]))
}
pub(crate) fn datetime_to_json(text: &str) -> String {
let (body, zulu) = split_zulu(text);
let Some((date, time)) = split_t(body) else {
return date_to_json(body) + zulu;
};
format!("{}T{}{zulu}", date_to_json(date), time_to_json(time))
}
pub(crate) fn datetime_from_json(text: &str) -> Option<String> {
let (body, zulu) = split_zulu(text);
let Some((date, time)) = split_t(body) else {
return date_from_json(body).map(|date| format!("{date}{zulu}"));
};
let (rewritten_date, rewritten_time) = (date_from_json(date), time_from_json(time));
if rewritten_date.is_none() && rewritten_time.is_none() {
return None;
}
Some(format!(
"{}T{}{zulu}",
rewritten_date.as_deref().unwrap_or(date),
rewritten_time.as_deref().unwrap_or(time),
))
}
pub(crate) fn period_to_json(text: &str) -> String {
match text.split_once('/') {
Some((start, end)) => format!("{}/{}", datetime_to_json(start), datetime_to_json(end)),
None => datetime_to_json(text),
}
}
pub(crate) fn period_from_json(text: &str) -> Option<String> {
let Some((start, end)) = text.split_once('/') else {
return datetime_from_json(text);
};
let (rewritten_start, rewritten_end) = (datetime_from_json(start), datetime_from_json(end));
if rewritten_start.is_none() && rewritten_end.is_none() {
return None;
}
Some(format!(
"{}/{}",
rewritten_start.as_deref().unwrap_or(start),
rewritten_end.as_deref().unwrap_or(end),
))
}
pub(crate) fn offset_to_json(text: &str) -> String {
let (sign, digits) = match text.as_bytes().first() {
Some(b'+') | Some(b'-') => (&text[..1], &text[1..]),
_ => ("", text),
};
if !digits.bytes().all(|b| b.is_ascii_digit()) {
return text.to_string();
}
match digits.len() {
4 => format!("{sign}{}:{}", &digits[..2], &digits[2..4]),
6 => format!("{sign}{}:{}:{}", &digits[..2], &digits[2..4], &digits[4..6]),
_ => text.to_string(),
}
}
pub(crate) fn offset_from_json(text: &str) -> Option<String> {
text.contains(':').then(|| text.replace(':', ""))
}
fn split_zulu(text: &str) -> (&str, &str) {
match text.as_bytes().last() {
Some(b'Z') | Some(b'z') => (&text[..text.len() - 1], &text[text.len() - 1..]),
_ => (text, ""),
}
}
fn split_t(text: &str) -> Option<(&str, &str)> {
let at = text.find(['T', 't'])?;
Some((&text[..at], &text[at + 1..]))
}