114 lines
3.3 KiB
Rust
114 lines
3.3 KiB
Rust
use crate::error::{CoreError, CoreErrorKind, CoreResult};
|
|
use chrono::{Datelike, NaiveDate, Utc};
|
|
|
|
pub fn parse_metar_time(observation_time: &str) -> CoreResult<String> {
|
|
if observation_time.len() != 7 {
|
|
return Err(CoreError::new(
|
|
CoreErrorKind::InvalidInput,
|
|
format!("Unable to parse observation time in {}", observation_time),
|
|
));
|
|
}
|
|
let observation_day = match observation_time[0..2].parse::<u32>() {
|
|
Ok(day) => day,
|
|
Err(err) => return Err(err.into()),
|
|
};
|
|
let observation_hour = match observation_time[2..4].parse::<u32>() {
|
|
Ok(hour) => hour,
|
|
Err(err) => return Err(err.into()),
|
|
};
|
|
let observation_minute = match observation_time[4..6].parse::<u32>() {
|
|
Ok(minute) => minute,
|
|
Err(err) => return Err(err.into()),
|
|
};
|
|
let current_time = Utc::now().naive_utc();
|
|
let current_year = current_time.year();
|
|
let current_month = current_time.month();
|
|
let candidate_date = NaiveDate::from_ymd_opt(current_year, current_month, observation_day)
|
|
.ok_or_else(|| {
|
|
CoreError::new(
|
|
CoreErrorKind::InvalidInput,
|
|
format!(
|
|
"Invalid date with day {} for current month",
|
|
observation_day
|
|
),
|
|
)
|
|
})?;
|
|
let candidate_date = match candidate_date.and_hms_opt(observation_hour, observation_minute, 0) {
|
|
Some(date) => date,
|
|
None => {
|
|
return Err(CoreError::new(
|
|
CoreErrorKind::InvalidInput,
|
|
format!(
|
|
"Invalid time for time '{}': hour {}, minute {}",
|
|
observation_time, observation_hour, observation_minute
|
|
),
|
|
));
|
|
}
|
|
};
|
|
|
|
let obs_datetime = if candidate_date > current_time {
|
|
// Subtract one month. (Handle year rollover carefully.)
|
|
let (month, year) = if current_month == 1 {
|
|
(12, current_year - 1)
|
|
} else {
|
|
(current_month - 1, current_year)
|
|
};
|
|
|
|
let adjusted_date = NaiveDate::from_ymd_opt(year, month, observation_day).ok_or_else(|| {
|
|
CoreError::new(
|
|
CoreErrorKind::InvalidInput,
|
|
format!(
|
|
"Invalid date with day {} for month {}",
|
|
observation_day, month
|
|
),
|
|
)
|
|
})?;
|
|
adjusted_date
|
|
.and_hms_opt(observation_hour, observation_minute, 0)
|
|
.unwrap()
|
|
} else {
|
|
candidate_date
|
|
};
|
|
Ok(obs_datetime.format("%Y-%m-%dT%H:%M:00Z").to_string())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use chrono::NaiveDateTime;
|
|
|
|
#[test]
|
|
fn test_parse_metar_time() {
|
|
for day in 1..=31 {
|
|
for hour in 0..24 {
|
|
for minute in 0..60 {
|
|
// METAR form "DDHHMMZ"
|
|
let obs_time = format!("{:02}{:02}{:02}Z", day, hour, minute);
|
|
let result = parse_metar_time(&obs_time);
|
|
match result {
|
|
Ok(datetime_str) => {
|
|
// "YYYY-MM-DDTHH:MM:00Z"
|
|
assert_eq!(
|
|
datetime_str.len(),
|
|
20,
|
|
"Unexpected length for input {} yielded {}",
|
|
obs_time,
|
|
datetime_str
|
|
);
|
|
// Remove the trailing 'Z' and parse
|
|
let trimmed = &datetime_str[..19];
|
|
NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%dT%H:%M:%S").unwrap_or_else(|e| {
|
|
panic!(
|
|
"Parsing '{}' from input {} failed: {}",
|
|
trimmed, obs_time, e
|
|
)
|
|
});
|
|
}
|
|
Err(_err) => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|