~ruther/simple-clock

ref: 86dd835a2a06b72ed83fb15ad12dc4f54905ca93 simple-clock/source/src/calendar.rs -rw-r--r-- 7.8 KiB
86dd835a — František Boháček feat(src): implement Calendar::from_ticks for arbitrary year, add Calendar::to_ticks 1 year, 10 months ago
                                                                                
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use core::cmp::{max, min};

#[derive(Clone, PartialEq, Eq)]
pub struct Calendar {
    base_year: u16,
    frozen: bool,
    hours: u8,
    minutes: u8,
    seconds: u8,
    day: u8,
    month: u8,
    year: u16,
}

impl Calendar {
    pub fn new(hours: u8, minutes: u8, seconds: u8, day: u8, month: u8, year: u16) -> Self {
        Self::with_base_year(year, hours, minutes, seconds, day, month, year)
    }

    /// Calendar may have a base year, that will be used when calling [to_ticks](`Calendar::to_ticks`).
    pub fn with_base_year(
        base_year: u16,
        hours: u8,
        minutes: u8,
        seconds: u8,
        day: u8,
        month: u8,
        year: u16,
    ) -> Self {
        Self {
            base_year,
            hours,
            minutes,
            seconds,
            day,
            month,
            year,
            frozen: false,
        }
    }

    /// Calculate current date based off of the seconds elapsed since
    /// a base date
    pub fn from_ticks(base_year: u16, mut seconds: u32) -> Self {
        let total_seconds = seconds;
        let seconds = total_seconds % 60;

        let total_minutes = total_seconds / 60;
        let minutes = total_minutes % 60;

        let total_hours = total_minutes / 60;
        let hours = total_hours % 24;

        let elapsed_days = total_hours / 24;
        // elapsed_days / 365 / 4 subtracts leap days each 4 years, except for the last leap day
        // that could have been in the last 3 years, solution for that day is presented
        // below.
        let estimated_years_elapsed = (elapsed_days - elapsed_days / 365 / 4) / 365;
        let estimated_year = base_year as u32 + estimated_years_elapsed;

        // estimated_years_elapsed may not be correct, imagine a situation,
        // the current year is a leap year, ie. 2024, base is 2021 (1. 1. 00:00:00),
        // it's 31. 12. 2024, the problem is that the last leap year, 2024, is not captured
        // by elapsed_days / 365 / 4 in this case.
        // If we did not subtract a day from the elapsed days, the program would
        // incorrectly conclude it's 1. 1. 2025.
        let mut elapsed_days_without_leap_day_in_past_3_years = total_hours / 24;
        // uhh... naming of this property is 1. long, 2. still not correct,
        // the variable should have leap days that were in last 3 years,
        // but not in the current one.
        for current_year in max(base_year as u32, estimated_year - 3)..=estimated_year - 1 {
            elapsed_days_without_leap_day_in_past_3_years -=
                if Self::is_leap_year(current_year as u16) {
                    1
                } else {
                    0
                };
        }

        // this should be the correct year
        let years_elapsed = (elapsed_days_without_leap_day_in_past_3_years
            - elapsed_days_without_leap_day_in_past_3_years / 365 / 4)
            / 365;
        let year = base_year as u32 + years_elapsed;

        if year != estimated_year && Self::is_leap_year(year as u16) {
            // we went back one year, the current year is a leap year,
            // because leap day is included in Self::days_in_month,
            // it should not be present in the elapsed days...
            elapsed_days_without_leap_day_in_past_3_years += 1;
        }

        let leap_year = Self::is_leap_year(year as u16);

        let days_from_year_start =
            elapsed_days_without_leap_day_in_past_3_years - years_elapsed * 365 - years_elapsed / 4;
        let mut month = 1;
        let mut day = days_from_year_start;
        let mut total_days_in_month = Self::days_in_month(month, leap_year) as u32;
        while day >= total_days_in_month {
            day -= total_days_in_month;
            month += 1;
            total_days_in_month = Self::days_in_month(month, leap_year) as u32;
        }
        day += 1;

        Self {
            base_year,
            seconds: seconds as u8,
            minutes: minutes as u8,
            hours: hours as u8,
            day: day as u8,
            month: month as u8,
            year: year as u16,
            frozen: false,
        }
    }

