pubmed_client/pubmed/query/
date.rs1#[derive(Debug, Clone, PartialEq)]
5pub struct PubDate {
6 year: u32,
7 month: Option<u32>,
8 day: Option<u32>,
9}
10
11impl PubDate {
12 pub fn new(year: u32) -> Self {
14 Self {
15 year,
16 month: None,
17 day: None,
18 }
19 }
20
21 pub fn with_month(year: u32, month: u32) -> Self {
23 Self {
24 year,
25 month: Some(month),
26 day: None,
27 }
28 }
29
30 pub fn with_day(year: u32, month: u32, day: u32) -> Self {
32 Self {
33 year,
34 month: Some(month),
35 day: Some(day),
36 }
37 }
38
39 pub fn to_pubmed_string(&self) -> String {
41 match (self.month, self.day) {
42 (Some(month), Some(day)) => format!("{}/{:02}/{:02}", self.year, month, day),
43 (Some(month), None) => format!("{}/{:02}", self.year, month),
44 _ => self.year.to_string(),
45 }
46 }
47}
48
49impl From<u32> for PubDate {
50 fn from(year: u32) -> Self {
51 Self::new(year)
52 }
53}
54
55impl From<(u32, u32)> for PubDate {
56 fn from((year, month): (u32, u32)) -> Self {
57 Self::with_month(year, month)
58 }
59}
60
61impl From<(u32, u32, u32)> for PubDate {
62 fn from((year, month, day): (u32, u32, u32)) -> Self {
63 Self::with_day(year, month, day)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_pubdate_new() {
73 let date = PubDate::new(2023);
74 assert_eq!(date.to_pubmed_string(), "2023");
75 }
76
77 #[test]
78 fn test_pubdate_with_month() {
79 let date = PubDate::with_month(2023, 6);
80 assert_eq!(date.to_pubmed_string(), "2023/06");
81 }
82
83 #[test]
84 fn test_pubdate_with_day() {
85 let date = PubDate::with_day(2023, 6, 15);
86 assert_eq!(date.to_pubmed_string(), "2023/06/15");
87 }
88
89 #[test]
90 fn test_pubdate_from_u32() {
91 let date: PubDate = 2023.into();
92 assert_eq!(date.to_pubmed_string(), "2023");
93 }
94
95 #[test]
96 fn test_pubdate_from_tuple_month() {
97 let date: PubDate = (2023, 6).into();
98 assert_eq!(date.to_pubmed_string(), "2023/06");
99 }
100
101 #[test]
102 fn test_pubdate_from_tuple_day() {
103 let date: PubDate = (2023, 6, 15).into();
104 assert_eq!(date.to_pubmed_string(), "2023/06/15");
105 }
106
107 #[test]
108 fn test_pubdate_equality() {
109 let date1 = PubDate::new(2023);
110 let date2 = PubDate::new(2023);
111 let date3 = PubDate::new(2024);
112
113 assert_eq!(date1, date2);
114 assert_ne!(date1, date3);
115
116 let date_month1 = PubDate::with_month(2023, 6);
117 let date_month2 = PubDate::with_month(2023, 6);
118 let date_month3 = PubDate::with_month(2023, 7);
119
120 assert_eq!(date_month1, date_month2);
121 assert_ne!(date_month1, date_month3);
122 assert_ne!(date1, date_month1); }
124
125 #[test]
126 fn test_pubdate_clone() {
127 let original = PubDate::with_day(2023, 12, 25);
128 let cloned = original.clone();
129
130 assert_eq!(original, cloned);
131 assert_eq!(original.to_pubmed_string(), cloned.to_pubmed_string());
132 }
133
134 #[test]
135 fn test_pubdate_debug_format() {
136 let date = PubDate::with_month(2023, 6);
137 let debug_str = format!("{:?}", date);
138 assert!(debug_str.contains("2023"));
139 assert!(debug_str.contains("6"));
140 }
141
142 #[test]
143 fn test_month_padding() {
144 let date = PubDate::with_month(2023, 1);
145 assert_eq!(date.to_pubmed_string(), "2023/01");
146
147 let date = PubDate::with_month(2023, 12);
148 assert_eq!(date.to_pubmed_string(), "2023/12");
149 }
150
151 #[test]
152 fn test_day_padding() {
153 let date = PubDate::with_day(2023, 1, 5);
154 assert_eq!(date.to_pubmed_string(), "2023/01/05");
155
156 let date = PubDate::with_day(2023, 12, 25);
157 assert_eq!(date.to_pubmed_string(), "2023/12/25");
158 }
159
160 #[test]
161 fn test_edge_case_dates() {
162 let min_date = PubDate::with_day(1, 1, 1);
164 assert_eq!(min_date.to_pubmed_string(), "1/01/01");
165
166 let leap_year = PubDate::with_day(2024, 2, 29);
168 assert_eq!(leap_year.to_pubmed_string(), "2024/02/29");
169
170 let future_date = PubDate::new(3000);
172 assert_eq!(future_date.to_pubmed_string(), "3000");
173 }
174
175 #[test]
176 fn test_date_precision_consistency() {
177 let year_only = PubDate::new(2023);
179 assert_eq!(year_only.year, 2023);
180 assert_eq!(year_only.month, None);
181 assert_eq!(year_only.day, None);
182
183 let month_precision = PubDate::with_month(2023, 6);
185 assert_eq!(month_precision.year, 2023);
186 assert_eq!(month_precision.month, Some(6));
187 assert_eq!(month_precision.day, None);
188
189 let day_precision = PubDate::with_day(2023, 6, 15);
191 assert_eq!(day_precision.year, 2023);
192 assert_eq!(day_precision.month, Some(6));
193 assert_eq!(day_precision.day, Some(15));
194 }
195
196 mod proptest_suite {
197 use super::*;
198 use proptest::prelude::*;
199
200 proptest! {
201 #[test]
202 fn year_only_format_is_all_digits(year in 1u32..=9999u32) {
203 let s = PubDate::new(year).to_pubmed_string();
204 prop_assert!(s.chars().all(|c| c.is_ascii_digit()), "year-only should be all digits: {}", s);
205 prop_assert!(!s.contains('/'), "year-only should not contain '/': {}", s);
206 }
207
208 #[test]
209 fn year_month_zero_pads_month(year in 1u32..=9999u32, month in 1u32..=12u32) {
210 let s = PubDate::with_month(year, month).to_pubmed_string();
211 let parts: Vec<&str> = s.split('/').collect();
212 prop_assert_eq!(parts.len(), 2, "expected YYYY/MM format: {}", s);
213 prop_assert_eq!(parts[1].len(), 2, "month should be zero-padded: {}", s);
214 }
215
216 #[test]
217 fn full_date_zero_pads_month_and_day(
218 year in 1u32..=9999u32,
219 month in 1u32..=12u32,
220 day in 1u32..=31u32,
221 ) {
222 let s = PubDate::with_day(year, month, day).to_pubmed_string();
223 let parts: Vec<&str> = s.split('/').collect();
224 prop_assert_eq!(parts.len(), 3, "expected YYYY/MM/DD format: {}", s);
225 prop_assert_eq!(parts[1].len(), 2, "month should be zero-padded: {}", s);
226 prop_assert_eq!(parts[2].len(), 2, "day should be zero-padded: {}", s);
227 }
228 }
229 }
230}