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
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct DateTime {
54 year: u16,
56 month: u8,
58 day_of_month: u8,
60 hour: u8,
62 minute: u8,
64 second: u8,
66}
67
68impl DateTime {
69 pub fn new(
75 year: u16,
76 month: u8,
77 day_of_month: u8,
78 hour: u8,
79 minute: u8,
80 second: u8,
81 ) -> Result<Self, DateTimeError> {
82 let dt = DateTime {
83 year,
84 month,
85 day_of_month,
86 hour,
87 minute,
88 second,
89 };
90 dt.validate()?;
91 Ok(dt)
92 }
93
94 pub fn validate(&self) -> Result<(), DateTimeError> {
100 Self::validate_year(self.year)?;
101 Self::validate_month(self.month)?;
102 Self::validate_day(self.year, self.month, self.day_of_month)?;
103 Self::validate_hour(self.hour)?;
104 Self::validate_minute(self.minute)?;
105 Self::validate_second(self.second)?;
106 Ok(())
107 }
108
109 fn validate_year(year: u16) -> Result<(), DateTimeError> {
111 if year < 1970 {
112 return Err(DateTimeError::InvalidYear);
113 }
114 Ok(())
115 }
116
117 fn validate_month(month: u8) -> Result<(), DateTimeError> {
119 if month == 0 || month > 12 {
120 return Err(DateTimeError::InvalidMonth);
121 }
122 Ok(())
123 }
124
125 fn validate_day(year: u16, month: u8, day: u8) -> Result<(), DateTimeError> {
127 let max_day = days_in_month(year, month);
128 if day == 0 || day > max_day {
129 return Err(DateTimeError::InvalidDay);
130 }
131 Ok(())
132 }
133
134 fn validate_hour(hour: u8) -> Result<(), DateTimeError> {
136 if hour > 23 {
137 return Err(DateTimeError::InvalidHour);
138 }
139 Ok(())
140 }
141
142 fn validate_minute(minute: u8) -> Result<(), DateTimeError> {
144 if minute > 59 {
145 return Err(DateTimeError::InvalidMinute);
146 }
147 Ok(())
148 }
149
150 fn validate_second(second: u8) -> Result<(), DateTimeError> {
152 if second > 59 {
153 return Err(DateTimeError::InvalidSecond);
154 }
155 Ok(())
156 }
157
158 pub fn year(&self) -> u16 {
160 self.year
161 }
162
163 pub fn month(&self) -> u8 {
165 self.month
166 }
167
168 pub fn day_of_month(&self) -> u8 {
170 self.day_of_month
171 }
172
173 pub fn hour(&self) -> u8 {
175 self.hour
176 }
177
178 pub fn minute(&self) -> u8 {
180 self.minute
181 }
182
183 pub fn second(&self) -> u8 {
185 self.second
186 }
187
188 pub fn set_year(&mut self, year: u16) -> Result<(), DateTimeError> {
192 Self::validate_year(year)?;
193 Self::validate_day(year, self.month, self.day_of_month)?;
194 self.year = year;
195 Ok(())
196 }
197
198 pub fn set_month(&mut self, month: u8) -> Result<(), DateTimeError> {
202 Self::validate_month(month)?;
203 Self::validate_day(self.year, month, self.day_of_month)?;
204 self.month = month;
205 Ok(())
206 }
207
208 pub fn set_day_of_month(&mut self, day_of_month: u8) -> Result<(), DateTimeError> {
210 Self::validate_day(self.year, self.month, day_of_month)?;
211 self.day_of_month = day_of_month;
212 Ok(())
213 }
214
215 pub fn set_hour(&mut self, hour: u8) -> Result<(), DateTimeError> {
217 Self::validate_hour(hour)?;
218 self.hour = hour;
219 Ok(())
220 }
221
222 pub fn set_minute(&mut self, minute: u8) -> Result<(), DateTimeError> {
224 Self::validate_minute(minute)?;
225 self.minute = minute;
226 Ok(())
227 }
228
229 pub fn set_second(&mut self, second: u8) -> Result<(), DateTimeError> {
231 Self::validate_second(second)?;
232 self.second = second;
233 Ok(())
234 }
235
236 pub fn calculate_weekday(&self) -> Result<Weekday, DateTimeError> {
238 calculate_weekday(self.year, self.month, self.day_of_month)
239 }
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244#[repr(u8)]
245pub enum Weekday {
246 Sunday = 1,
248 Monday = 2,
250 Tuesday = 3,
252 Wednesday = 4,
254 Thursday = 5,
256 Friday = 6,
258 Saturday = 7,
260}
261
262impl Weekday {
263 pub fn from_number(n: u8) -> Result<Self, DateTimeError> {
265 match n {
266 1 => Ok(Self::Sunday),
267 2 => Ok(Self::Monday),
268 3 => Ok(Self::Tuesday),
269 4 => Ok(Self::Wednesday),
270 5 => Ok(Self::Thursday),
271 6 => Ok(Self::Friday),
272 7 => Ok(Self::Saturday),
273 _ => Err(DateTimeError::InvalidWeekday),
274 }
275 }
276
277 pub fn to_number(self) -> u8 {
279 self as u8
280 }
281
282 pub fn as_str(&self) -> &'static str {
284 match self {
285 Weekday::Sunday => "Sunday",
286 Weekday::Monday => "Monday",
287 Weekday::Tuesday => "Tuesday",
288 Weekday::Wednesday => "Wednesday",
289 Weekday::Thursday => "Thursday",
290 Weekday::Friday => "Friday",
291 Weekday::Saturday => "Saturday",
292 }
293 }
294}
295
296pub fn is_leap_year(year: u16) -> bool {
298 (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)
299}
300
301pub fn days_in_month(year: u16, month: u8) -> u8 {
303 match month {
304 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
305 4 | 6 | 9 | 11 => 30,
306 2 => {
307 if is_leap_year(year) {
308 29
309 } else {
310 28
311 }
312 }
313 _ => 0,
314 }
315}
316
317pub fn calculate_weekday(year: u16, month: u8, day_of_month: u8) -> Result<Weekday, DateTimeError> {
320 let (year, month) = if month < 3 {
321 (year - 1, month + 12)
322 } else {
323 (year, month)
324 };
325
326 let k = year % 100;
327 let j = year / 100;
328
329 let h =
330 (day_of_month as u16 + ((13 * (month as u16 + 1)) / 5) + k + (k / 4) + (j / 4) - 2 * j) % 7;
331
332 let weekday_num = ((h + 6) % 7) + 1;
334
335 Weekday::from_number(weekday_num as u8)
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_valid_datetime_creation() {
345 let dt = DateTime::new(2024, 3, 15, 14, 30, 45).unwrap();
346 assert_eq!(dt.year(), 2024);
347 assert_eq!(dt.month(), 3);
348 assert_eq!(dt.day_of_month(), 15);
349 assert_eq!(dt.hour(), 14);
350 assert_eq!(dt.minute(), 30);
351 assert_eq!(dt.second(), 45);
352 }
353
354 #[test]
355 fn test_invalid_year() {
356 let result = DateTime::new(1969, 1, 1, 0, 0, 0);
357 assert_eq!(result.unwrap_err(), DateTimeError::InvalidYear);
358 }
359
360 #[test]
361 fn test_invalid_month() {
362 assert_eq!(
363 DateTime::new(2024, 0, 1, 0, 0, 0).unwrap_err(),
364 DateTimeError::InvalidMonth
365 );
366 assert_eq!(
367 DateTime::new(2024, 13, 1, 0, 0, 0).unwrap_err(),
368 DateTimeError::InvalidMonth
369 );
370 }
371
372 #[test]
373 fn test_invalid_day() {
374 assert_eq!(
376 DateTime::new(2024, 2, 30, 0, 0, 0).unwrap_err(),
377 DateTimeError::InvalidDay
378 );
379
380 assert_eq!(
382 DateTime::new(2024, 1, 0, 0, 0, 0).unwrap_err(),
383 DateTimeError::InvalidDay
384 );
385
386 assert_eq!(
388 DateTime::new(2024, 4, 31, 0, 0, 0).unwrap_err(),
389 DateTimeError::InvalidDay
390 );
391 }
392
393 #[test]
394 fn test_invalid_hour() {
395 assert_eq!(
396 DateTime::new(2024, 1, 1, 24, 0, 0).unwrap_err(),
397 DateTimeError::InvalidHour
398 );
399 }
400
401 #[test]
402 fn test_invalid_minute() {
403 assert_eq!(
404 DateTime::new(2024, 1, 1, 0, 60, 0).unwrap_err(),
405 DateTimeError::InvalidMinute
406 );
407 }
408
409 #[test]
410 fn test_invalid_second() {
411 assert_eq!(
412 DateTime::new(2024, 1, 1, 0, 0, 60).unwrap_err(),
413 DateTimeError::InvalidSecond
414 );
415 }
416
417 #[test]
418 fn test_leap_year_february_29() {
419 assert!(DateTime::new(2024, 2, 29, 0, 0, 0).is_ok());
421
422 assert_eq!(
424 DateTime::new(2023, 2, 29, 0, 0, 0).unwrap_err(),
425 DateTimeError::InvalidDay
426 );
427 }
428
429 #[test]
430 fn test_setters_with_validation() {
431 let mut dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap();
432
433 assert!(dt.set_year(2025).is_ok());
435 assert_eq!(dt.year(), 2025);
436
437 assert!(dt.set_month(12).is_ok());
438 assert_eq!(dt.month(), 12);
439
440 assert!(dt.set_hour(23).is_ok());
441 assert_eq!(dt.hour(), 23);
442
443 assert_eq!(dt.set_year(1969), Err(DateTimeError::InvalidYear));
445 assert_eq!(dt.set_month(13), Err(DateTimeError::InvalidMonth));
446 assert_eq!(dt.set_hour(24), Err(DateTimeError::InvalidHour));
447 }
448
449 #[test]
450 fn test_leap_year_edge_cases_in_setters() {
451 let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap(); assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
455
456 assert_eq!(dt.year(), 2024);
458 assert_eq!(dt.day_of_month(), 29);
459 }
460
461 #[test]
462 fn test_month_day_validation_in_setters() {
463 let mut dt = DateTime::new(2024, 1, 31, 0, 0, 0).unwrap(); assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
467
468 assert_eq!(dt.month(), 1);
470 assert_eq!(dt.day_of_month(), 31);
471
472 assert!(dt.set_month(3).is_ok());
474 assert_eq!(dt.month(), 3);
475 }
476
477 #[test]
478 fn test_weekday_calculation() {
479 let dt = DateTime::new(2024, 1, 1, 0, 0, 0).unwrap(); let weekday = dt.calculate_weekday().unwrap();
481 assert_eq!(weekday, Weekday::Monday); let dt = DateTime::new(2024, 12, 25, 0, 0, 0).unwrap();
484 let weekday = dt.calculate_weekday().unwrap();
485 assert_eq!(weekday, Weekday::Wednesday); }
487
488 #[test]
489 fn test_weekday_from_number() {
490 assert_eq!(Weekday::from_number(1).unwrap(), Weekday::Sunday);
491 assert_eq!(Weekday::from_number(2).unwrap(), Weekday::Monday);
492 assert_eq!(Weekday::from_number(7).unwrap(), Weekday::Saturday);
493
494 assert_eq!(
495 Weekday::from_number(0).unwrap_err(),
496 DateTimeError::InvalidWeekday
497 );
498 assert_eq!(
499 Weekday::from_number(8).unwrap_err(),
500 DateTimeError::InvalidWeekday
501 );
502 }
503
504 #[test]
505 fn test_weekday_to_number() {
506 assert_eq!(Weekday::Sunday.to_number(), 1);
507 assert_eq!(Weekday::Monday.to_number(), 2);
508 assert_eq!(Weekday::Saturday.to_number(), 7);
509 }
510
511 #[test]
512 fn test_weekday_as_str() {
513 assert_eq!(Weekday::Sunday.as_str(), "Sunday");
514 assert_eq!(Weekday::Monday.as_str(), "Monday");
515 assert_eq!(Weekday::Tuesday.as_str(), "Tuesday");
516 assert_eq!(Weekday::Wednesday.as_str(), "Wednesday");
517 assert_eq!(Weekday::Thursday.as_str(), "Thursday");
518 assert_eq!(Weekday::Friday.as_str(), "Friday");
519 assert_eq!(Weekday::Saturday.as_str(), "Saturday");
520 }
521
522 #[test]
523 fn test_calculate_weekday_known_dates() {
524 assert_eq!(calculate_weekday(2000, 1, 1).unwrap(), Weekday::Saturday);
526 assert_eq!(calculate_weekday(2024, 1, 1).unwrap(), Weekday::Monday);
527 assert_eq!(calculate_weekday(2025, 8, 15).unwrap(), Weekday::Friday);
528
529 assert_eq!(calculate_weekday(2024, 2, 29).unwrap(), Weekday::Thursday); }
532
533 #[test]
534 fn test_is_leap_year() {
535 assert!(is_leap_year(2024));
537 assert!(is_leap_year(2020));
538 assert!(is_leap_year(1996));
539
540 assert!(!is_leap_year(2023));
542 assert!(!is_leap_year(2021));
543 assert!(!is_leap_year(1999));
544
545 assert!(!is_leap_year(1900));
547 assert!(!is_leap_year(2100));
548
549 assert!(is_leap_year(2000));
551 assert!(is_leap_year(1600));
552 }
553
554 #[test]
555 fn test_days_in_month() {
556 assert_eq!(days_in_month(2024, 1), 31);
558
559 assert_eq!(days_in_month(2024, 2), 29);
561
562 assert_eq!(days_in_month(2023, 2), 28);
564
565 assert_eq!(days_in_month(2024, 4), 30);
567
568 assert_eq!(days_in_month(2024, 12), 31);
570
571 assert_eq!(days_in_month(2024, 13), 0);
573 assert_eq!(days_in_month(2024, 0), 0);
574 }
575
576 #[test]
577 fn test_setter_interdependency_edge_cases() {
578 let mut dt = DateTime::new(2023, 1, 31, 0, 0, 0).unwrap();
580 assert_eq!(dt.set_month(2), Err(DateTimeError::InvalidDay));
581
582 let mut dt = DateTime::new(2023, 3, 31, 0, 0, 0).unwrap();
584 assert_eq!(dt.set_month(4), Err(DateTimeError::InvalidDay));
585
586 let mut dt = DateTime::new(2024, 2, 29, 0, 0, 0).unwrap();
588 assert_eq!(dt.set_year(2023), Err(DateTimeError::InvalidDay));
589
590 let mut dt = DateTime::new(2023, 2, 28, 0, 0, 0).unwrap();
592 assert!(dt.set_year(2024).is_ok());
593 }
594}