~ruther/simple-clock

ref: 0248a3d23e58c50caa5085204db3d0211664da8e simple-clock/source/src/calendar.rs -rw-r--r-- 8.6 KiB
0248a3d2 — František Boháček docs: document new changes 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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
use core::cmp::max;

#[derive(Clone, PartialEq, Eq)]
pub struct Calendar {
    base_year: u16,
    ticks: u32,
    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 {
        let mut result = Self {
            base_year,
            hours,
            minutes,
            seconds,
            day,
            month,
            year,
            frozen: false,
            ticks: 0,
        };

        result.ticks = result.to_ticks();
        result
    }

    /// Calculate current date based off of the seconds elapsed since
    /// a base date
    pub fn from_ticks(base_year: u16, 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,
            ticks: seconds,
        }
    }

    /// 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
    }

    /// Like Calendar::to_ticks, but the ticks may get
    /// desynchronized at times. Use Calendar::to_ticks
    /// for correct ticks.
    pub fn estimated_ticks(&self) -> u32 {
        self.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 };

        self.ticks += 1;
    }

    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
    }

    /// Sets the current hour of the day,
    /// gets clamped to 0 - 24.
    pub fn set_hours(&mut self, hours: u8) {
        self.hours = hours.clamp(0, 23);
    }

    /// Sets the current minute of the hour,
    /// gets clamped to 0 - 59.
    pub fn set_minutes(&mut self, minutes: u8) {
        self.minutes = minutes.clamp(0, 59);
    }

    /// Sets the current seconds of the minute,
    /// gets clamped to 0 - 59.
    pub fn set_seconds(&mut self, seconds: u8) {
        self.seconds = seconds.clamp(0, 59);
    }

    /// Sets the current day of the month,
    /// gets clamped to 1 - days in the month.
    pub fn set_day(&mut self, day: u8) {
        self.day = day.clamp(
            1,
            Self::days_in_month(self.month, Self::is_leap_year(self.year)),
        );
    }

    /// Sets the current day of the month,
    /// gets clamped to 1 - days in the month.
    pub fn set_month(&mut self, month: u8) {
        self.month = month.clamp(1, 12);
    }

    /// Sets the current year,
    /// The minimum is the base year specified
    /// upon Calendar creation. Lower year
    /// will be adjusted to base year.
    pub fn set_year(&mut self, year: u16) {
        self.year = max(year, self.base_year);
    }

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

    pub fn freeze(&mut self) {
        self.frozen = true;
    }

    pub fn unfreeze(&mut self) {
        self.frozen = false;
    }

    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!("Month {month} does not exist, cannot get days in that month."),
        }
    }

    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