use crate::error::{CoreError, CoreErrorKind, CoreResult}; use chrono::{Datelike, NaiveDate, Utc}; pub fn parse_metar_time(observation_time: &str) -> CoreResult { 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::() { Ok(day) => day, Err(err) => return Err(err.into()), }; let observation_hour = match observation_time[2..4].parse::() { Ok(hour) => hour, Err(err) => return Err(err.into()), }; let observation_minute = match observation_time[4..6].parse::() { 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) => {} } } } } } }