    /// Converts the date into ticks, elapsed seconds
    /// from base year, that was specified upon creation of the calendar.
    pub fn to_ticks(&self) -> u32 {
        let mut ticks = 0u32;

        ticks += self.seconds as u32;
        ticks += self.minutes as u32 * 60;
        ticks += self.hours as u32 * 60 * 60;
        ticks += (self.day - 1) as u32 * 24 * 60 * 60;
        ticks +=
            Self::days_in_year(self.month, Self::is_leap_year(self.year)) as u32 * 24 * 60 * 60;
        let elapsed_years = self.year as u32 - self.base_year as u32;
        ticks += (elapsed_years * 365 + elapsed_years / 4) * 24 * 60 * 60;

        for year in max(self.year - 3, self.base_year)..=self.year - 1 {
            ticks += if Self::is_leap_year(year) {
                24 * 60 * 60
            } else {
                0
            };
        }

        ticks
    }

    /// Adds a second, correctly updating
    /// minutes, hours, days, month, year...
    pub fn second_elapsed(&mut self) {
        if self.frozen {
            return;
        }

        self.seconds = (self.seconds + 1) % 60;
        let minute_elapsed = self.seconds == 0;
        self.minutes = (self.minutes + if minute_elapsed { 1 } else { 0 }) % 60;

        let hour_elapsed = minute_elapsed && self.minutes == 0;
        self.hours = (self.hours + if hour_elapsed { 1 } else { 0 }) % 24;

        let day_elapsed = hour_elapsed && self.hours == 0;
        let day = self.day - 1 + if day_elapsed { 1 } else { 0 };
        let days_in_month = Self::days_in_month(self.month, Self::is_leap_year(self.year));
        self.day = day % days_in_month + 1;

        let month_elapsed = day_elapsed && self.day == 1;
        self.month = (self.month - 1 + if month_elapsed { 1 } else { 0 }) % 12 + 1;
        let year_elapsed = month_elapsed && self.month == 1;
        self.year += if year_elapsed { 1 } else { 0 };
    }

    pub fn hours(&self) -> u8 {
        self.hours
    }

    pub fn minutes(&self) -> u8 {
        self.minutes
    }

    pub fn seconds(&self) -> u8 {
        self.seconds
    }

    pub fn day(&self) -> u8 {
        self.day
    }

    pub fn month(&self) -> u8 {
        self.month
    }

    pub fn year(&self) -> u16 {
        self.year
    }

    pub fn second_elapsed(&mut self) {
        self.seconds = (self.seconds + 1) % 60;
        let minute_elapsed = self.seconds == 0;
        self.minutes = (self.minutes + if minute_elapsed { 1 } else { 0 }) % 60;

        let hour_elapsed = minute_elapsed && self.minutes == 0;
        self.hours = (self.hours + if hour_elapsed { 1 } else { 0 }) % 24;

        let day_elapsed = hour_elapsed && self.hours == 0;
        let day = self.day - 1 + if day_elapsed { 1 } else { 0 };
        let days_in_month = Self::days_in_month(self.month, Self::is_leap_year(self.year));
        self.day = day % days_in_month + 1;

        let month_elapsed = day_elapsed && self.day == 1;
        self.month = (self.month - 1 + if month_elapsed { 1 } else { 0 }) % 12 + 1;
        let year_elapsed = month_elapsed && self.month == 1;
        self.year += if year_elapsed { 1 } else { 0 };
    }

    fn is_leap_year(year: u16) -> bool {
        matches!(year % 4, 0 if year % 100 != 0 || year % 400 == 0)
    }

    fn days_in_month(month: u8, leap_year: bool) -> u8 {
        match month {
            2 if leap_year => 29,
            2 => 28,
            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
            4 | 6 | 9 | 11 => 30,
            _ => panic!(),
        }
    }

    fn days_in_year(before_month: u8, leap_year: bool) -> u16 {
        let mut days_in_year = 0u16;
        for i in 1..before_month {
            days_in_year += Self::days_in_month(i, leap_year) as u16;
        }

        days_in_year
    }
}
Do not follow this link