1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use anyhow::Error;
use chrono::{DateTime, FixedOffset, NaiveDate, Utc};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::io::Write;

use crate::latitude::Latitude;
use crate::longitude::Longitude;
use crate::temperature::Temperature;
use crate::timestamp;

#[derive(Deserialize, Serialize, Debug)]
pub struct ForecastMain {
    pub temp: Temperature,
    pub feels_like: f64,
    pub temp_min: Temperature,
    pub temp_max: Temperature,
    pub pressure: i64,
    pub sea_level: i64,
    pub grnd_level: i64,
    pub humidity: i64,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct ForecastEntry {
    #[serde(with = "timestamp")]
    pub dt: DateTime<Utc>,
    pub main: ForecastMain,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct CityEntry {
    pub timezone: i32,
    #[serde(with = "timestamp")]
    pub sunrise: DateTime<Utc>,
    #[serde(with = "timestamp")]
    pub sunset: DateTime<Utc>,
}

#[derive(Deserialize, Serialize, Debug)]
pub struct WeatherForecast {
    pub list: Vec<ForecastEntry>,
    pub city: CityEntry,
}

impl WeatherForecast {
    /// Get Map of Date to High/Low temperatures
    /// ```
    /// # use anyhow::Error;
    /// # use std::io::{stdout, Write, Read};
    /// # use std::fs::File;
    /// # use std::convert::TryFrom;
    /// # use chrono::NaiveDate;
    /// use weather_util_rust::weather_forecast::WeatherForecast;
    /// use weather_util_rust::temperature::Temperature;
    /// # fn main() -> Result<(), Error> {
    /// # let mut buf = String::new();
    /// # let mut f = File::open("tests/forecast.json")?;
    /// # f.read_to_string(&mut buf)?;
    /// let data: WeatherForecast = serde_json::from_str(&buf)?;
    ///
    /// let high_low = data.get_high_low();
    /// assert_eq!(high_low.len(), 6);
    /// let date: NaiveDate = "2020-01-21".parse()?;
    /// assert_eq!(
    ///     high_low.get(&date),
    ///     Some(
    ///         &(
    ///             Temperature::try_from(272.65)?,
    ///             Temperature::try_from(266.76)?
    ///         )
    ///     )
    /// );
    /// # Ok(())
    /// # }
    /// ```
    pub fn get_high_low(&self) -> BTreeMap<NaiveDate, (Temperature, Temperature)> {
        let fo = FixedOffset::east(self.city.timezone);
        self.list.iter().fold(BTreeMap::new(), |mut hmap, entry| {
            let date = entry.dt.with_timezone(&fo).date().naive_local();
            let high = entry.main.temp_max;
            let low = entry.main.temp_min;

            if let Some((h, l)) = hmap.get(&date) {
                let high = if high > *h { high } else { *h };
                let low = if low < *l { low } else { *l };

                if (high, low) != (*h, *l) {
                    hmap.insert(date, (high, low));
                }
            } else {
                hmap.insert(date, (high, low));
            }
            hmap
        })
    }

    /// Get High and Low Temperatures for the Next Few Days
    /// ```
    /// # use anyhow::Error;
    /// # use std::io::{stdout, Write, Read};
    /// # use std::fs::File;
    /// # use std::convert::TryFrom;
    /// # use chrono::NaiveDate;
    /// use weather_util_rust::weather_forecast::WeatherForecast;
    /// # fn main() -> Result<(), Error> {
    /// # let mut buf = String::new();
    /// # let mut f = File::open("tests/forecast.json")?;
    /// # f.read_to_string(&mut buf)?;
    /// let data: WeatherForecast = serde_json::from_str(&buf)?;
    ///
    /// let mut buf = Vec::new();
    /// data.get_forecast(&mut buf)?;
    ///
    /// let buf = String::from_utf8(buf)?;
    /// assert!(buf.starts_with("\nForecast:"), buf);
    /// assert!(buf.contains("2020-01-23 High: 37.72 F / 3.18 C"));
    /// assert!(buf.contains("Low: 30.07 F / -1.07 C"));
    /// # Ok(())
    /// # }
    /// ```
    pub fn get_forecast<T: Write>(&self, buf: &mut T) -> Result<(), Error> {
        writeln!(buf, "\nForecast:")?;
        self.get_high_low()
            .into_iter()
            .map(|(d, (h, l))| {
                writeln!(
                    buf,
                    "\t{} {:30} {:30}",
                    d,
                    format!("High: {:0.2} F / {:0.2} C", h.fahrenheit(), h.celcius(),),
                    format!("Low: {:0.2} F / {:0.2} C", l.fahrenheit(), l.celcius(),),
                )
                .map(|_| ())
                .map_err(Into::into)
            })
            .collect()
    }
}