1use date_utils;
2use month_tuple::MonthTuple;
3use regex::Regex;
4use std::cmp::Ordering;
5use std::fmt;
6use std::str::FromStr;
7
8const DAYS_IN_A_COMMON_YEAR: u32 = 365;
9const DAYS_IN_A_LEAP_YEAR: u32 = 366;
10
11pub type Date = DateTuple;
12
13#[cfg_attr(
17 feature = "serde_support",
18 derive(serde::Serialize, serde::Deserialize)
19)]
20#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
21pub struct DateTuple {
22 y: u16,
23 m: u8,
24 d: u8,
25}
26
27impl DateTuple {
28 pub fn new(y: u16, m: u8, d: u8) -> Result<DateTuple, String> {
32 if y > 9999 {
33 return Err(format!(
34 "Invalid year in DateTuple {:?}: year must be <= 9999.",
35 DateTuple { y, m, d }
36 ));
37 }
38 if (1..=12).contains(&m) {
39 if d == 0 || d > date_utils::get_last_date_in_month(m, y) {
40 return Err(format!(
41 "Invalid date in DateTuple: {:?}",
42 DateTuple { y, m, d }
43 ));
44 }
45 Ok(DateTuple { y, m, d })
46 } else {
47 Err(format!(
48 "Invalid month in DateTuple: {:?}\nMonth must be between 1 and 12; Note that months are ONE-BASED since version 2.0.0.",
49 DateTuple { y, m, d }
50 ))
51 }
52 }
53
54 pub fn min_value() -> DateTuple {
56 DateTuple::new(0, 1, 1).unwrap()
57 }
58
59 pub fn max_value() -> DateTuple {
61 DateTuple::new(9999, 12, 31).unwrap()
62 }
63
64 pub fn today() -> DateTuple {
66 date_utils::now_as_datetuple()
67 }
68
69 pub fn get_year(self) -> u16 {
70 self.y
71 }
72
73 pub fn get_month(self) -> u8 {
74 self.m
75 }
76
77 pub fn get_date(self) -> u8 {
78 self.d
79 }
80
81 pub fn next_date(self) -> DateTuple {
84 if self.y == 9999 && self.m == 12 && self.d == 31 {
85 return self;
86 }
87 if self.d == date_utils::get_last_date_in_month(self.m, self.y) {
88 if self.m == 12 {
89 DateTuple {
90 y: self.y + 1,
91 m: 1,
92 d: 1,
93 }
94 } else {
95 DateTuple {
96 y: self.y,
97 m: self.m + 1,
98 d: 1,
99 }
100 }
101 } else {
102 DateTuple {
103 y: self.y,
104 m: self.m,
105 d: self.d + 1,
106 }
107 }
108 }
109
110 pub fn previous_date(self) -> DateTuple {
113 if self.y == 0 && self.m == 1 && self.d == 1 {
114 return self;
115 }
116 if self.d == 1 {
117 if self.m == 1 {
118 DateTuple {
119 y: self.y - 1,
120 m: 12,
121 d: date_utils::get_last_date_in_month(12, self.y - 1),
122 }
123 } else {
124 DateTuple {
125 y: self.y,
126 m: self.m - 1,
127 d: date_utils::get_last_date_in_month(self.m - 1, self.y),
128 }
129 }
130 } else {
131 DateTuple {
132 y: self.y,
133 m: self.m,
134 d: self.d - 1,
135 }
136 }
137 }
138
139 pub fn add_days(&mut self, days: u32) {
141 for _ in 0..days {
142 *self = self.next_date();
143 }
144 }
145
146 pub fn subtract_days(&mut self, days: u32) {
148 for _ in 0..days {
149 *self = self.previous_date();
150 }
151 }
152
153 pub fn add_months(&mut self, months: u32) {
158 let mut new_month = MonthTuple::from(*self);
159 new_month.add_months(months);
160 let last_date_in_month =
161 date_utils::get_last_date_in_month(new_month.get_month(), new_month.get_year());
162 if self.d > last_date_in_month {
163 self.d = last_date_in_month;
164 }
165 self.y = new_month.get_year();
166 self.m = new_month.get_month();
167 }
168
169 pub fn subtract_months(&mut self, months: u32) {
174 let mut new_month = MonthTuple::from(*self);
175 new_month.subtract_months(months);
176 let last_date_in_month =
177 date_utils::get_last_date_in_month(new_month.get_month(), new_month.get_year());
178 if self.d > last_date_in_month {
179 self.d = last_date_in_month;
180 }
181 self.y = new_month.get_year();
182 self.m = new_month.get_month();
183 }
184
185 pub fn add_years(&mut self, years: u16) {
190 let mut new_years = self.y + years;
191 if new_years > 9999 {
192 new_years = 9999;
193 }
194 if self.m == 2 && self.d == 29 && !date_utils::is_leap_year(new_years) {
195 self.d = 28
196 }
197 self.y = new_years;
198 }
199
200 pub fn subtract_years(&mut self, years: u16) {
205 let mut new_years = i32::from(self.y) - i32::from(years);
206 if new_years < 0 {
207 new_years = 0;
208 }
209 let new_years = new_years as u16;
210 if self.m == 2 && self.d == 29 && !date_utils::is_leap_year(new_years) {
211 self.d = 28
212 }
213 self.y = new_years;
214 }
215
216 pub fn to_readable_string(self) -> String {
222 let month = MonthTuple::from(self);
223 format!("{} {}", self.d, month.to_readable_string())
224 }
225
226 pub fn to_days(self) -> u32 {
229 let mut total_days = 0u32;
230 for y in 0..self.y {
231 total_days += if date_utils::is_leap_year(y) {
232 DAYS_IN_A_LEAP_YEAR
233 } else {
234 DAYS_IN_A_COMMON_YEAR
235 }
236 }
237 for m in 1..self.m {
238 total_days += u32::from(date_utils::get_last_date_in_month(m, self.y));
239 }
240 total_days + u32::from(self.d)
241 }
242
243 pub fn from_days(mut total_days: u32) -> Result<DateTuple, String> {
246 let mut years = 0u16;
247 let mut months = 1u8;
248 while total_days
249 > if date_utils::is_leap_year(years) {
250 DAYS_IN_A_LEAP_YEAR
251 } else {
252 DAYS_IN_A_COMMON_YEAR
253 }
254 {
255 total_days -= if date_utils::is_leap_year(years) {
256 DAYS_IN_A_LEAP_YEAR
257 } else {
258 DAYS_IN_A_COMMON_YEAR
259 };
260 years += 1;
261 }
262 while total_days > u32::from(date_utils::get_last_date_in_month(months, years)) {
263 total_days -= u32::from(date_utils::get_last_date_in_month(months, years));
264 months += 1;
265 }
266 DateTuple::new(years, months, total_days as u8)
267 }
268}
269
270impl fmt::Display for DateTuple {
271 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
272 write!(f, "{:04}-{:02}-{:02}", self.y, self.m, self.d)
273 }
274}
275
276impl FromStr for DateTuple {
277 type Err = String;
278
279 fn from_str(s: &str) -> Result<DateTuple, Self::Err> {
283 lazy_static! {
284 static ref VALID_FORMAT: Regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
285 static ref LEGACY_FORMAT: Regex = Regex::new(r"^\d{8}$").unwrap();
286 }
287
288 if VALID_FORMAT.is_match(s) {
289 match DateTuple::new(
290 u16::from_str(&s[0..4]).unwrap(),
291 u8::from_str(&s[5..7]).unwrap(),
292 u8::from_str(&s[8..10]).unwrap(),
293 ) {
294 Ok(d) => Ok(d),
295 Err(e) => Err(format!("Invalid date passed to from_str: {}", e)),
296 }
297 } else if LEGACY_FORMAT.is_match(s) {
298 let (s1, s2) = s.split_at(4);
299 let (s2, s3) = s2.split_at(2);
300 match DateTuple::new(
301 u16::from_str(s1).unwrap(),
302 u8::from_str(s2).unwrap(),
303 u8::from_str(s3).unwrap(),
304 ) {
305 Ok(d) => Ok(d),
306 Err(e) => Err(format!("Invalid date passed to from_str: {}", e)),
307 }
308 } else {
309 Err(format!("Invalid str formatting of DateTuple: {}\nExpects a string formatted like 2018-11-02.", s))
310 }
311 }
312}
313
314impl PartialOrd for DateTuple {
315 fn partial_cmp(&self, other: &DateTuple) -> Option<Ordering> {
316 if self.y == other.y {
317 if self.m == other.m {
318 self.d.partial_cmp(&other.d)
319 } else {
320 self.m.partial_cmp(&other.m)
321 }
322 } else {
323 self.y.partial_cmp(&other.y)
324 }
325 }
326}
327
328#[cfg_attr(tarpaulin, skip)]
329impl Ord for DateTuple {
330 fn cmp(&self, other: &DateTuple) -> Ordering {
331 if self.y == other.y {
332 if self.m == other.m {
333 self.d.cmp(&other.d)
334 } else {
335 self.m.cmp(&other.m)
336 }
337 } else {
338 self.y.cmp(&other.y)
339 }
340 }
341}
342
343#[cfg(test)]
344mod tests {
345
346 use super::Date;
347
348 #[test]
349 fn test_next_date() {
350 let tuple1 = Date::new(2000, 6, 10).unwrap();
351 let tuple2 = Date::new(2000, 3, 31).unwrap();
352 let tuple3 = Date::max_value();
353 assert_eq!(
354 Date {
355 y: 2000,
356 m: 6,
357 d: 11
358 },
359 tuple1.next_date()
360 );
361 assert_eq!(
362 Date {
363 y: 2000,
364 m: 4,
365 d: 1
366 },
367 tuple2.next_date()
368 );
369 assert_eq!(
370 Date {
371 y: 9999,
372 m: 12,
373 d: 31
374 },
375 tuple3.next_date()
376 );
377 }
378
379 #[test]
380 fn test_previous_date() {
381 let tuple1 = Date::new(2000, 6, 10).unwrap();
382 let tuple2 = Date::new(2000, 3, 1).unwrap();
383 let tuple3 = Date::new(0, 1, 1).unwrap();
384 let tuple4 = Date::new(2000, 1, 1).unwrap();
385 assert_eq!(
386 Date {
387 y: 2000,
388 m: 6,
389 d: 9
390 },
391 tuple1.previous_date()
392 );
393 assert_eq!(
394 Date {
395 y: 2000,
396 m: 2,
397 d: 29
398 },
399 tuple2.previous_date()
400 );
401 assert_eq!(Date { y: 0, m: 1, d: 1 }, tuple3.previous_date());
402 assert_eq!(
403 Date {
404 y: 1999,
405 m: 12,
406 d: 31
407 },
408 tuple4.previous_date()
409 );
410 }
411}