pubmed_client/pubmed/query/
dates.rs

1//! Date filtering methods for PubMed search queries
2
3use super::{PubDate, SearchQuery};
4
5impl SearchQuery {
6    /// Filter by publication date range
7    ///
8    /// # Arguments
9    ///
10    /// * `start_year` - Start year (inclusive)
11    /// * `end_year` - End year (inclusive, optional)
12    ///
13    /// # Example
14    ///
15    /// ```
16    /// use pubmed_client::pubmed::SearchQuery;
17    ///
18    /// let query = SearchQuery::new()
19    ///     .query("immunotherapy")
20    ///     .date_range(2020, Some(2023));
21    /// ```
22    pub fn date_range(mut self, start_year: u32, end_year: Option<u32>) -> Self {
23        let date_filter = match end_year {
24            Some(end) => format!("{start_year}:{end}[pdat]"),
25            None => format!("{start_year}:3000[pdat]"), // Far future date
26        };
27        self.filters.push(date_filter);
28        self
29    }
30
31    /// Filter by publication date range with flexible precision
32    ///
33    /// # Arguments
34    ///
35    /// * `start` - Start date (can be year, (year, month), or (year, month, day))
36    /// * `end` - End date (optional, same format as start)
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use pubmed_client::pubmed::SearchQuery;
42    ///
43    /// // Year range
44    /// let query = SearchQuery::new()
45    ///     .query("covid vaccines")
46    ///     .published_between(2020, Some(2023));
47    ///
48    /// // Month precision
49    /// let query = SearchQuery::new()
50    ///     .query("pandemic response")
51    ///     .published_between((2020, 3), Some((2021, 12)));
52    ///
53    /// // Day precision
54    /// let query = SearchQuery::new()
55    ///     .query("outbreak analysis")
56    ///     .published_between((2020, 3, 15), Some((2020, 12, 31)));
57    ///
58    /// // Open-ended (from date onwards)
59    /// let query = SearchQuery::new()
60    ///     .query("recent research")
61    ///     .published_between(2023, None::<u32>);
62    /// ```
63    pub fn published_between<S, E>(mut self, start: S, end: Option<E>) -> Self
64    where
65        S: Into<PubDate>,
66        E: Into<PubDate>,
67    {
68        let start_date = start.into();
69        let date_filter = match end {
70            Some(end_date) => {
71                let end_date = end_date.into();
72                format!(
73                    "{}:{}[pdat]",
74                    start_date.to_pubmed_string(),
75                    end_date.to_pubmed_string()
76                )
77            }
78            None => format!("{}:3000[pdat]", start_date.to_pubmed_string()),
79        };
80        self.filters.push(date_filter);
81        self
82    }
83
84    /// Filter to articles published in a specific year
85    ///
86    /// # Arguments
87    ///
88    /// * `year` - Year to filter by
89    ///
90    /// # Example
91    ///
92    /// ```
93    /// use pubmed_client::pubmed::SearchQuery;
94    ///
95    /// let query = SearchQuery::new()
96    ///     .query("artificial intelligence")
97    ///     .published_in_year(2023);
98    /// ```
99    pub fn published_in_year(mut self, year: u32) -> Self {
100        self.filters.push(format!("{year}[pdat]"));
101        self
102    }
103
104    /// Filter by entry date (when added to PubMed database)
105    ///
106    /// # Arguments
107    ///
108    /// * `start` - Start date (can be year, (year, month), or (year, month, day))
109    /// * `end` - End date (optional, same format as start)
110    ///
111    /// # Example
112    ///
113    /// ```
114    /// use pubmed_client::pubmed::SearchQuery;
115    ///
116    /// let query = SearchQuery::new()
117    ///     .query("recent discoveries")
118    ///     .entry_date_between(2023, Some(2024));
119    /// ```
120    pub fn entry_date_between<S, E>(mut self, start: S, end: Option<E>) -> Self
121    where
122        S: Into<PubDate>,
123        E: Into<PubDate>,
124    {
125        let start_date = start.into();
126        let date_filter = match end {
127            Some(end_date) => {
128                let end_date = end_date.into();
129                format!(
130                    "{}:{}[edat]",
131                    start_date.to_pubmed_string(),
132                    end_date.to_pubmed_string()
133                )
134            }
135            None => format!("{}:3000[edat]", start_date.to_pubmed_string()),
136        };
137        self.filters.push(date_filter);
138        self
139    }
140
141    /// Filter by modification date (when last updated in PubMed database)
142    ///
143    /// # Arguments
144    ///
145    /// * `start` - Start date (can be year, (year, month), or (year, month, day))
146    /// * `end` - End date (optional, same format as start)
147    ///
148    /// # Example
149    ///
150    /// ```
151    /// use pubmed_client::pubmed::SearchQuery;
152    ///
153    /// let query = SearchQuery::new()
154    ///     .query("updated articles")
155    ///     .modification_date_between(2023, None::<u32>);
156    /// ```
157    pub fn modification_date_between<S, E>(mut self, start: S, end: Option<E>) -> Self
158    where
159        S: Into<PubDate>,
160        E: Into<PubDate>,
161    {
162        let start_date = start.into();
163        let date_filter = match end {
164            Some(end_date) => {
165                let end_date = end_date.into();
166                format!(
167                    "{}:{}[mdat]",
168                    start_date.to_pubmed_string(),
169                    end_date.to_pubmed_string()
170                )
171            }
172            None => format!("{}:3000[mdat]", start_date.to_pubmed_string()),
173        };
174        self.filters.push(date_filter);
175        self
176    }
177
178    /// Filter to articles published after a specific date
179    ///
180    /// # Arguments
181    ///
182    /// * `date` - Date after which articles were published (can be year, (year, month), or (year, month, day))
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use pubmed_client::pubmed::SearchQuery;
188    ///
189    /// // After a specific year
190    /// let query = SearchQuery::new()
191    ///     .query("crispr")
192    ///     .published_after(2020);
193    ///
194    /// // After a specific month
195    /// let query = SearchQuery::new()
196    ///     .query("covid treatment")
197    ///     .published_after((2020, 3));
198    ///
199    /// // After a specific date
200    /// let query = SearchQuery::new()
201    ///     .query("pandemic response")
202    ///     .published_after((2020, 3, 15));
203    /// ```
204    pub fn published_after<D>(self, date: D) -> Self
205    where
206        D: Into<PubDate>,
207    {
208        self.published_between(date, None::<u32>)
209    }
210
211    /// Filter to articles published before a specific date
212    ///
213    /// # Arguments
214    ///
215    /// * `date` - Date before which articles were published (can be year, (year, month), or (year, month, day))
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use pubmed_client::pubmed::SearchQuery;
221    ///
222    /// // Before a specific year
223    /// let query = SearchQuery::new()
224    ///     .query("genome sequencing")
225    ///     .published_before(2020);
226    ///
227    /// // Before a specific month
228    /// let query = SearchQuery::new()
229    ///     .query("early research")
230    ///     .published_before((2020, 3));
231    ///
232    /// // Before a specific date
233    /// let query = SearchQuery::new()
234    ///     .query("pre-pandemic studies")
235    ///     .published_before((2020, 3, 15));
236    /// ```
237    pub fn published_before<D>(self, date: D) -> Self
238    where
239        D: Into<PubDate>,
240    {
241        self.published_between(1900, Some(date))
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_date_range_with_end_year() {
251        let query = SearchQuery::new().date_range(2020, Some(2023));
252        assert_eq!(query.build(), "2020:2023[pdat]");
253    }
254
255    #[test]
256    fn test_date_range_without_end_year() {
257        let query = SearchQuery::new().date_range(2020, None);
258        assert_eq!(query.build(), "2020:3000[pdat]");
259    }
260
261    #[test]
262    fn test_published_in_year() {
263        let query = SearchQuery::new().published_in_year(2023);
264        assert_eq!(query.build(), "2023[pdat]");
265    }
266
267    #[test]
268    fn test_published_between_years() {
269        let query = SearchQuery::new().published_between(2020, Some(2023));
270        assert_eq!(query.build(), "2020:2023[pdat]");
271    }
272
273    #[test]
274    fn test_published_between_months() {
275        let query = SearchQuery::new().published_between((2020, 3), Some((2021, 12)));
276        assert_eq!(query.build(), "2020/03:2021/12[pdat]");
277    }
278
279    #[test]
280    fn test_published_between_days() {
281        let query = SearchQuery::new().published_between((2020, 3, 15), Some((2020, 12, 31)));
282        assert_eq!(query.build(), "2020/03/15:2020/12/31[pdat]");
283    }
284
285    #[test]
286    fn test_published_after_year() {
287        let query = SearchQuery::new().published_after(2020);
288        assert_eq!(query.build(), "2020:3000[pdat]");
289    }
290
291    #[test]
292    fn test_published_after_month() {
293        let query = SearchQuery::new().published_after((2020, 3));
294        assert_eq!(query.build(), "2020/03:3000[pdat]");
295    }
296
297    #[test]
298    fn test_published_after_day() {
299        let query = SearchQuery::new().published_after((2020, 3, 15));
300        assert_eq!(query.build(), "2020/03/15:3000[pdat]");
301    }
302
303    #[test]
304    fn test_published_before_year() {
305        let query = SearchQuery::new().published_before(2020);
306        assert_eq!(query.build(), "1900:2020[pdat]");
307    }
308
309    #[test]
310    fn test_published_before_month() {
311        let query = SearchQuery::new().published_before((2020, 3));
312        assert_eq!(query.build(), "1900:2020/03[pdat]");
313    }
314
315    #[test]
316    fn test_published_before_day() {
317        let query = SearchQuery::new().published_before((2020, 3, 15));
318        assert_eq!(query.build(), "1900:2020/03/15[pdat]");
319    }
320
321    #[test]
322    fn test_entry_date_between() {
323        let query = SearchQuery::new().entry_date_between(2023, Some(2024));
324        assert_eq!(query.build(), "2023:2024[edat]");
325    }
326
327    #[test]
328    fn test_entry_date_between_open_ended() {
329        let query = SearchQuery::new().entry_date_between((2023, 6), None::<u32>);
330        assert_eq!(query.build(), "2023/06:3000[edat]");
331    }
332
333    #[test]
334    fn test_modification_date_between() {
335        let query =
336            SearchQuery::new().modification_date_between((2023, 1, 1), Some((2023, 12, 31)));
337        assert_eq!(query.build(), "2023/01/01:2023/12/31[mdat]");
338    }
339
340    #[test]
341    fn test_modification_date_between_open_ended() {
342        let query = SearchQuery::new().modification_date_between(2023, None::<u32>);
343        assert_eq!(query.build(), "2023:3000[mdat]");
344    }
345
346    #[test]
347    fn test_combined_date_filters() {
348        let query = SearchQuery::new()
349            .query("covid-19")
350            .published_between(2020, Some(2023))
351            .entry_date_between((2023, 1), None::<u32>);
352
353        assert_eq!(
354            query.build(),
355            "covid-19 AND 2020:2023[pdat] AND 2023/01:3000[edat]"
356        );
357    }
358
359    #[test]
360    fn test_multiple_date_ranges() {
361        let query = SearchQuery::new()
362            .date_range(2020, Some(2021))
363            .date_range(2022, Some(2023));
364
365        assert_eq!(query.build(), "2020:2021[pdat] AND 2022:2023[pdat]");
366    }
367
368    #[test]
369    fn test_date_precision_mixing() {
370        let query = SearchQuery::new()
371            .published_between(2020, Some((2023, 6, 15)))
372            .entry_date_between((2023, 1), Some(2024));
373
374        let built = query.build();
375        assert!(built.contains("2020:2023/06/15[pdat]"));
376        assert!(built.contains("2023/01:2024[edat]"));
377    }
378
379    #[test]
380    fn test_multiple_date_types() {
381        let query = SearchQuery::new()
382            .query("research")
383            .published_between(2020, Some(2023))
384            .entry_date_between((2023, 1), Some((2023, 12)))
385            .modification_date_between((2023, 6), None::<u32>);
386
387        let built = query.build();
388        assert!(built.contains("2020:2023[pdat]"));
389        assert!(built.contains("2023/01:2023/12[edat]"));
390        assert!(built.contains("2023/06:3000[mdat]"));
391    }
392
393    #[test]
394    fn test_edge_year_values() {
395        let query = SearchQuery::new().date_range(1, Some(9999));
396        assert_eq!(query.build(), "1:9999[pdat]");
397    }
398
399    #[test]
400    fn test_same_start_end_date() {
401        let query = SearchQuery::new().published_between(2023, Some(2023));
402        assert_eq!(query.build(), "2023:2023[pdat]");
403    }
404
405    #[test]
406    fn test_published_between_none_end() {
407        let query = SearchQuery::new().published_between(2020, None::<u32>);
408        assert_eq!(query.build(), "2020:3000[pdat]");
409    }
410}