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
/// Galileo week number.
pub type Wn = u16;
/// Time of week.
///
/// This represents the time of week in seconds.
pub type Tow = u32;

const SECS_IN_WEEK: Tow = 24 * 3600 * 7;
const SECS_PER_SUBFRAME: Tow = 30;

/// GST (Galileo System Time)
///
/// The Galileo System Time, stored as a week number and a time of week.
///
/// # Examples
/// ```
/// use galileo_osnma::Gst;
///
/// let gst = Gst::new(1177, 175767);
/// assert_eq!(gst.wn(), 1177);
/// assert_eq!(gst.tow(), 175767);
/// ```
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Gst {
    wn: Wn,
    tow: Tow,
}

impl Gst {
    /// Constructs a new GST from a week number and TOW.
    ///
    /// # Panics
    ///
    /// Panics if `tow` is greater or equal to 604800 (the number of
    /// seconds in a week).
    pub fn new(wn: Wn, tow: Tow) -> Self {
        assert!(tow < SECS_IN_WEEK);
        Gst { wn, tow }
    }

    /// Returns the week number of the GST.
    pub fn wn(&self) -> Wn {
        self.wn
    }

    /// Returns the time of week of the GST.
    pub fn tow(&self) -> Tow {
        self.tow
    }

    /// Adds `seconds` seconds to the GST.
    ///
    /// The GST corresponding to the sum is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// use galileo_osnma::Gst;
    ///
    /// let gst = Gst::new(1177, 175767);
    /// let gst_next_page = gst.add_seconds(2);
    /// assert_eq!(gst_next_page.wn(), 1177);
    /// assert_eq!(gst_next_page.tow(), 175769);
    ///
    /// assert_eq!(gst_next_page.add_seconds(-2), gst);
    /// ```
    pub fn add_seconds(&self, seconds: i32) -> Self {
        let secs_in_week = SECS_IN_WEEK.try_into().unwrap();
        let weeks = seconds / secs_in_week;
        let seconds = seconds - weeks * secs_in_week;
        let mut tow = i32::try_from(self.tow).unwrap() + seconds;
        let mut wn = self.wn + u16::try_from(weeks).unwrap();
        if tow < 0 {
            wn -= 1;
            tow += secs_in_week;
        } else if tow >= secs_in_week {
            wn += 1;
            tow -= secs_in_week;
        };
        assert!((0..secs_in_week).contains(&tow));
        Gst {
            tow: tow.try_into().unwrap(),
            wn,
        }
    }

    /// Returns the GST at the start of the subframe that contains `self`.
    ///
    /// The GST returned has the same week number as `self` and its time
    /// of week is the largest multiple of 30 seconds that is smaller or
    /// equal than the time of week of `self`.
    ///
    /// # Examples
    ///
    /// ```
    /// use galileo_osnma::Gst;
    ///
    /// let gst = Gst::new(1177, 175767);
    /// let subframe = gst.gst_subframe();
    /// assert_eq!(subframe.wn(), 1177);
    /// assert_eq!(subframe.tow(), 175740);
    pub fn gst_subframe(&self) -> Self {
        Gst {
            wn: self.wn,
            tow: self.tow / SECS_PER_SUBFRAME * SECS_PER_SUBFRAME,
        }
    }

    /// Returns `true` if `self` corresponds to the start of a subframe.
    ///
    /// A GST corresponds to the start of a subframe if its time of week is a
    /// multiple of 30 seconds.
    ///
    /// # Examples
    /// ```
    /// use galileo_osnma::Gst;
    ///
    /// let gst = Gst::new(1177, 175767);
    /// let subframe = gst.gst_subframe();
    /// assert_eq!(gst.is_subframe(), false);
    /// assert_eq!(subframe.is_subframe(), true);
    pub fn is_subframe(&self) -> bool {
        self.tow % SECS_PER_SUBFRAME == 0
    }
}