1use regex::Regex;
4use std::sync::OnceLock;
5
6static DATE_REGEX: OnceLock<Regex> = OnceLock::new();
7static DATETIME_REGEX: OnceLock<Regex> = OnceLock::new();
8
9fn get_date_regex() -> &'static Regex {
10 DATE_REGEX.get_or_init(|| {
11 Regex::new(r"^\d{4}-\d{2}-\d{2}$")
12 .expect("Failed to compile date regex")
13 })
14}
15
16fn get_datetime_regex() -> &'static Regex {
17 DATETIME_REGEX.get_or_init(|| {
18 Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$")
19 .expect("Failed to compile datetime regex")
20 })
21}
22
23pub fn is_valid_date(date: &str) -> bool {
34 if !get_date_regex().is_match(date) {
35 return false;
36 }
37
38 let parts: Vec<&str> = date.split('-').collect();
39 if parts.len() != 3 {
40 return false;
41 }
42
43 let year = parts[0].parse::<i32>().ok();
44 let month = parts[1].parse::<u32>().ok();
45 let day = parts[2].parse::<u32>().ok();
46
47 match (year, month, day) {
48 (Some(y), Some(m), Some(d)) => is_valid_date_parts(y, m, d),
49 _ => false,
50 }
51}
52
53fn is_valid_date_parts(year: i32, month: u32, day: u32) -> bool {
55 if !(1..=12).contains(&month) || day < 1 {
56 return false;
57 }
58
59 let max_day = match month {
60 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
61 4 | 6 | 9 | 11 => 30,
62 2 => {
63 if is_leap_year(year) {
64 29
65 } else {
66 28
67 }
68 }
69 _ => return false,
70 };
71
72 day <= max_day
73}
74
75fn is_leap_year(year: i32) -> bool {
77 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
78}
79
80pub fn is_valid_datetime(datetime: &str) -> bool {
82 get_datetime_regex().is_match(datetime)
83}
84
85pub fn is_valid_time(time: &str) -> bool {
87 let parts: Vec<&str> = time.split(':').collect();
88 if parts.len() != 3 {
89 return false;
90 }
91
92 let hour = parts[0].parse::<u32>().ok();
93 let minute = parts[1].parse::<u32>().ok();
94 let second = parts[2].parse::<u32>().ok();
95
96 match (hour, minute, second) {
97 (Some(h), Some(m), Some(s)) => h < 24 && m < 60 && s < 60,
98 _ => false,
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_valid_dates() {
108 assert!(is_valid_date("2023-12-31"));
109 assert!(is_valid_date("2024-02-29")); assert!(is_valid_date("2000-01-01"));
111 }
112
113 #[test]
114 fn test_invalid_dates() {
115 assert!(!is_valid_date(""));
116 assert!(!is_valid_date("2023-13-01")); assert!(!is_valid_date("2023-02-30")); assert!(!is_valid_date("2023-02-29")); assert!(!is_valid_date("31-12-2023")); assert!(!is_valid_date("2023/12/31")); }
122
123 #[test]
124 fn test_leap_year() {
125 assert!(is_leap_year(2024));
126 assert!(is_leap_year(2000));
127 assert!(!is_leap_year(2023));
128 assert!(!is_leap_year(1900));
129 }
130
131 #[test]
132 fn test_valid_datetime() {
133 assert!(is_valid_datetime("2023-12-31T23:59:59Z"));
134 assert!(is_valid_datetime("2023-12-31T23:59:59.123Z"));
135 assert!(is_valid_datetime("2023-12-31T23:59:59+05:30"));
136 }
137
138 #[test]
139 fn test_invalid_datetime() {
140 assert!(!is_valid_datetime("2023-12-31"));
141 assert!(!is_valid_datetime("2023-12-31 23:59:59"));
142 }
143
144 #[test]
145 fn test_valid_time() {
146 assert!(is_valid_time("12:30:45"));
147 assert!(is_valid_time("00:00:00"));
148 assert!(is_valid_time("23:59:59"));
149 }
150
151 #[test]
152 fn test_invalid_time() {
153 assert!(!is_valid_time("24:00:00"));
154 assert!(!is_valid_time("12:60:00"));
155 assert!(!is_valid_time("12:30:60"));
156 assert!(!is_valid_time("12:30"));
157 }
158}
159