1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum DateTimeError {
29 InvalidMonth,
31 InvalidDay,
33 InvalidHour,
35 InvalidMinute,
37 InvalidSecond,
39 InvalidWeekday,
41 InvalidYear,
43}
44
45impl core::fmt::Display for DateTimeError {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 match self {
48 DateTimeError::InvalidMonth => write!(f, "invalid month"),
49 DateTimeError::InvalidDay => write!(f, "invalid day"),
50 DateTimeError::InvalidHour => write!(f, "invalid hour"),
51 DateTimeError::InvalidMinute => write!(f, "invalid minute"),
52 DateTimeError::InvalidSecond => write!(f, "invalid second"),
53 DateTimeError::InvalidWeekday => write!(f, "invalid weekday"),
54 DateTimeError::InvalidYear => write!(f, "invalid year"),
55 }
56 }
57}
58
59impl core::error::Error for DateTimeError {}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct DateTime {
70 year: u16,
72 month: u8,
74 day_of_month: u8,
76 hour: u8,
78 minute: u8,
80 second: u8,
82}
83
84impl DateTime {
85 pub fn new(
91 year: u16,
92 month: u8,
93 day_of_month: u8,
94 hour: u8,
95 minute: u8,
96 second: u8,
97 ) -> Result<Self, DateTimeError> {
98 let dt = DateTime {
99 year,
100 month,
101 day_of_month,
102 hour,
103 minute,
104 second,
105 };
106 dt.validate()?;
107 Ok(dt)
108 }
109
110 pub fn validate(&self) -> Result<(), DateTimeError> {
116 Self::validate_year(self.year)?;
117 Self::validate_month(self.month)?;
118 Self::validate_day(self.year, self.month, self.day_of_month)?;
119 Self::validate_hour(self.hour)?;
120 Self::validate_minute(self.minute)?;
121 Self::validate_second(self.second)?;
122 Ok(())
123 }
124
125 fn validate_year(year: u16) -> Result<(), DateTimeError> {
127 if year < 1970 {
128 return Err(DateTimeError::InvalidYear);
129 }
130 Ok(())
131 }
132
133 fn validate_month(month: u8) -> Result<(), DateTimeError> {
135 if month == 0 || month > 12 {
136 return Err(DateTimeError::InvalidMonth);
137 }
138 Ok(())
139 }
140
141 fn validate_day(year: u16, month: u8, day: u8) -> Result<(), DateTimeError> {
143 let max_day = days_in_month(year, month);
144 if day == 0 || day > max_day {
145 return Err(DateTimeError::InvalidDay);
146 }
147 Ok(())
148 }
149
150 fn validate_hour(hour: u8) -> Result<(), DateTimeError> {
152 if hour > 23 {
153 return Err(DateTimeError::InvalidHour);
154 }
155 Ok(())
156 }
157
158 fn validate_minute(minute: u8) -> Result<(), DateTimeError> {
160 if minute > 59 {
161 return Err(DateTimeError::InvalidMinute);
162 }
163 Ok(())
164 }
165
166 fn validate_second(second: u8) -> Result<(), DateTimeError> {
168 if second > 59 {
169 return Err(DateTimeError::InvalidSecond);
170 }
171 Ok(())
172 }
173
174 pub fn year(&self) -> u16 {
176 self.year
177 }
178
179 pub fn month(&self) -> u8 {
181 self.month
182 }
183
184 pub fn day_of_month(&self) -> u8 {
186 self.day_of_month
187 }
188
189 pub fn hour(&self) -> u8 {
191 self.hour
192 }
193
194 pub fn minute(&self) -> u8 {
196 self.minute
197 }
198
199 pub fn second(&self) -> u8 {
201 self.second
202 }
203
204 pub fn set_year(&mut self, year: u16) -> Result<(), DateTimeError> {
208 Self::validate_year(year)?;
209 Self::validate_day(year, self.month, self.day_of_month)?;
210 self.year = year;
211 Ok(())
212 }
213
214 pub fn set_month(&mut self, month: u8) -> Result<(), DateTimeError> {
218 Self::validate_month(month)?;
219 Self::validate_day(self.year, month, self.day_of_month)?;
220 self.month = month;
221 Ok(())
222 }
223
224 pub fn set_day_of_month(&mut self, day_of_month: u8) -> Result<(), DateTimeError> {
226 Self::validate_day(self.year, self.month, day_of_month)?;
227 self.day_of_month = day_of_month;
228 Ok(())
229 }
230
231 pub fn set_hour(&mut self, hour: u8) -> Result<(), DateTimeError> {
233 Self::validate_hour(hour)?;
234 self.hour = hour;
235 Ok(())
236 }
237
238 pub fn set_minute(&mut self, minute: u8) -> Result<(), DateTimeError> {
240 Self::validate_minute(minute)?;
241 self.minute = minute;
242 Ok(())
243 }
244
245 pub fn set_second(&mut self, second: u8) -> Result<(), DateTimeError> {
247 Self::validate_second(second)?;
248 self.second = second;
249 Ok(())
250 }
251
252 pub fn calculate_weekday(&self) -> Result<Weekday, DateTimeError> {
254 calculate_weekday(self.year, self.month, self.day_of_month)
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260#[repr(u8)]
261pub enum Weekday {
262 Sunday = 1,
264 Monday = 2,
266 Tuesday = 3,
268 Wednesday = 4,
270 Thursday = 5,
272 Friday = 6,
274 Saturday = 7,
276}
277
278impl Weekday {
279 pub fn from_number(n: u8) -> Result<Self, DateTimeError> {
281 match n {
282 1 => Ok(Self::Sunday),
283 2 => Ok(Self::Monday),
284 3 => Ok(Self::Tuesday),
285 4 => Ok(Self::Wednesday),
286 5 => Ok(Self::Thursday),
287 6 => Ok(Self::Friday),
288 7 => Ok(Self::Saturday),
289 _ => Err(DateTimeError::InvalidWeekday),
290 }
291 }
292
293 pub fn to_number(self) -> u8 {
295 self as u8
296 }
297
298 pub fn as_str(&self) -> &'static str {
300 match self {
301 Weekday::Sunday => "Sunday",
302 Weekday::Monday => "Monday",
303 Weekday::Tuesday => "Tuesday",
304 Weekday::Wednesday => "Wednesday",
305 Weekday::Thursday => "Thursday",
306 Weekday::Friday => "Friday",
307 Weekday::Saturday => "Saturday",
308 }
309 }
310}
311
312pub fn is_leap_year(year: u16) -> bool {
314 (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)
315}
316
317pub fn days_in_month(year: u16, month: u8) -> u8 {
319 match month {
320 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
321 4 | 6 | 9 | 11 => 30,
322 2 => {
323 if is_leap_year(year) {
324 29
325 } else {
326 28
327 }
328 }
329 _ => 0,
330 }
331}
332
333pub fn calculate_weekday(year: u16, month: u8, day_of_month: u8) -> Result<Weekday, DateTimeError> {
336 let (year, month) = if month < 3 {
337 (year - 1, month + 12)
338 } else {
339 (year, month)
340 };
341
342 let k = year % 100;
343 let j = year / 100;
344
345 let h =
346 (day_of_month as u16 + ((13 * (month as u16 + 1)) / 5) + k + (k / 4) + (j / 4) - 2 * j) % 7;
347
348 let weekday_num = ((h + 6) % 7) + 1;
350
351 Weekday::from_number(weekday_num as u8)
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_valid_datetime_creation() {
361 let dt = DateTime::new(2024, 3, 15, 14, 30, 45).unwrap();
362 assert_eq!(dt.year(), 2024);
363 assert_eq!(dt.month(), 3);
364 assert_eq!(dt.day_of_month(), 15);
365 assert_eq!(dt.hour(), 14);
366 assert_eq!(dt.minute(), 30);
367 assert_eq!(dt.second(), 45);
368 }
369
370 #[test]
371 fn test_invalid_year() {
372 let result = DateTime::new(1969, 1, 1, 0, 0, 0);
373 assert_eq!(result.unwrap_err(), DateTimeError::InvalidYear);
374 }
375
376 #[test]
377 fn test_invalid_month() {
378 assert_eq!(
379 DateTime::new(2024, 0, 1, 0, 0, 0).unwrap_err(),
380 DateTimeError::InvalidMonth
381 );
382 assert_eq!(
383 DateTime::new(2024, 13, 1, 0, 0, 0).unwrap_err(),
384 DateTimeError::InvalidMonth
385 );
386 }
387
388 #[test]
389 fn test_invalid_day() {
390 assert_eq!(
392 DateTime::new(2024, 2, 30, 0, 0, 0).unwrap_err(),
393 DateTimeError::InvalidDay
394 );
395
396 assert_eq!(
398 DateTime::new(2024, 1, 0, 0, 0, 0).unwrap_err(),
399 DateTimeError::InvalidDay
400 );
401
402 assert_eq!(
404 DateTime::new(2024, 4, 31, 0, 0, 0).unwrap_err(),
405 DateTimeError::InvalidDay
406 );
407 }
408
409 #[test]
410 fn test_invalid_hour() {
411 assert_eq!(
412 DateTime::new(2024, 1, 1, 24, 0, 0).unwrap_err(),
413 DateTimeError::InvalidHour
414 );
415 }
416
417 #[test]
418 fn test_invalid_minute() {
419 assert_eq!(
420 DateTime::new(2024, 1, 1, 0, 60, 0).unwrap_err(),
421 DateTimeError::InvalidMinute
422 );
423 }
424
425 #[test]
426 fn test_invalid_second() {
427 assert_eq!(
428 DateTime::new(2024, 1, 1, 0, 0, 60).unwrap_err(),
429 DateTimeError::InvalidSecond
430 );
431 }
432
433 #[test]
434 fn test_leap_year_february_29() {
435 assert!(DateTime::new(2024, 2, 29, 0, 0, 0).is_ok());
437
438 assert_eq!(
440 DateTime::new(2023, 2, 29, 0, 0, 0).unwrap_err(),
441 DateTimeError::InvalidDay
442 );
443 }
444
445 #[test]
446 fn test_setters_with_validation() {
447 let mut dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap();
448
449 assert!(dt.set_year(2025).is_ok());
451 assert_eq!(dt.year(), 2025);
452
453 assert!(dt.set_month(12).is_ok());
454 assert_eq!(dt.month(), 12);
455
456 assert!(dt.set_hour(23).is_ok());
457 assert_eq!(dt.hour(), 23);
458
459 assert_eq!(dt.set_year(1969), Err(DateTimeError::InvalidYear));
461 assert_eq!(dt.set_month(13), Err(DateTimeError::InvalidMonth));
462 assert_eq!(dt.set_hour(24), Err(DateTimeError::InvalidHour));
463 }
464
465 #[test]
466 fn test_leap_year_edge_cases_in_setters() {
467 let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap(); assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
471
472 assert_eq!(dt.year(), 2024);
474 assert_eq!(dt.day_of_month(), 29);
475 }
476
477 #[test]
478 fn test_month_day_validation_in_setters() {
479 let mut dt = DateTime::new(2024, 1, 31, 0, 0, 0).unwrap(); assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
483
484 assert_eq!(dt.month(), 1);
486 assert_eq!(dt.day_of_month(), 31);
487
488 assert!(dt.set_month(3).is_ok());
490 assert_eq!(dt.month(), 3);
491 }
492
493 #[test]
494 fn test_weekday_calculation() {
495 let dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap(); let weekday = dt.calculate_weekday().unwrap();
497 assert_eq!(weekday, Weekday::Monday); let dt = DateTime::new(2024, 12, 25, 0, 0, 0).unwrap();
500 let weekday = dt.calculate_weekday().unwrap();
501 assert_eq!(weekday, Weekday::Wednesday); }
503
504 #[test]
505 fn test_weekday_from_number() {
506 assert_eq!(Weekday::from_number(1).unwrap(), Weekday::Sunday);
507 assert_eq!(Weekday::from_number(2).unwrap(), Weekday::Monday);
508 assert_eq!(Weekday::from_number(7).unwrap(), Weekday::Saturday);
509 assert_eq!(Weekday::from_number(3).unwrap(), Weekday::Tuesday);
510
511 assert_eq!(
512 Weekday::from_number(0).unwrap_err(),
513 DateTimeError::InvalidWeekday
514 );
515 assert_eq!(
516 Weekday::from_number(8).unwrap_err(),
517 DateTimeError::InvalidWeekday
518 );
519 }
520
521 #[test]
522 fn test_weekday_to_number() {
523 assert_eq!(Weekday::Sunday.to_number(), 1);
524 assert_eq!(Weekday::Monday.to_number(), 2);
525 assert_eq!(Weekday::Saturday.to_number(), 7);
526 }
527
528 #[test]
529 fn test_weekday_as_str() {
530 assert_eq!(Weekday::Sunday.as_str(), "Sunday");
531 assert_eq!(Weekday::Monday.as_str(), "Monday");
532 assert_eq!(Weekday::Tuesday.as_str(), "Tuesday");
533 assert_eq!(Weekday::Wednesday.as_str(), "Wednesday");
534 assert_eq!(Weekday::Thursday.as_str(), "Thursday");
535 assert_eq!(Weekday::Friday.as_str(), "Friday");
536 assert_eq!(Weekday::Saturday.as_str(), "Saturday");
537 }
538
539 #[test]
540 fn test_calculate_weekday_known_dates() {
541 assert_eq!(calculate_weekday(2000, 1, 1).unwrap(), Weekday::Saturday);
543 assert_eq!(calculate_weekday(2024, 1, 1).unwrap(), Weekday::Monday);
544 assert_eq!(calculate_weekday(2025, 8, 15).unwrap(), Weekday::Friday);
545
546 assert_eq!(calculate_weekday(2024, 2, 29).unwrap(), Weekday::Thursday); }
549
550 #[test]
551 fn test_is_leap_year() {
552 assert!(is_leap_year(2024));
554 assert!(is_leap_year(2020));
555 assert!(is_leap_year(1996));
556
557 assert!(!is_leap_year(2023));
559 assert!(!is_leap_year(2021));
560 assert!(!is_leap_year(1999));
561
562 assert!(!is_leap_year(1900));
564 assert!(!is_leap_year(2100));
565
566 assert!(is_leap_year(2000));
568 assert!(is_leap_year(1600));
569 }
570
571 #[test]
572 fn test_days_in_month() {
573 assert_eq!(days_in_month(2024, 1), 31);
575
576 assert_eq!(days_in_month(2024, 2), 29);
578
579 assert_eq!(days_in_month(2023, 2), 28);
581
582 assert_eq!(days_in_month(2024, 4), 30);
584
585 assert_eq!(days_in_month(2024, 12), 31);
587
588 assert_eq!(days_in_month(2024, 13), 0);
590 assert_eq!(days_in_month(2024, 0), 0);
591 }
592
593 #[test]
594 fn test_setter_interdependency_edge_cases() {
595 let mut dt = DateTime::new(2023, 1, 31, 0, 0, 0).unwrap();
597 assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
598
599 let mut dt = DateTime::new(2023, 3, 31, 0, 0, 0).unwrap();
601 assert_eq!(dt.set_month(4), Err(DateTimeError::InvalidDay));
602
603 let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap();
605 assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
606
607 let mut dt = DateTime::new(2023, 2, 28, 0, 0, 0).unwrap();
609 assert!(dt.set_year(2024).is_ok());
610 }
611
612 #[test]
613 fn test_display_datetime_error() {
614 assert_eq!(format!("{}", DateTimeError::InvalidMonth), "invalid month");
615 assert_eq!(format!("{}", DateTimeError::InvalidDay), "invalid day");
616 assert_eq!(format!("{}", DateTimeError::InvalidHour), "invalid hour");
617 assert_eq!(
618 format!("{}", DateTimeError::InvalidMinute),
619 "invalid minute"
620 );
621 assert_eq!(
622 format!("{}", DateTimeError::InvalidSecond),
623 "invalid second"
624 );
625 assert_eq!(
626 format!("{}", DateTimeError::InvalidWeekday),
627 "invalid weekday"
628 );
629 assert_eq!(format!("{}", DateTimeError::InvalidYear), "invalid year");
630 }
631
632 #[test]
633 fn test_datetime_error_trait() {
634 let error = DateTimeError::InvalidMonth;
635 let _: &dyn core::error::Error = &error;
636 }
637
638 #[test]
639 fn test_boundary_values() {
640 assert!(DateTime::new(1970, 1, 1, 0, 0, 0).is_ok());
642
643 assert!(DateTime::new(2024, 12, 31, 23, 59, 59).is_ok());
645
646 assert!(DateTime::new(2024, 1, 1, 0, 0, 0).is_ok());
648 }
649
650 #[test]
651 fn test_february_edge_cases() {
652 assert!(DateTime::new(2024, 2, 28, 0, 0, 0).is_ok());
654
655 assert!(DateTime::new(2023, 2, 28, 0, 0, 0).is_ok());
657
658 assert_eq!(
660 DateTime::new(2023, 2, 29, 0, 0, 0).unwrap_err(),
661 DateTimeError::InvalidDay
662 );
663 }
664
665 #[test]
666 fn test_all_month_max_days() {
667 let year = 2023; for month in [1, 3, 5, 7, 8, 10, 12] {
671 assert!(DateTime::new(year, month, 31, 0, 0, 0).is_ok());
672 assert_eq!(
673 DateTime::new(year, month, 32, 0, 0, 0).unwrap_err(),
674 DateTimeError::InvalidDay
675 );
676 }
677
678 for month in [4, 6, 9, 11] {
680 assert!(DateTime::new(year, month, 30, 0, 0, 0).is_ok());
681 assert_eq!(
682 DateTime::new(year, month, 31, 0, 0, 0).unwrap_err(),
683 DateTimeError::InvalidDay
684 );
685 }
686 }
687
688 #[test]
689 fn test_set_day_of_month() {
690 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
691
692 assert!(dt.set_day_of_month(10).is_ok());
693 assert_eq!(dt.day_of_month, 10);
694 }
695
696 #[test]
697 fn test_all_setters_preserve_state_on_error() {
698 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
699 let original = dt;
700
701 assert!(dt.set_day_of_month(40).is_err());
703 assert_eq!(dt, original);
704
705 assert!(dt.set_minute(70).is_err());
706 assert_eq!(dt, original);
707
708 assert!(dt.set_second(70).is_err());
709 assert_eq!(dt, original);
710 }
711
712 #[test]
713 fn test_set_minute() {
714 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
715
716 assert!(dt.set_minute(10).is_ok());
717 assert_eq!(dt.minute, 10);
718 }
719
720 #[test]
721 fn test_set_second() {
722 let mut dt = DateTime::new(2024, 5, 15, 12, 30, 45).unwrap();
723 assert!(dt.set_second(10).is_ok());
724 assert_eq!(dt.second, 10);
725 }
726}