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
//! Models potentially stored in the archive.

use chrono::{Duration, NaiveDateTime};
use std::fmt;
use strum_macros::{EnumIter, EnumString, IntoStaticStr};

/// Models potentially stored in the archive.
#[derive(Clone, Copy, PartialEq, Eq, Debug, EnumString, IntoStaticStr, EnumIter, Hash)]
pub enum Model {
    /// The U.S. Global Forecast System
    #[strum(
        to_string = "gfs",
        serialize = "gfs3",
        serialize = "GFS",
        serialize = "GFS3"
    )]
    GFS,
    /// The U.S. North American Model
    #[strum(
        to_string = "nam",
        serialize = "namm",
        serialize = "NAM",
        serialize = "NAMM"
    )]
    NAM,
    /// The high resolution nest of the `NAM`
    #[strum(to_string = "nam4km", serialize = "NAM4KM")]
    NAM4KM,
}

impl fmt::Display for Model {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use crate::Model::*;

        match *self {
            GFS => write!(f, "{}", stringify!(GFS)),
            NAM => write!(f, "{}", stringify!(NAM)),
            NAM4KM => write!(f, "{}", stringify!(NAM4KM)),
        }
    }
}

impl Model {
    /// Get the number of hours between runs.
    pub fn hours_between_runs(self) -> i64 {
        match self {
            Model::GFS => 6,
            Model::NAM => 6,
            Model::NAM4KM => 6,
        }
    }

    /// Get the base hour of a model run.
    ///
    /// Most model run times are 0Z, 6Z, 12Z, 18Z. The base hour along with hours between runs
    /// allows you to reconstruct these times. Note that SREF starts at 03Z and runs every 6 hours,
    /// so it is different.
    pub fn base_hour(self) -> i64 {
        match self {
            _ => 0,
        }
    }

    /// Create an iterator of all the model runs between two times
    pub fn all_runs(
        self,
        start: &NaiveDateTime,
        end: &NaiveDateTime,
    ) -> impl Iterator<Item = NaiveDateTime> {
        debug_assert!(start <= end);

        let delta_t = self.hours_between_runs();

        //
        // Find a good start time.
        //
        let mut round_start = start.date().and_hms(0, 0, 0) + Duration::hours(self.base_hour());
        // Make sure we didn't jump ahead into the future.
        while round_start > *start {
            round_start -= Duration::hours(self.hours_between_runs());
        }
        // Make sure we didn't jumb too far back.
        while round_start < *start {
            round_start += Duration::hours(self.hours_between_runs());
        }

        // Ultimately make sure we start before we end.
        while round_start > *end {
            round_start -= Duration::hours(self.hours_between_runs());
        }

        let steps: i64 = (*end - round_start).num_hours() / self.hours_between_runs();

        (0..=steps).map(move |step| round_start + Duration::hours(step * delta_t))
    }

    /// Get a static str representation
    pub fn as_static_str(self) -> &'static str {
        self.into()
    }
}

/*--------------------------------------------------------------------------------------------------
                                          Unit Tests
--------------------------------------------------------------------------------------------------*/
#[cfg(test)]
mod unit {
    use super::*;

    use chrono::NaiveDate;

    #[test]
    fn test_all_runs() {
        assert_eq!(
            Model::GFS.hours_between_runs(),
            6,
            "test pre-condition failed."
        );

        let start = &NaiveDate::from_ymd(2018, 9, 1).and_hms(0, 0, 0);
        let end = &NaiveDate::from_ymd(2018, 9, 2).and_hms(0, 0, 0);
        assert_eq!(Model::GFS.all_runs(start, end).count(), 5);

        let start = &NaiveDate::from_ymd(2018, 9, 1).and_hms(0, 1, 0);
        let end = &NaiveDate::from_ymd(2018, 9, 2).and_hms(0, 0, 0);
        assert_eq!(Model::GFS.all_runs(start, end).count(), 4);
    }
}