diff --git a/data/empire_city_3.xml b/data/empire_city_3.xml
new file mode 100644
index 0000000..0eb91b6
--- /dev/null
+++ b/data/empire_city_3.xml
@@ -0,0 +1,185 @@
+
+
+
+
+ 2025-05-03T15:51:08-04:00
+ current observations and forecast
+
+
+ New York, NY
+ https://www.weather.gov/okx/
+ https://www.nws.noaa.gov/forecasts/xml/
+
+
+
+
+
+ point1
+ New York, NY
+
+ New York
+ 7
+
+https://forecast.weather.gov/MapClick.php?lat=40.71&lon=-74.01
+
+ k-p12h-n15-1
+ 2025-05-03T17:00:00-04:00
+ 2025-05-03T18:00:00-04:00
+ 2025-05-04T06:00:00-04:00
+ 2025-05-04T18:00:00-04:00
+ 2025-05-05T06:00:00-04:00
+ 2025-05-05T18:00:00-04:00
+ 2025-05-06T06:00:00-04:00
+ 2025-05-06T18:00:00-04:00
+ 2025-05-07T06:00:00-04:00
+ 2025-05-07T18:00:00-04:00
+ 2025-05-08T06:00:00-04:00
+ 2025-05-08T18:00:00-04:00
+ 2025-05-09T06:00:00-04:00
+ 2025-05-09T18:00:00-04:00
+ 2025-05-10T06:00:00-04:00
+
+
+
+ k-p24h-n8-1
+ 2025-05-03T17:00:00-04:00
+ 2025-05-04T06:00:00-04:00
+ 2025-05-05T06:00:00-04:00
+ 2025-05-06T06:00:00-04:00
+ 2025-05-07T06:00:00-04:00
+ 2025-05-08T06:00:00-04:00
+ 2025-05-09T06:00:00-04:00
+ 2025-05-10T06:00:00-04:00
+
+
+
+ k-p24h-n7-2
+ 2025-05-03T18:00:00-04:00
+ 2025-05-04T18:00:00-04:00
+ 2025-05-05T18:00:00-04:00
+ 2025-05-06T18:00:00-04:00
+ 2025-05-07T18:00:00-04:00
+ 2025-05-08T18:00:00-04:00
+ 2025-05-09T18:00:00-04:00
+
+
+
+
+
+
+ Daily Maximum Temperature
+ 78
+ 72
+ 62
+ 64
+ 69
+ 66
+ 59
+ 67
+
+
+
+ Daily Minimum Temperature
+ 61
+ 57
+ 60
+ 59
+ 59
+ 52
+ 52
+
+
+
+ 12 Hourly Probability of Precipitation
+ 60
+ 80
+ 60
+ 60
+ 90
+ 90
+ 90
+ 50
+
+
+ 40
+ 40
+ 30
+ 30
+
+
+
+
+ Weather Type, Coverage, Intensity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Conditions Iconhttps://forecast.weather.gov/newimages/medium/shra60.png
+ https://forecast.weather.gov/DualImage.php?i=nshra&j=ntsra&ip=80&jp=30
+ https://forecast.weather.gov/DualImage.php?i=shra&j=shra&ip=30&jp=60
+ https://forecast.weather.gov/newimages/medium/nshra60.png
+ https://forecast.weather.gov/newimages/medium/shra90.png
+ https://forecast.weather.gov/newimages/medium/nshra90.png
+ https://forecast.weather.gov/newimages/medium/shra90.png
+ https://forecast.weather.gov/DualImage.php?i=nscttsra&j=nshra&ip=50&jp=50
+ https://forecast.weather.gov/newimages/medium/sct.png
+ https://forecast.weather.gov/newimages/medium/nsct.png
+ https://forecast.weather.gov/DualImage.php?i=bkn&j=shra&jp=40
+ https://forecast.weather.gov/newimages/medium/nshra40.png
+ https://forecast.weather.gov/newimages/medium/shra30.png
+ https://forecast.weather.gov/newimages/medium/hi_nshwrs30.png
+ https://forecast.weather.gov/newimages/medium/sct.png
+
+
+
+ Watches, Warnings, and Advisories
+
+
+ https://forecast.weather.gov/showsigwx.php?warnzone=NYZ072&warncounty=NYC061&firewxzone=NYZ212&local_place1=New+York+NY&product1=Hazardous+Weather+Outlook
+
+
+
+
+
+ Text Forecast
+ Showers likely and possibly a thunderstorm. Partly sunny, with a steady temperature around 78. Southwest wind around 13 mph, with gusts as high as 25 mph. Chance of precipitation is 60%.
+ Showers and possibly a thunderstorm before 11pm, then a chance of showers and thunderstorms between 11pm and 2am, then a slight chance of showers after 2am. Low around 61. South wind around 10 mph. Chance of precipitation is 80%. New rainfall amounts between a tenth and quarter of an inch, except higher amounts possible in thunderstorms.
+ A chance of showers, then showers likely and possibly a thunderstorm after 2pm. Cloudy, with a high near 72. South wind 9 to 14 mph. Chance of precipitation is 60%. New rainfall amounts of less than a tenth of an inch, except higher amounts possible in thunderstorms.
+ Showers likely and possibly a thunderstorm. Cloudy, with a low around 57. East wind 9 to 11 mph. Chance of precipitation is 60%. New precipitation amounts between a quarter and half of an inch possible.
+ Showers, with thunderstorms also possible after 2pm. High near 62. East wind 11 to 14 mph. Chance of precipitation is 90%. New rainfall amounts between a half and three quarters of an inch possible.
+ Showers, with thunderstorms also possible after 2am. Low around 60. Chance of precipitation is 90%. New rainfall amounts between a half and three quarters of an inch possible.
+ Showers and possibly a thunderstorm before 8am, then showers between 8am and 2pm, then showers and possibly a thunderstorm after 2pm. High near 64. Chance of precipitation is 90%.
+ A chance of showers and thunderstorms before 8pm, then a chance of showers between 8pm and 2am. Mostly cloudy, with a low around 59. Chance of precipitation is 50%.
+ Mostly sunny, with a high near 69.
+ Partly cloudy, with a low around 59.
+ A 40 percent chance of showers. Partly sunny, with a high near 66.
+ A 40 percent chance of showers. Mostly cloudy, with a low around 52.
+ A 30 percent chance of showers. Partly sunny, with a high near 59.
+ A 30 percent chance of showers. Partly cloudy, with a low around 52.
+ Mostly sunny, with a high near 67.
+
+
+
+
+
+
+ point1
+
+ New York City, Central Park, NY
+ 154
+
+ https://www.nws.noaa.gov/data/obhistory/KNYC.htmlk-p1h-n1-12025-05-03T16:51:00-04:00845537Weather Type, Coverage, Intensity10.00Conditions Iconhttps://forecast.weather.gov/newimages/medium/sct.pngNANANA29.84
\ No newline at end of file
diff --git a/examples/file.rs b/examples/file.rs
index aed9d08..09e862b 100644
--- a/examples/file.rs
+++ b/examples/file.rs
@@ -5,7 +5,9 @@ fn main() -> Result<()> {
let mut args = std::env::args();
args.next().unwrap();
- let path = args.next().context("Usage: cargo run --example file -- untracked/data.xml")?;
+ let path = args
+ .next()
+ .context("Usage: cargo run --example file -- untracked/data.xml")?;
let s = std::fs::read_to_string(&path)?;
let dwml = weather_dot_gov_rs::Dwml::from_str(&s)?;
diff --git a/src/dwml.rs b/src/dwml.rs
index 301b333..295df25 100644
--- a/src/dwml.rs
+++ b/src/dwml.rs
@@ -10,6 +10,32 @@ pub struct Dwml {
pub data: (Forecast, CurrentObservations),
}
+impl Dwml {
+ /// Returns the current "high" temperature.
+ ///
+ /// Panics if the data doesn't show a current high
+ pub fn current_maximum(&self) -> TempWithUnit {
+ self.forecast()
+ .parameters
+ .temp_by_type(TempType::Maximum)
+ .first()
+ }
+
+ /// Returns the current "low" temperature.
+ ///
+ /// Panics if the data doesn't show a current low
+ pub fn current_minimum(&self) -> TempWithUnit {
+ self.forecast()
+ .parameters
+ .temp_by_type(TempType::Minimum)
+ .first()
+ }
+
+ fn forecast(&self) -> &Forecast {
+ &self.data.0
+ }
+}
+
impl std::str::FromStr for Dwml {
type Err = quick_xml::DeError;
@@ -51,24 +77,6 @@ pub struct TimeLayout {
pub start_valid_time: Vec,
}
-impl TimeLayout {
- /// For a given time, returns the index of the interval that the time falls in.
- fn lookup(&self, t: &chrono::DateTime) -> Option {
- for i in 0..self.start_valid_time.len() - 1 {
- let Some(start) = self.start_valid_time.get(i) else {
- break;
- };
- let Some(stop) = self.start_valid_time.get(i + 1) else {
- break;
- };
- if start.value.0 <= *t && *t < stop.value.0 {
- return Some(i);
- }
- }
- None
- }
-}
-
#[derive(Debug, Deserialize, PartialEq)]
pub enum TimeCoordinate {
#[serde(rename = "local")]
@@ -86,7 +94,7 @@ pub struct StartValidTime {
#[derive(Debug, Deserialize, PartialEq)]
#[serde(try_from = "String")]
-pub struct DateTime (pub chrono::DateTime);
+pub struct DateTime(pub chrono::DateTime);
impl TryFrom for DateTime {
type Error = chrono::ParseError;
@@ -101,6 +109,26 @@ impl TryFrom for DateTime {
#[derive(Debug, Deserialize, PartialEq)]
pub struct CurrentObservations {
pub location: Location,
+ pub parameters: CurrentParameters,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct CurrentParameters {
+ pub temperature: (Temperature, Temperature),
+ pub humidity: Humidity,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Humidity {
+ #[serde(rename = "@type")]
+ pub humidity_type: HumidityType,
+ pub value: u8,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub enum HumidityType {
+ #[serde(rename = "relative")]
+ Relative,
}
#[derive(Debug, Deserialize, PartialEq)]
@@ -126,7 +154,7 @@ pub struct Source {
#[derive(Debug, Deserialize, PartialEq)]
pub struct Parameters {
- pub temperature: (Temperature, Temperature),
+ pub temperature: [Temperature; 2],
#[serde(rename = "probability-of-precipitation")]
pub probability_of_precipitation: ProbabilityOfPrecipitation,
@@ -134,6 +162,22 @@ pub struct Parameters {
pub weather: Weather,
}
+impl Parameters {
+ fn temp_by_type(&self, temp_type: TempType) -> &Temperature {
+ for temp in &self.temperature {
+ if temp.temp_type == temp_type {
+ return temp;
+ }
+ }
+ panic!("Couldn't find a Temperature with the given TempType");
+ }
+}
+
+pub struct TempWithUnit {
+ pub units: TempUnits,
+ pub value: i32,
+}
+
#[derive(Debug, Deserialize, PartialEq)]
pub struct Temperature {
#[serde(rename = "@type")]
@@ -148,6 +192,19 @@ pub struct Temperature {
pub value: Vec,
}
+impl Temperature {
+ fn first(&self) -> TempWithUnit {
+ let value = *self
+ .value
+ .first()
+ .expect("temperatures should always have at least one value");
+ TempWithUnit {
+ units: self.units,
+ value,
+ }
+ }
+}
+
#[derive(Debug, Deserialize, PartialEq)]
pub struct ProbabilityOfPrecipitation {
#[serde(rename = "@time-layout")]
@@ -180,7 +237,7 @@ pub struct PrecipitationValue {
pub value: Option,
}
-#[derive(Debug, Deserialize, PartialEq)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum TempType {
#[serde(rename = "apparent")]
Apparent,
@@ -192,7 +249,7 @@ pub enum TempType {
Maximum,
}
-#[derive(Debug, Deserialize, PartialEq)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum TempUnits {
Celsius,
Fahrenheit,
@@ -203,24 +260,16 @@ pub struct TimeLayoutKey(String);
#[cfg(test)]
mod tests {
- use anyhow::{Context as _, Result};
use super::*;
+ use anyhow::{Context as _, Result};
#[test]
fn empire_city() {
let s = include_str!("../data/empire_city.xml");
let dwml: Dwml = quick_xml::de::from_str(s).unwrap();
- for (input, expected) in [
- ("2025-01-14T18:59:59-05:00", None),
- ("2025-01-14T19:00:00-05:00", Some(0)),
- ("2025-01-21T05:59:59-05:00", Some(12)),
- ("2025-01-21T06:00:00-05:00", None),
- ] {
- let dt = chrono::DateTime::parse_from_rfc3339(input).unwrap();
- let actual = dwml.data.0.time_layout.0.lookup(&dt);
- assert_eq!(actual, expected);
- }
+ assert_eq!(dwml.current_maximum().value, 33);
+ assert_eq!(dwml.current_minimum().value, 26);
}
#[test]
@@ -237,10 +286,8 @@ mod tests {
#[test]
fn date() -> Result<()> {
- for s in [
- "2025-01-01T23:00:00-00:00"
- ] {
- let _ = chrono::DateTime::parse_from_rfc3339(&s).context(s).unwrap();
+ for s in ["2025-01-01T23:00:00-00:00", "2025-01-01T23:00:00-00:00"] {
+ let _ = chrono::DateTime::parse_from_rfc3339(s).context(s).unwrap();
}
Ok(())
}