edtf/level_1/
sorting.rs

1use super::*;
2use chrono::NaiveTime;
3use core::cmp::Ordering;
4
5/// # Sort orders
6///
7/// Ord is not implemented on Edtf, because Edtf's Eq and Hash implementations usefully
8/// differentiate between e.g. `2010-08-12` and `2010-08-12T00:00:00Z`, and sorting Edtfs on
9/// by the points in time they represent is incompatible with that. Edtf's Eq/Hash
10/// implementations should be thought of as being roughly equivalent to a string comparison on
11/// the underlying EDTF format, whereas this function sorts on an actual timeline.
12///
13/// All the sort orders here convert any datetimes to UTC, including "no datetime" which is just
14/// presumed to be UTC.
15///
16impl Edtf {
17    /// A type that is sortable in a reasonable way, first using [Edtf::sort_order_start] and tie-breaking
18    /// with [Edtf::sort_order_end]. Use with `Vec::sort_by_key` etc.
19    ///
20    /// ```
21    /// use edtf::level_1::Edtf;
22    /// let a = Edtf::parse("2009-08").unwrap();
23    /// let b = Edtf::parse("2009/2010").unwrap();
24    /// let c = Edtf::parse("2008/2012").unwrap();
25    /// let d = Edtf::parse("../2011").unwrap();
26    /// let e = Edtf::parse("2008/2011").unwrap();
27    /// let mut edtfs = vec![a, b, c, d, e];
28    /// // you can't sort this directly
29    /// // edtfs.sort();
30    /// edtfs.sort_by_key(|a| a.sort_order());
31    /// assert_eq!(edtfs, vec![d, e, c, b, a])
32    /// ```
33    pub fn sort_order(&self) -> SortOrder {
34        SortOrder(*self)
35    }
36
37    /// A sort order that sorts by the EDTF's start point. Use with `Vec::sort_by_key` etc.
38    ///
39    /// An open range on the left is considered to be negative infinity.
40    ///
41    /// ```
42    /// use edtf::level_1::Edtf;
43    /// let a = Edtf::parse("2009-08").unwrap();
44    /// let b = Edtf::parse("2009/2010").unwrap();
45    /// let c = Edtf::parse("2008/2012").unwrap();
46    /// let d = Edtf::parse("../2011").unwrap();
47    /// let e = Edtf::parse("2008/2011").unwrap();
48    /// let mut edtfs = vec![a, b, c, d, e];
49    /// edtfs.sort_by_key(|a| a.sort_order_start());
50    /// assert_eq!(edtfs, vec![d, c, e, b, a])
51    /// ```
52    pub fn sort_order_start(&self) -> SortOrderStart {
53        SortOrderStart(edtf_start_date(self))
54    }
55
56    /// A sort order that sorts by the EDTF's end point. Use with `Vec::sort_by_key` etc.
57    ///
58    /// An open range on the right is considered to be infinity.
59    ///
60    /// ```
61    /// use edtf::level_1::Edtf;
62    /// let a = Edtf::parse("2009-08").unwrap();
63    /// let b = Edtf::parse("2009/2010").unwrap();
64    /// let c = Edtf::parse("2008/2012").unwrap();
65    /// let d = Edtf::parse("../2011").unwrap();
66    /// let e = Edtf::parse("2008/2011").unwrap();
67    /// let mut edtfs = vec![a, b, c, d, e];
68    /// edtfs.sort_by_key(|a| a.sort_order_end());
69    /// assert_eq!(edtfs, vec![a, b, d, e, c])
70    /// ```
71    pub fn sort_order_end(&self) -> SortOrderEnd {
72        SortOrderEnd(edtf_end_date(self))
73    }
74}
75
76/// See [Edtf::sort_order_start]
77#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
78#[cfg(feature = "chrono")]
79#[derive(PartialEq, Eq, PartialOrd, Ord)]
80pub struct SortOrderStart(Infinite);
81
82/// See [Edtf::sort_order_end]
83#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
84#[cfg(feature = "chrono")]
85#[derive(PartialEq, Eq, PartialOrd, Ord)]
86pub struct SortOrderEnd(Infinite);
87
88/// See [Edtf::sort_order]
89#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
90#[cfg(feature = "chrono")]
91pub struct SortOrder(Edtf);
92
93impl Ord for SortOrder {
94    fn cmp(&self, other: &Self) -> Ordering {
95        sort_order(&self.0, &other.0)
96    }
97}
98
99impl PartialOrd for SortOrder {
100    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
101        Some(self.cmp(other))
102    }
103}
104
105impl PartialEq for SortOrder {
106    fn eq(&self, other: &Self) -> bool {
107        self.cmp(other).is_eq()
108    }
109}
110
111impl Eq for SortOrder {}
112
113fn sort_order(a: &Edtf, b: &Edtf) -> Ordering {
114    edtf_start_date(a)
115        .cmp(&edtf_start_date(b))
116        .then_with(|| edtf_end_date(a).cmp(&edtf_end_date(b)))
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
120enum Infinite {
121    NegativeInfinity,
122    YYearNegative(i64),
123    Value(Date, NaiveTime),
124    YYearPositive(i64),
125    Infinity,
126}
127
128impl From<Date> for Infinite {
129    fn from(d: Date) -> Self {
130        Self::Value(d, NaiveTime::from_num_seconds_from_midnight(0, 0))
131    }
132}
133
134fn edtf_start_date(edtf: &Edtf) -> Infinite {
135    use self::Infinite::*;
136    match *edtf {
137        Edtf::Date(d) => d.into(),
138        Edtf::Interval(d, _) => d.into(),
139        Edtf::IntervalFrom(d, _) => d.into(),
140        // this sorts first, which makes sense
141        Edtf::IntervalTo(_, _) => NegativeInfinity,
142        Edtf::DateTime(d) => {
143            let tz = chrono::Utc;
144            let dt = d.to_chrono(&tz);
145            let date = dt.date().naive_utc();
146            let time = dt.time();
147            Value(date.into(), time)
148        }
149        Edtf::YYear(y) => {
150            let val = y.value();
151            if val < i32::MIN as i64 {
152                YYearNegative(val)
153            } else if val > i32::MAX as i64 {
154                YYearPositive(val)
155            } else {
156                let v32 = val as i32;
157                if !Date::year_in_range(v32) {
158                    if v32 < 0 {
159                        YYearNegative(val)
160                    } else {
161                        YYearPositive(val)
162                    }
163                } else {
164                    Date::from_ymd(v32, 0, 0).into()
165                }
166            }
167        }
168    }
169}
170
171fn edtf_end_date(edtf: &Edtf) -> Infinite {
172    use self::Infinite::*;
173    match *edtf {
174        Edtf::Date(_) | Edtf::DateTime(_) | Edtf::YYear(_) => edtf_start_date(edtf),
175        Edtf::Interval(_, d) => d.into(),
176        Edtf::IntervalFrom(_, _) => Infinity,
177        Edtf::IntervalTo(_, d) => d.into(),
178    }
179}
180
181#[cfg(test)]
182fn cmp(a: &str, b: &str) -> Ordering {
183    sort_order(&Edtf::parse(a).unwrap(), &Edtf::parse(b).unwrap())
184}
185
186#[test]
187fn test_cmp_single() {
188    assert_eq!(cmp("2009", "2010"), Ordering::Less);
189    assert_eq!(cmp("2011", "2010"), Ordering::Greater);
190    assert_eq!(cmp("2010", "2010"), Ordering::Equal);
191    assert_eq!(cmp("2010-08", "2010"), Ordering::Greater);
192    assert_eq!(cmp("2010-08", "2010-09"), Ordering::Less);
193    assert_eq!(cmp("2010-08", "2010-08"), Ordering::Equal);
194}
195
196#[test]
197fn test_cmp_single_interval() {
198    assert_eq!(cmp("2009", "2010/2011"), Ordering::Less);
199    assert_eq!(cmp("2011", "2009/2011"), Ordering::Greater);
200    assert_eq!(cmp("2010", "2010/2010"), Ordering::Equal);
201    assert_eq!(cmp("2010", "2010/2011"), Ordering::Less);
202    assert_eq!(cmp("2010-08", "2010/2011"), Ordering::Greater);
203}
204
205#[test]
206fn test_cmp_double_interval() {
207    // we compare first on the LHS terminal, and then tie break with the RHS
208    // 2009    2010    2011    2012    2013
209    // |---------------|
210    //         |-------|
211    assert_eq!(cmp("2009/2011", "2010/2011"), Ordering::Less);
212    // |---------------|
213    //                 |-------|
214    assert_eq!(cmp("2009/2011", "2011/2012",), Ordering::Less);
215    // |---------------|
216    //         |---------------|
217    assert_eq!(cmp("2009/2011", "2010/2012"), Ordering::Less);
218    // |---------------|
219    //                         |-------|
220    assert_eq!(cmp("2009/2011", "2012/2013"), Ordering::Less);
221    // |---------------|
222    //     |-------|
223    assert_eq!(cmp("2009/2011", "2009-03/2010-07"), Ordering::Less);
224}
225
226#[test]
227fn test_cmp_double_interval_open() {
228    // the LHS terminal being .. means it starts at the beginning of time itself, beats everything
229    // 2009    2010    2011    2012    2013
230    // ----------------|
231    //         |-------|
232    assert_eq!(cmp("../2011", "2010/2011"), Ordering::Less);
233    // ----------------|
234    // ----------------|
235    assert_eq!(cmp("../2011", "../2011"), Ordering::Equal);
236    // ----------------|
237    //         |-------|
238    assert_eq!(cmp("../2011", "2010/2011"), Ordering::Less);
239    // and now for the RHS being open
240    //
241    //         |---------------
242    //         |-------|
243    assert_eq!(cmp("2010/..", "2010/2011",), Ordering::Greater);
244}
245
246#[test]
247fn test_cmp_yyear() {
248    assert_eq!(cmp("Y-10000", "2010"), Ordering::Less);
249    assert_eq!(cmp("Y10000", "2010"), Ordering::Greater);
250    assert_eq!(cmp("Y10000", "2010/.."), Ordering::Greater);
251    assert_eq!(cmp("Y-10000", "2010/.."), Ordering::Less);
252    assert_eq!(cmp("Y-10000", "../2010"), Ordering::Greater);
253}
254
255#[test]
256fn test_cmp_datetime() {
257    assert_eq!(cmp("2010-08-12T00:00:00Z", "2010-08-12"), Ordering::Equal);
258    assert_eq!(
259        cmp("2010-08-12T00:00:00Z", "2010-08-12T00:00:05Z"),
260        Ordering::Less
261    );
262    assert_eq!(
263        cmp("2010-08-12T00:00:00Z", "2010-08-12T00:00:00-01:00"),
264        Ordering::Less
265    );
266    // the first one is on the 12th in the -01:00 timezone, but convert it to UTC and it's 50
267    // minutes past midnight on the 13th.
268    assert_eq!(
269        cmp("2010-08-12T23:50:00-01:00", "2010-08-13"),
270        Ordering::Greater
271    );
272}