use chrono::FixedOffset;
use crate::error::{TransformError, TransformErrorKind};
pub(super) fn looks_like_timezone(value: &str) -> bool {
if value.eq_ignore_ascii_case("utc") || value == "Z" {
return true;
}
matches!(value.chars().next(), Some('+') | Some('-'))
}
pub(super) fn parse_timezone(value: &str, path: &str) -> Result<FixedOffset, TransformError> {
if value.eq_ignore_ascii_case("utc") || value == "Z" {
return FixedOffset::east_opt(0).ok_or_else(|| {
TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path)
});
}
let (sign, rest) = match value.chars().next() {
Some('+') => (1i32, &value[1..]),
Some('-') => (-1i32, &value[1..]),
_ => {
return Err(TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path));
}
};
let (hours, minutes) = if let Some((h, m)) = rest.split_once(':') {
let hours = h.parse::<i32>().ok();
let minutes = m.parse::<i32>().ok();
match (hours, minutes) {
(Some(hours), Some(minutes)) => (hours, minutes),
_ => {
return Err(TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path));
}
}
} else {
match rest.len() {
2 => {
let hours = rest.parse::<i32>().ok();
match hours {
Some(hours) => (hours, 0),
None => {
return Err(TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path));
}
}
}
4 => {
let hours = rest[..2].parse::<i32>().ok();
let minutes = rest[2..].parse::<i32>().ok();
match (hours, minutes) {
(Some(hours), Some(minutes)) => (hours, minutes),
_ => {
return Err(TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path));
}
}
}
_ => {
return Err(TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path));
}
}
};
if !(0..=23).contains(&hours) || !(0..=59).contains(&minutes) {
return Err(TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path));
}
let offset_seconds = sign * (hours * 3600 + minutes * 60);
FixedOffset::east_opt(offset_seconds).ok_or_else(|| {
TransformError::new(
TransformErrorKind::ExprError,
"timezone must be UTC or an offset like +09:00",
)
.with_path(path)
})
}