|
1 | 1 | use std::fmt; |
| 2 | +use std::time::Duration; |
| 3 | + |
| 4 | +use super::ValueError; |
2 | 5 |
|
3 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
4 | 7 | pub struct IntervalValue { |
@@ -40,6 +43,128 @@ pub struct IntervalValue { |
40 | 43 | pub fractional_seconds_precision: Option<u64>, |
41 | 44 | } |
42 | 45 |
|
| 46 | +impl IntervalValue { |
| 47 | + /// Get Either the number of Months or the Duration specified by this interval |
| 48 | + /// |
| 49 | + /// # Errors |
| 50 | + /// |
| 51 | + /// If a required field is missing (i.e. there is no value) or the `TO` field is wrong |
| 52 | + pub fn computed(&self) -> Result<Interval, ValueError> { |
| 53 | + use DateTimeField::*; |
| 54 | + match &self.leading_field { |
| 55 | + Year => match &self.last_field { |
| 56 | + Some(Month) => Ok(Interval::Months( |
| 57 | + self.positivity() * self.parsed.year.unwrap_or(0) as i64 * 12 |
| 58 | + + self.parsed.month.unwrap_or(0) as i64, |
| 59 | + )), |
| 60 | + Some(Year) | None => self |
| 61 | + .parsed |
| 62 | + .year |
| 63 | + .ok_or_else(|| ValueError("No YEAR provided".into())) |
| 64 | + .map(|year| Interval::Months(self.positivity() * year as i64 * 12)), |
| 65 | + Some(invalid) => Err(ValueError(format!( |
| 66 | + "Invalid specifier for YEAR precision: {}", |
| 67 | + &invalid |
| 68 | + ))), |
| 69 | + }, |
| 70 | + Month => match &self.last_field { |
| 71 | + Some(Month) | None => self |
| 72 | + .parsed |
| 73 | + .month |
| 74 | + .ok_or_else(|| ValueError("No MONTH provided".into())) |
| 75 | + .map(|m| Interval::Months(self.positivity() * m as i64)), |
| 76 | + Some(invalid) => Err(ValueError(format!( |
| 77 | + "Invalid specifier for MONTH precision: {}", |
| 78 | + &invalid |
| 79 | + ))), |
| 80 | + }, |
| 81 | + durationlike_field => { |
| 82 | + let mut seconds = 0u64; |
| 83 | + match self.units_of(&durationlike_field) { |
| 84 | + Some(time) => seconds += time * seconds_multiplier(&durationlike_field), |
| 85 | + None => { |
| 86 | + return Err(ValueError(format!( |
| 87 | + "No {} provided in value string for {}", |
| 88 | + durationlike_field, self.value |
| 89 | + ))) |
| 90 | + } |
| 91 | + } |
| 92 | + let min_field = &self |
| 93 | + .last_field |
| 94 | + .clone() |
| 95 | + .unwrap_or_else(|| durationlike_field.clone()); |
| 96 | + for field in durationlike_field |
| 97 | + .clone() |
| 98 | + .into_iter() |
| 99 | + .take_while(|f| f <= min_field) |
| 100 | + { |
| 101 | + if let Some(time) = self.units_of(&field) { |
| 102 | + seconds += time * seconds_multiplier(&field); |
| 103 | + } |
| 104 | + } |
| 105 | + let duration = match (min_field, self.parsed.nano) { |
| 106 | + (DateTimeField::Second, Some(nanos)) => Duration::new(seconds, nanos), |
| 107 | + (_, _) => Duration::from_secs(seconds), |
| 108 | + }; |
| 109 | + Ok(Interval::Duration { |
| 110 | + is_positive: self.parsed.is_positive, |
| 111 | + duration, |
| 112 | + }) |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + /// Retrieve the number that we parsed out of the literal string for the `field` |
| 118 | + fn units_of(&self, field: &DateTimeField) -> Option<u64> { |
| 119 | + match field { |
| 120 | + DateTimeField::Year => self.parsed.year, |
| 121 | + DateTimeField::Month => self.parsed.month, |
| 122 | + DateTimeField::Day => self.parsed.day, |
| 123 | + DateTimeField::Hour => self.parsed.hour, |
| 124 | + DateTimeField::Minute => self.parsed.minute, |
| 125 | + DateTimeField::Second => self.parsed.second, |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + /// `1` if is_positive, otherwise `-1` |
| 130 | + fn positivity(&self) -> i64 { |
| 131 | + if self.parsed.is_positive { |
| 132 | + 1 |
| 133 | + } else { |
| 134 | + -1 |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +fn seconds_multiplier(field: &DateTimeField) -> u64 { |
| 140 | + match field { |
| 141 | + DateTimeField::Day => 60 * 60 * 24, |
| 142 | + DateTimeField::Hour => 60 * 60, |
| 143 | + DateTimeField::Minute => 60, |
| 144 | + DateTimeField::Second => 1, |
| 145 | + _other => unreachable!("Do not call with a non-duration field"), |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +/// The result of parsing an `INTERVAL '<value>' <unit> [TO <precision>]` |
| 150 | +/// |
| 151 | +/// Units of type `YEAR` or `MONTH` are semantically some multiple of months, |
| 152 | +/// which are not well defined, and this parser normalizes them to some number |
| 153 | +/// of months. |
| 154 | +/// |
| 155 | +/// Intervals of unit [`DateTimeField::Day`] or smaller are semantically a |
| 156 | +/// multiple of seconds. |
| 157 | +#[derive(Debug, Clone, Copy, PartialEq)] |
| 158 | +pub enum Interval { |
| 159 | + /// A possibly negative number of months for field types like `YEAR` |
| 160 | + Months(i64), |
| 161 | + /// An actual timespan, possibly negative, because why not |
| 162 | + Duration { |
| 163 | + is_positive: bool, |
| 164 | + duration: Duration, |
| 165 | + }, |
| 166 | +} |
| 167 | + |
43 | 168 | /// All of the fields that can appear in a literal `TIMESTAMP` or `INTERVAL` string |
44 | 169 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
45 | 170 | pub struct ParsedDateTime { |
|
0 commit comments