1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29pub enum DateTimeError {
30 InvalidMonth,
32 InvalidDay,
34 InvalidHour,
36 InvalidMinute,
38 InvalidSecond,
40 InvalidWeekday,
42 InvalidYear,
44}
45
46impl core::fmt::Display for DateTimeError {
47 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 match self {
49 DateTimeError::InvalidMonth => write!(f, "invalid month"),
50 DateTimeError::InvalidDay => write!(f, "invalid day"),
51 DateTimeError::InvalidHour => write!(f, "invalid hour"),
52 DateTimeError::InvalidMinute => write!(f, "invalid minute"),
53 DateTimeError::InvalidSecond => write!(f, "invalid second"),
54 DateTimeError::InvalidWeekday => write!(f, "invalid weekday"),
55 DateTimeError::InvalidYear => write!(f, "invalid year"),
56 }
57 }
58}
59
60impl core::error::Error for DateTimeError {}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct DateTime {
71 year: u16,
73 month: u8,
75 day_of_month: u8,
77 hour: u8,
79 minute: u8,
81 second: u8,
83}
84
85impl DateTime {
86 pub fn new(
92 year: u16,
93 month: u8,
94 day_of_month: u8,
95 hour: u8,
96 minute: u8,
97 second: u8,
98 ) -> Result<Self, DateTimeError> {
99 let dt = DateTime {
100 year,
101 month,
102 day_of_month,
103 hour,
104 minute,
105 second,
106 };
107 dt.validate()?;
108 Ok(dt)
109 }
110
111 pub fn validate(&self) -> Result<(), DateTimeError> {
117 Self::validate_year(self.year)?;
118 Self::validate_month(self.month)?;
119 Self::validate_day(self.year, self.month, self.day_of_month)?;
120 Self::validate_hour(self.hour)?;
121 Self::validate_minute(self.minute)?;
122 Self::validate_second(self.second)?;
123 Ok(())
124 }
125
126 fn validate_year(year: u16) -> Result<(), DateTimeError> {
128 if year < 1970 {
129 return Err(DateTimeError::InvalidYear);
130 }
131 Ok(())
132 }
133
134 fn validate_month(month: u8) -> Result<(), DateTimeError> {
136 if month == 0 || month > 12 {
137 return Err(DateTimeError::InvalidMonth);
138 }
139 Ok(())
140 }
141
142 fn validate_day(year: u16, month: u8, day: u8) -> Result<(), DateTimeError> {
144 let max_day = days_in_month(year, month);
145 if day == 0 || day > max_day {
146 return Err(DateTimeError::InvalidDay);
147 }
148 Ok(())
149 }
150
151 fn validate_hour(hour: u8) -> Result<(), DateTimeError> {
153 if hour > 23 {
154 return Err(DateTimeError::InvalidHour);
155 }
156 Ok(())
157 }
158
159 fn validate_minute(minute: u8) -> Result<(), DateTimeError> {
161 if minute > 59 {
162 return Err(DateTimeError::InvalidMinute);
163 }
164 Ok(())
165 }
166
167 fn validate_second(second: u8) -> Result<(), DateTimeError> {
169 if second > 59 {
170 return Err(DateTimeError::InvalidSecond);
171 }
172 Ok(())
173 }
174
175 pub fn year(&self) -> u16 {
177 self.year
178 }
179
180 pub fn month(&self) -> u8 {
182 self.month
183 }
184
185 pub fn day_of_month(&self) -> u8 {
187 self.day_of_month
188 }
189
190 pub fn hour(&self) -> u8 {
192 self.hour
193 }
194
195 pub fn minute(&self) -> u8 {
197 self.minute
198 }
199
200 pub fn second(&self) -> u8 {
202 self.second
203 }
204
205 pub fn set_year(&mut self, year: u16) -> Result<(), DateTimeError> {
209 Self::validate_year(year)?;
210 Self::validate_day(year, self.month, self.day_of_month)?;
211 self.year = year;
212 Ok(())
213 }
214
215 pub fn set_month(&mut self, month: u8) -> Result<(), DateTimeError> {
219 Self::validate_month(month)?;
220 Self::validate_day(self.year, month, self.day_of_month)?;
221 self.month = month;
222 Ok(())
223 }
224
225 pub fn set_day_of_month(&mut self, day_of_month: u8) -> Result<(), DateTimeError> {
227 Self::validate_day(self.year, self.month, day_of_month)?;
228 self.day_of_month = day_of_month;
229 Ok(())
230 }
231
232 pub fn set_hour(&mut self, hour: u8) -> Result<(), DateTimeError> {
234 Self::validate_hour(hour)?;
235 self.hour = hour;
236 Ok(())
237 }
238
239 pub fn set_minute(&mut self, minute: u8) -> Result<(), DateTimeError> {
241 Self::validate_minute(minute)?;
242 self.minute = minute;
243 Ok(())
244 }
245
246 pub fn set_second(&mut self, second: u8) -> Result<(), DateTimeError> {
248 Self::validate_second(second)?;
249 self.second = second;
250 Ok(())
251 }
252
253 pub fn calculate_weekday(&self) -> Result<Weekday, DateTimeError> {
255 calculate_weekday(self.year, self.month, self.day_of_month)
256 }
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261#[repr(u8)]
262pub enum Weekday {
263 Sunday = 1,
265 Monday = 2,
267 Tuesday = 3,
269 Wednesday = 4,
271 Thursday = 5,
273 Friday = 6,
275 Saturday = 7,
277}
278
279impl Weekday {
280 pub fn from_number(n: u8) -> Result<Self, DateTimeError> {
282 match n {
283 1 => Ok(Self::Sunday),
284 2 => Ok(Self::Monday),
285 3 => Ok(Self::Tuesday),
286 4 => Ok(Self::Wednesday),
287 5 => Ok(Self::Thursday),
288 6 => Ok(Self::Friday),
289 7 => Ok(Self::Saturday),
290 _ => Err(DateTimeError::InvalidWeekday),
291 }
292 }
293
294 pub fn to_number(self) -> u8 {
296 self as u8
297 }
298
299 pub fn as_str(&self) -> &'static str {
301 match self {
302 Weekday::Sunday => "Sunday",
303 Weekday::Monday => "Monday",
304 Weekday::Tuesday => "Tuesday",
305 Weekday::Wednesday => "Wednesday",
306 Weekday::Thursday => "Thursday",
307 Weekday::Friday => "Friday",
308 Weekday::Saturday => "Saturday",
309 }
310 }
311}
312
313pub fn is_leap_year(year: u16) -> bool {
315 (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)
316}
317
318pub fn days_in_month(year: u16, month: u8) -> u8 {
320 match month {
321 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
322 4 | 6 | 9 | 11 => 30,
323 2 => {
324 if is_leap_year(year) {
325 29
326 } else {
327 28
328 }
329 }
330 _ => 0,
331 }
332}
333
334pub fn calculate_weekday(year: u16, month: u8, day_of_month: u8) -> Result<Weekday, DateTimeError> {
337 let (year, month) = if month < 3 {
338 (year - 1, month + 12)
339 } else {
340 (year, month)
341 };
342
343 let k = year % 100;
344 let j = year / 100;
345
346 let h =
347 (day_of_month as u16 + ((13 * (month as u16 + 1)) / 5) + k + (k / 4) + (j / 4) - 2 * j) % 7;
348
349 let weekday_num = ((h + 6) % 7) + 1;
351
352 Weekday::from_number(weekday_num as u8)
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn test_valid_datetime_creation() {
362 let dt = DateTime::new(2024, 3, 15, 14, 30, 45).unwrap();
363 assert_eq!(dt.year(), 2024);
364 assert_eq!(dt.month(), 3);
365 assert_eq!(dt.day_of_month(), 15);
366 assert_eq!(dt.hour(), 14);
367 assert_eq!(dt.minute(), 30);
368 assert_eq!(dt.second(), 45);
369 }
370
371 #[test]
372 fn test_invalid_year() {
373 let result = DateTime::new(1969, 1, 1, 0, 0, 0);
374 assert_eq!(result.unwrap_err(), DateTimeError::InvalidYear);
375 }
376
377 #[test]
378 fn test_invalid_month() {
379 assert_eq!(
380 DateTime::new(2024, 0, 1, 0, 0, 0).unwrap_err(),
381 DateTimeError::InvalidMonth
382 );
383 assert_eq!(
384 DateTime::new(2024, 13, 1, 0, 0, 0).unwrap_err(),
385 DateTimeError::InvalidMonth
386 );
387 }
388
389 #[test]
390 fn test_invalid_day() {
391 assert_eq!(
393 DateTime::new(2024, 2, 30, 0, 0, 0).unwrap_err(),
394 DateTimeError::InvalidDay
395 );
396
397 assert_eq!(
399 DateTime::new(2024, 1, 0, 0, 0, 0).unwrap_err(),
400 DateTimeError::InvalidDay
401 );
402
403 assert_eq!(
405 DateTime::new(2024, 4, 31, 0, 0, 0).unwrap_err(),
406 DateTimeError::InvalidDay
407 );
408 }
409
410 #[test]
411 fn test_invalid_hour() {
412 assert_eq!(
413 DateTime::new(2024, 1, 1, 24, 0, 0).unwrap_err(),
414 DateTimeError::InvalidHour
415 );
416 }
417
418 #[test]
419 fn test_invalid_minute() {
420 assert_eq!(
421 DateTime::new(2024, 1, 1, 0, 60, 0).unwrap_err(),
422 DateTimeError::InvalidMinute
423 );
424 }
425
426 #[test]
427 fn test_invalid_second() {
428 assert_eq!(
429 DateTime::new(2024, 1, 1, 0, 0, 60).unwrap_err(),
430 DateTimeError::InvalidSecond
431 );
432 }
433
434 #[test]
435 fn test_leap_year_february_29() {
436 assert!(DateTime::new(2024, 2, 29, 0, 0, 0).is_ok());
438
439 assert_eq!(
441 DateTime::new(2023, 2, 29, 0, 0, 0).unwrap_err(),
442 DateTimeError::InvalidDay
443 );
444 }
445
446 #[test]
447 fn test_setters_with_validation() {
448 let mut dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap();
449
450 assert!(dt.set_year(2025).is_ok());
452 assert_eq!(dt.year(), 2025);
453
454 assert!(dt.set_month(12).is_ok());
455 assert_eq!(dt.month(), 12);
456
457 assert!(dt.set_hour(23).is_ok());
458 assert_eq!(dt.hour(), 23);
459
460 assert_eq!(dt.set_year(1969), Err(DateTimeError::InvalidYear));
462 assert_eq!(dt.set_month(13), Err(DateTimeError::InvalidMonth));
463 assert_eq!(dt.set_hour(24), Err(DateTimeError::InvalidHour));
464 }
465
466 #[test]
467 fn test_leap_year_edge_cases_in_setters() {
468 let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap(); assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
472
473 assert_eq!(dt.year(), 2024);
475 assert_eq!(dt.day_of_month(), 29);
476 }
477
478 #[test]
479 fn test_month_day_validation_in_setters() {
480 let mut dt = DateTime::new(2024, 1, 31, 0, 0, 0).unwrap(); assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
484
485 assert_eq!(dt.month(), 1);
487 assert_eq!(dt.day_of_month(), 31);
488
489 assert!(dt.set_month(3).is_ok());
491 assert_eq!(dt.month(), 3);
492 }
493
494 #[test]
495 fn test_weekday_calculation() {
496 let dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap(); let weekday = dt.calculate_weekday().unwrap();
498 assert_eq!(weekday, Weekday::Monday); let dt = DateTime::new(2024, 12, 25, 0, 0, 0).unwrap();
501 let weekday = dt.calculate_weekday().unwrap();
502 assert_eq!(weekday, Weekday::Wednesday); }
504
505 #[test]
506 fn test_weekday_from_number() {
507 assert_eq!(Weekday::from_number(1).unwrap(), Weekday::Sunday);
508 assert_eq!(Weekday::from_number(2).unwrap(), Weekday::Monday);
509 assert_eq!(Weekday::from_number(7).unwrap(), Weekday::Saturday);
510 assert_eq!(Weekday::from_number(3).unwrap(), Weekday::Tuesday);
511
512 assert_eq!(
513 Weekday::from_number(0).unwrap_err(),
514 DateTimeError::InvalidWeekday
515 );
516 assert_eq!(
517 Weekday::from_number(8).unwrap_err(),
518 DateTimeError::InvalidWeekday
519 );
520 }
521
522 #[test]
523 fn test_weekday_to_number() {
524 assert_eq!(Weekday::Sunday.to_number(), 1);
525 assert_eq!(Weekday::Monday.to_number(), 2);
526 assert_eq!(Weekday::Saturday.to_number(), 7);
527 }
528
529 #[test]
530 fn test_weekday_as_str() {
531 assert_eq!(Weekday::Sunday.as_str(), "Sunday");
532 assert_eq!(Weekday::Monday.as_str(), "Monday");
533 assert_eq!(Weekday::Tuesday.as_str(), "Tuesday");
534 assert_eq!(Weekday::Wednesday.as_str(), "Wednesday");
535 assert_eq!(Weekday::Thursday.as_str(), "Thursday");
536 assert_eq!(Weekday::Friday.as_str(), "Friday");
537 assert_eq!(Weekday::Saturday.as_str(), "Saturday");
538 }
539
540 #[test]
541 fn test_calculate_weekday_known_dates() {
542 assert_eq!(calculate_weekday(2000, 1, 1).unwrap(), Weekday::Saturday);
544 assert_eq!(calculate_weekday(2024, 1, 1).unwrap(), Weekday::Monday);
545 assert_eq!(calculate_weekday(2025, 8, 15).unwrap(), Weekday::Friday);
546
547 assert_eq!(calculate_weekday(2024, 2, 29).unwrap(), Weekday::Thursday); }
550
551 #[test]
552 fn test_is_leap_year() {
553 assert!(is_leap_year(2024));
555 assert!(is_leap_year(2020));
556 assert!(is_leap_year(1996));
557
558 assert!(!is_leap_year(2023));
560 assert!(!is_leap_year(2021));
561 assert!(!is_leap_year(1999));
562
563 assert!(!is_leap_year(1900));
565 assert!(!is_leap_year(2100));
566
567 assert!(is_leap_year(2000));
569 assert!(is_leap_year(1600));
570 }
571
572 #[test]
573 fn test_days_in_month() {
574 assert_eq!(days_in_month(2024, 1), 31);
576
577 assert_eq!(days_in_month(2024, 2), 29);
579
580 assert_eq!(days_in_month(2023, 2), 28);
582
583 assert_eq!(days_in_month(2024, 4), 30);
585
586 assert_eq!(days_in_month(2024, 12), 31);
588
589 assert_eq!(days_in_month(2024, 13), 0);
591 assert_eq!(days_in_month(2024, 0), 0);
592 }
593
594 #[test]
595 fn test_setter_interdependency_edge_cases() {
596 let mut dt = DateTime::new(2023, 1, 31, 0, 0, 0).unwrap();
598 assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
599
600 let mut dt = DateTime::new(2023, 3, 31, 0, 0, 0).unwrap();
602 assert_eq!(dt.set_month(4), Err(DateTimeError::InvalidDay));
603
604 let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap();
606 assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
607
608 let mut dt = DateTime::new(2023, 2, 28, 0, 0, 0).unwrap();
610 assert!(dt.set_year(2024).is_ok());
611 }
612
613 #[test]
614 fn test_display_datetime_error() {
615 assert_eq!(format!("{}", DateTimeError::InvalidMonth), "invalid month");
616 assert_eq!(format!("{}", DateTimeError::InvalidDay), "invalid day");
617 assert_eq!(format!("{}", DateTimeError::InvalidHour), "invalid hour");
618 assert_eq!(
619 format!("{}", DateTimeError::InvalidMinute),
620 "invalid minute"
621 );
622 assert_eq!(
623 format!("{}", DateTimeError::InvalidSecond),
624 "invalid second"
625 );
626 assert_eq!(
627 format!("{}", DateTimeError::InvalidWeekday),
628 "invalid weekday"
629 );
630 assert_eq!(format!("{}", DateTimeError::InvalidYear), "invalid year");
631 }
632
633 #[test]
634 fn test_datetime_error_trait() {
635 let error = DateTimeError::InvalidMonth;
636 let _: &dyn core::error::Error = &error;
637 }
638
639 #[test]
640 fn test_boundary_values() {
641 assert!(DateTime::new(1970, 1, 1, 0, 0, 0).is_ok());
643
644 assert!(DateTime::new(2024, 12, 31, 23, 59, 59).is_ok());
646
647 assert!(DateTime::new(2024, 1, 1, 0, 0, 0).is_ok());
649 }
650
651 #[test]
652 fn test_february_edge_cases() {
653 assert!(DateTime::new(2024, 2, 28, 0, 0, 0).is_ok());
655
656 assert!(DateTime::new(2023, 2, 28, 0, 0, 0).is_ok());
658
659 assert_eq!(
661 DateTime::new(2023, 2, 29, 0, 0, 0).unwrap_err(),
662 DateTimeError::InvalidDay
663 );
664 }
665
666 #[test]
667 fn test_all_month_max_days() {
668 let year = 2023; for month in [1, 3, 5, 7, 8, 10, 12] {
672 assert!(DateTime::new(year, month, 31, 0, 0, 0).is_ok());
673 assert_eq!(
674 DateTime::new(year, month, 32, 0, 0, 0).unwrap_err(),
675 DateTimeError::InvalidDay
676 );
677 }
678
679 for month in [4, 6, 9, 11] {
681 assert!(DateTime::new(year, month, 30, 0, 0, 0).is_ok());
682 assert_eq!(
683 DateTime::new(year, month, 31, 0, 0, 0).unwrap_err(),
684 DateTimeError::InvalidDay
685 );
686 }
687 }
688
689 #[test]
690 fn test_set_day_of_month() {
691 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
692
693 assert!(dt.set_day_of_month(10).is_ok());
694 assert_eq!(dt.day_of_month, 10);
695 }
696
697 #[test]
698 fn test_all_setters_preserve_state_on_error() {
699 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
700 let original = dt;
701
702 assert!(dt.set_day_of_month(40).is_err());
704 assert_eq!(dt, original);
705
706 assert!(dt.set_minute(70).is_err());
707 assert_eq!(dt, original);
708
709 assert!(dt.set_second(70).is_err());
710 assert_eq!(dt, original);
711 }
712
713 #[test]
714 fn test_set_minute() {
715 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
716
717 assert!(dt.set_minute(10).is_ok());
718 assert_eq!(dt.minute, 10);
719 }
720
721 #[test]
722 fn test_set_second() {
723 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
724 assert!(dt.set_second(10).is_ok());
725 assert_eq!(dt.second, 10);
726 }
727}