From 86dd835a2a06b72ed83fb15ad12dc4f54905ca93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Sun, 6 Aug 2023 16:27:00 +0200 Subject: [PATCH] feat(src): implement Calendar::from_ticks for arbitrary year, add Calendar::to_ticks So far, Calendar::from_ticks could not correctly parse other month than the base month, this commit fixes that. Calendar::to_ticks may convert any Calendar date to seconds passed from the base year. The base year is remembered in Calendar instance. --- source/src/calendar.rs | 152 ++++++++++++++++++++++++++++++++++++++--- source/src/main.rs | 2 +- 2 files changed, 143 insertions(+), 11 deletions(-) diff --git a/source/src/calendar.rs b/source/src/calendar.rs index 085c324..f1a65fe 100644 --- a/source/src/calendar.rs +++ b/source/src/calendar.rs @@ -1,4 +1,9 @@ +use core::cmp::{max, min}; + +#[derive(Clone, PartialEq, Eq)] pub struct Calendar { + base_year: u16, + frozen: bool, hours: u8, minutes: u8, seconds: u8, @@ -9,40 +14,158 @@ pub struct Calendar { 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, } } - pub fn from_seconds(base: Calendar, seconds: u32) -> Self { - let total_seconds = seconds + base.seconds as u32; + /// 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 + base.minutes as u32; + let total_minutes = total_seconds / 60; let minutes = total_minutes % 60; - let total_hours = total_minutes / 60 + base.hours as u32; + let total_hours = total_minutes / 60; let hours = total_hours % 24; - let total_days = total_hours / 24 + base.day as u32; - let day = total_days % 30; // TODO... - // the current month and year has to be known prior to the calculation of the day + 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: base.month, - year: base.year, + 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 } @@ -95,8 +218,17 @@ impl Calendar { 2 if leap_year => 29, 2 => 28, 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, - 4 | 6 | 9 | 11 => 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 + } } diff --git a/source/src/main.rs b/source/src/main.rs index 5209bd2..5405d2a 100644 --- a/source/src/main.rs +++ b/source/src/main.rs @@ -311,7 +311,7 @@ fn main() -> ! { } let state = ClockState::new( - Calendar::from_seconds(Calendar::new(0, 0, 0, 1, 8, 2023), current_time), + Calendar::from_ticks(2023, current_time), MonoTimer::new(cp.DWT, cp.DCB, clocks), ); -- 2.49.0