klirr_core/models/
date.rs1use crate::prelude::*;
2
3#[derive(
6 Clone,
7 Copy,
8 Debug,
9 Display,
10 PartialEq,
11 Eq,
12 Hash,
13 SerializeDisplay,
14 DeserializeFromStr,
15 Builder,
16 Getters,
17)]
18#[display("{year:04}-{month:02}-{day:02}")]
19pub struct Date {
20 #[getset(get = "pub")]
22 year: Year,
23
24 #[getset(get = "pub")]
26 month: Month,
27
28 #[getset(get = "pub")]
30 day: Day,
31}
32
33impl std::str::FromStr for Date {
34 type Err = crate::prelude::Error;
35
36 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 let parts: Vec<&str> = s.split('-').collect();
52 if parts.len() != 3 {
53 return Err(Error::FailedToParseDate {
54 underlying: "Invalid Format".to_owned(),
55 });
56 }
57
58 let year = Year::from_str(parts[0])?;
59 let month = Month::from_str(parts[1])?;
60 let day = Day::from_str(parts[2])?;
61
62 Ok(Self::builder().year(year).month(month).day(day).build())
63 }
64}
65
66fn from_ymd_parts(year: i32, month: u32, day: u32) -> Date {
67 Date::builder()
68 .year(year.into())
69 .month(Month::try_from(month).expect("Invalid month"))
70 .day(Day::try_from(day).expect("Invalid day"))
71 .build()
72}
73
74impl From<NaiveDate> for Date {
75 fn from(value: NaiveDate) -> Self {
76 from_ymd_parts(value.year(), value.month(), value.day())
77 }
78}
79
80impl From<NaiveDateTime> for Date {
81 fn from(value: NaiveDateTime) -> Self {
82 from_ymd_parts(value.year(), value.month(), value.day())
83 }
84}
85
86impl Date {
87 pub fn to_datetime(&self) -> NaiveDateTime {
88 let naive_date = chrono::NaiveDate::from_ymd_opt(
89 **self.year() as i32,
90 **self.month() as u32,
91 **self.day() as u32,
92 )
93 .expect("Invalid date components");
94 naive_date
95 .and_hms_opt(0, 0, 0)
96 .expect("Invalid time components")
97 }
98
99 pub fn advance_days(&self, days: &Day) -> Self {
100 let datetime = self.to_datetime();
101 let days: u8 = **days;
102 let advanced_date = datetime + chrono::Duration::days(days as i64);
103 Self::from(advanced_date)
104 }
105
106 pub fn advance(&self, terms: &PaymentTerms) -> Self {
107 match terms {
108 PaymentTerms::Net(days) => self.advance_days(days.due_in()),
109 }
110 }
111}
112
113impl HasSample for Date {
114 fn sample() -> Self {
115 Self::builder()
116 .year(2025.into())
117 .month(Month::May)
118 .day(Day::try_from(31).expect("LEQ 31 days"))
119 .build()
120 }
121 fn sample_other() -> Self {
122 Self::builder()
123 .year(2024.into())
124 .month(Month::December)
125 .day(Day::try_from(15).expect("LEQ 31 days"))
126 .build()
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use test_log::test;
134
135 type Sut = Date;
136
137 #[test]
138 fn equality() {
139 assert_eq!(Sut::sample(), Sut::sample());
140 assert_eq!(Sut::sample_other(), Sut::sample_other());
141 }
142
143 #[test]
144 fn inequality() {
145 assert_ne!(Sut::sample(), Sut::sample_other());
146 }
147
148 #[test]
149 fn test_date_from_str() {
150 let sut = Sut::from_str("2025-05-23").unwrap();
151 assert_eq!(sut.year(), &Year::from(2025));
152 assert_eq!(sut.month(), &Month::May);
153 assert_eq!(sut.day(), &Day::try_from(23).unwrap());
154 }
155
156 #[test]
157 fn test_year_month_from_str() {
158 let sut = YearAndMonth::from_str("2025-05").unwrap();
159 assert_eq!(sut.year(), &Year::from(2025));
160 assert_eq!(sut.month(), &Month::May);
161 }
162
163 #[test]
164 fn test_from_str_all_reasons_invalid() {
165 let invalid_dates = [
166 "2025-05-32", "99999999999-05-32", "2025-13-01", "2025-00-01", "2025-13-01", "2025-05", "2025", "05-23", "2025-05-23-01", ];
176
177 for date in invalid_dates {
178 assert!(Sut::from_str(date).is_err());
179 }
180 }
181
182 #[test]
183 fn test_from_naive_date() {
184 let naive_date = NaiveDate::from_ymd_opt(2025, 5, 23).unwrap();
185 let date: Date = naive_date.into();
186 assert_eq!(date.year(), &Year::from(2025));
187 assert_eq!(date.month(), &Month::May);
188 assert_eq!(date.day(), &Day::try_from(23).unwrap());
189 }
190}