1use std::fmt;
2use std::fmt::Formatter;
3use crate::event::{TickTimeEvent, TicketTimeEventValue};
4
5pub mod event;
6mod lib_tests;
7
8const LUNAR_MONTH_DURATION: usize = 30;
9const LUNAR_YEAR_DURATION: usize = LUNAR_MONTH_DURATION * 12;
10
11#[derive(Clone, Debug)]
13pub enum TickTimeType {
14 EarthLike {
16 seconds_per_tick: usize,
18 month_type: EarthLikeMonthType,
20 },
21 Custom {
24 seconds_per_tick: usize,
26 hours_in_a_day: usize,
28 months_durations: Vec<usize>,
30 seasons_durations: Vec<usize>,
32 week_duration: usize,
34 },
35}
36
37#[derive(Clone, Debug)]
39pub enum EarthLikeMonthType {
40 Lunar,
42 Real,
44}
45
46#[derive(Clone, Debug)]
48pub struct TickTimeOptions {
49 pub tick_time_type: TickTimeType,
51 pub compute_events: bool,
53}
54
55#[derive(Clone, Debug, Default)]
56struct TickTimeValue {
57 year: usize,
59 season: usize,
61 month: usize,
63 week: usize,
65 day: usize,
67 hour: usize,
69 minute: usize,
71 second: usize,
73}
74
75#[derive(Clone, Debug)]
79pub struct TickTime {
80 options: TickTimeOptions,
82 current_tick: usize,
84 values: TickTimeValue,
86 old_values: TickTimeValue,
88}
89
90impl TickTime {
91 pub fn init(current_tick: usize, options: TickTimeOptions) -> Result<Self, &'static str> {
94 if let Err(e) = verify_tick_time_type_values(&options.tick_time_type) {
95 return Err(e);
96 }
97 let mut tick_time = TickTime {
98 current_tick,
99 options,
100 values: Default::default(),
101 old_values: Default::default()
102 };
103 tick_time.apply_current_tick();
104 Ok(tick_time)
105 }
106
107 pub fn tick(&mut self) -> Option<TickTimeEvent> {
109 self.current_tick += 1;
110 self.apply_current_tick();
111 if self.options.compute_events {
112 Some(self.compute_event())
113 }else{
114 None
115 }
116 }
117
118 pub fn values(&self) -> (usize, usize, usize, usize, usize, usize, usize, usize) {
120 (
121 self.values.year,
122 self.values.season,
123 self.values.week,
124 self.values.month,
125 self.values.day,
126 self.values.hour,
127 self.values.minute,
128 self.values.second,
129 )
130 }
131
132 fn compute_event(&self) -> TickTimeEvent {
133 let mut event = TickTimeEvent::default();
134 let mut update_level = 0;
135
136 if self.old_values.year != self.values.year {
137 update_level += 1;
138 event.year_update = Some(TicketTimeEventValue{ old_value: self.old_values.year, new_value: self.values.year });
139 }
140
141 if update_level > 0 || self.old_values.season != self.values.season {
142 event.season_update = Some(TicketTimeEventValue{ old_value: self.old_values.season, new_value: self.values.season });
143 }
144
145 if update_level > 0 || self.old_values.week != self.values.week {
146 event.week_update = Some(TicketTimeEventValue{ old_value: self.old_values.week, new_value: self.values.week });
147 }
148
149 if update_level > 0 || self.old_values.month != self.values.month {
150 update_level += 1;
151 event.month_update = Some(TicketTimeEventValue{ old_value: self.old_values.month, new_value: self.values.month });
152 }
153
154 if update_level > 0 || self.old_values.day != self.values.day {
155 update_level += 1;
156 event.day_update = Some(TicketTimeEventValue{ old_value: self.old_values.day, new_value: self.values.day });
157 }
158
159 if update_level > 0 || self.old_values.hour != self.values.hour {
160 update_level += 1;
161 event.hour_update = Some(TicketTimeEventValue{ old_value: self.old_values.hour, new_value: self.values.hour });
162 }
163
164 if update_level > 0 || self.old_values.minute != self.values.minute {
165 update_level += 1;
166 event.minute_update = Some(TicketTimeEventValue{ old_value: self.old_values.minute, new_value: self.values.minute });
167 }
168
169 if update_level > 0 || self.old_values.second != self.values.second {
170 event.second_update = Some(TicketTimeEventValue{ old_value: self.old_values.second, new_value: self.values.second });
171 }
172
173 event
174 }
175
176 pub fn current_tick(&self) -> usize {
178 self.current_tick
179 }
180
181 pub fn year(&self) -> usize {
183 self.values.year
184 }
185
186 pub fn month(&self) -> usize {
188 self.values.month
189 }
190
191 pub fn season(&self) -> usize {
193 self.values.season
194 }
195
196 pub fn week(&self) -> usize {
198 self.values.week
199 }
200
201 pub fn day(&self) -> usize {
203 self.values.day
204 }
205
206 pub fn hour(&self) -> usize {
208 self.values.hour
209 }
210
211 pub fn minute(&self) -> usize {
213 self.values.minute
214 }
215
216 pub fn second(&self) -> usize {
218 self.values.second
219 }
220
221 fn apply_current_tick(&mut self) {
222 if self.options.compute_events {
223 self.old_values = self.values.clone();
224 }
225 match self.options.tick_time_type {
226 TickTimeType::EarthLike { .. } => { self.compute_earthlike_time(); }
227 TickTimeType::Custom { .. } => { self.compute_custom_date_time_values() }
228 }
229 }
230
231 fn compute_earthlike_time(&mut self) {
232 if let TickTimeType::EarthLike {
233 seconds_per_tick,
234 month_type,
235 } = &self.options.tick_time_type
236 {
237 let total_seconds = self.current_tick * seconds_per_tick;
238 self.values.second = total_seconds % 60;
239 self.values.minute = (total_seconds / 60) % 60;
240 self.values.hour = (total_seconds / 3600) % 24;
241 let total_days = total_seconds / 86400;
242 let (day, week, month, season, year) = match month_type {
243 EarthLikeMonthType::Lunar => compute_lunar_calendar_value(total_days),
244 EarthLikeMonthType::Real => compute_real_calendar_value(total_days)
245 };
246 self.values.day = day;
247 self.values.month = month;
248 self.values.week = week;
249 self.values.season = season;
250 self.values.year = year;
251 }
252 }
253
254 fn compute_custom_date_time_values(&mut self) {
255 if let TickTimeType::Custom {
256 seconds_per_tick, hours_in_a_day, months_durations, seasons_durations, week_duration
257 } = &self.options.tick_time_type
258 {
259 let total_seconds = self.current_tick * seconds_per_tick;
260 self.values.second = total_seconds % 60;
261 self.values.minute = (total_seconds / 60) % 60;
262 self.values.hour = (total_seconds / 3600) % hours_in_a_day;
263 let total_days = total_seconds / 3600 / hours_in_a_day;
264 let year_duration: usize = months_durations.iter().sum();
265 let (day, week, month, season, year) = {
266 let (day, current_year) = (total_days % year_duration, total_days / year_duration);
267
268 let (month, day_of_month) = find_correct_index_and_day_in_section(
269 day,
270 months_durations.len(),
271 months_durations,
272 );
273
274 let (season, _) = find_correct_index_and_day_in_section(
275 day,
276 seasons_durations.len(),
277 seasons_durations,
278 );
279
280 (day_of_month, day / week_duration, month, season % 4, current_year)
281 };
282 self.values.day = day;
283 self.values.week = week;
284 self.values.month = month;
285 self.values.season = season;
286 self.values.year = year;
287 }
288 }
289}
290
291impl fmt::Display for TickTime {
292 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
293 write!(f, "Tick time: [ Current tick: {}, Year: {}, Season: {}, Week: {} Month: {}, Day: {}, Hour: {}, Minute: {}, Second: {}]",
294 self.current_tick, self.year(), self.season(), self.week(), self.month(), self.day(), self.hour(), self.minute(), self.second())
295 }
296}
297
298fn compute_real_calendar_value(total_days: usize) -> (usize, usize, usize, usize, usize) {
299 let (day, current_year, is_leap_year) =
300 normalize_total_day_to_year_information(total_days);
301
302 let (month, day_of_month) = find_correct_index_and_day_in_section(
303 day,
304 12,
305 &get_month_duration(is_leap_year),
306 );
307
308 let (season, _) = find_correct_index_and_day_in_section(
309 day,
310 4,
311 &get_season_duration(is_leap_year),
312 );
313
314 (day_of_month, day / 7, month, season % 4, current_year)
315}
316
317fn compute_lunar_calendar_value(total_days: usize) -> (usize, usize, usize, usize, usize) {
318 (
319 total_days % LUNAR_YEAR_DURATION % LUNAR_MONTH_DURATION,
320 total_days % LUNAR_YEAR_DURATION / 7,
321 total_days % LUNAR_YEAR_DURATION / LUNAR_MONTH_DURATION,
322 (total_days % LUNAR_YEAR_DURATION) / (LUNAR_YEAR_DURATION / 4),
323 total_days / LUNAR_YEAR_DURATION,
324 )
325}
326
327fn get_month_duration(is_leap_year: bool) -> Vec<usize> {
328 vec![31, if is_leap_year { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
329}
330
331fn get_season_duration(is_leap_year: bool) -> Vec<usize> {
332 vec![if is_leap_year { 81 } else { 80 }, 92, 92, 91]
333}
334
335fn verify_tick_time_type_values(tick_time_type: &TickTimeType) -> Result<(), &'static str> {
336 match tick_time_type {
337 TickTimeType::EarthLike {
338 seconds_per_tick, ..
339 } => {
340 if *seconds_per_tick == 0 {
341 return Err("The minimum value for EarthLike::seconds_per_tick is 1");
342 }
343 }
344 TickTimeType::Custom {
345 seconds_per_tick, hours_in_a_day: _, months_durations, seasons_durations, ..
346 } => {
347 if *seconds_per_tick == 0 {
348 return Err("The minimum value for Custom::seconds_per_tick is 1");
349 }
350 if months_durations.iter().sum::<usize>() != seasons_durations.iter().sum::<usize>() {
351 return Err("The sum of values of Custom::months_durations and Custom::season_duration should be the same to keep consistent");
352 }
353 }
354 }
355 Ok(())
356}
357
358fn normalize_total_day_to_year_information(total_days: usize) -> (usize, usize, bool) {
359 let base_4_year_days = total_days % 1461;
360 let base_4_year_start = (total_days / 1461) * 4;
361 match base_4_year_days {
362 0..=365 => (base_4_year_days, base_4_year_start, true),
363 366..=730 => (base_4_year_days - 366, base_4_year_start + 1, false),
364 731..=1095 => (base_4_year_days - 731, base_4_year_start + 2, false),
365 _ => (base_4_year_days - 1095, base_4_year_start + 3, false),
366 }
367}
368
369fn find_correct_index_and_day_in_section(
370 day: usize,
371 max: usize,
372 array: &Vec<usize>,
373) -> (usize, usize) {
374 let (mut day_counter, mut stop, mut index) = (day, false, 0);
375 while !stop && index < max {
376 let next_month_duration = array[index];
377 if day_counter < next_month_duration {
378 stop = true;
379 } else {
380 day_counter -= next_month_duration;
381 index += 1;
382 }
383 }
384 (index, day_counter)
385}