gitql_ast/
interval.rs

1use std::cmp::Ordering;
2use std::fmt::Display;
3use std::fmt::Formatter;
4use std::ops::Div;
5use std::ops::Mul;
6
7const INTERVAL_MAX_VALUE_I: i64 = 170_000_000;
8const INTERVAL_MAX_VALUE_F: f64 = 170_000_000.0;
9
10#[derive(Default, PartialEq, Clone)]
11pub struct Interval {
12    pub years: i64,
13    pub months: i64,
14    pub days: i64,
15    pub hours: i64,
16    pub minutes: i64,
17    pub seconds: f64,
18}
19
20impl Interval {
21    pub fn add(&self, other: &Interval) -> Result<Interval, String> {
22        let mut result = self.clone();
23        result.years = interval_value_or_error_i64(result.years + other.years)?;
24        result.months = interval_value_or_error_i64(result.months + other.months)?;
25        result.days = interval_value_or_error_i64(result.days + other.days)?;
26        result.hours = interval_value_or_error_i64(result.hours + other.hours)?;
27        result.minutes = interval_value_or_error_i64(result.minutes + other.minutes)?;
28        result.seconds = interval_value_or_error_f64(result.seconds + other.seconds)?;
29        Ok(result)
30    }
31
32    pub fn sub(&self, other: &Interval) -> Result<Interval, String> {
33        let mut result = self.clone();
34        result.years = interval_value_or_error_i64(result.years - other.years)?;
35        result.months = interval_value_or_error_i64(result.months - other.months)?;
36        result.days = interval_value_or_error_i64(result.days - other.days)?;
37        result.hours = interval_value_or_error_i64(result.hours - other.hours)?;
38        result.minutes = interval_value_or_error_i64(result.minutes - other.minutes)?;
39        result.seconds = interval_value_or_error_f64(result.seconds - other.seconds)?;
40        Ok(result)
41    }
42
43    pub fn mul(&self, other: i64) -> Result<Interval, String> {
44        let mut result = self.clone();
45        result.years = interval_value_or_error_i64(result.years * other)?;
46        result.months = interval_value_or_error_i64(result.months * other)?;
47        result.days = interval_value_or_error_i64(result.days * other)?;
48        result.hours = interval_value_or_error_i64(result.hours * other)?;
49        result.minutes = interval_value_or_error_i64(result.minutes * other)?;
50        result.seconds = interval_value_or_error_f64(result.seconds.mul(other as f64))?;
51        Ok(result)
52    }
53
54    pub fn div(&self, other: i64) -> Result<Interval, String> {
55        let mut result = self.clone();
56        result.years = interval_value_or_error_i64(result.years / other)?;
57        result.months = interval_value_or_error_i64(result.months / other)?;
58        result.days = interval_value_or_error_i64(result.days / other)?;
59        result.hours = interval_value_or_error_i64(result.hours / other)?;
60        result.minutes = interval_value_or_error_i64(result.minutes / other)?;
61        result.seconds = interval_value_or_error_f64(result.seconds.div(other as f64))?;
62        Ok(result)
63    }
64
65    pub fn to_seconds(&self) -> i64 {
66        let days =
67            self.years as f64 * 365.25 + self.months as f64 * (365.25 / 12.0) + self.days as f64;
68
69        let seconds = days * 24.0 * 60.0 * 60.0
70            + self.hours as f64 * 60.0 * 60.0
71            + self.minutes as f64 * 60.0
72            + self.seconds;
73
74        seconds as i64
75    }
76}
77
78impl PartialOrd for Interval {
79    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
80        let self_seconds = self.to_seconds();
81        let other_seconds = other.to_seconds();
82        self_seconds.partial_cmp(&other_seconds)
83    }
84}
85
86impl Display for Interval {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        let mut parts = Vec::new();
89
90        if self.years != 0 {
91            parts.push(format!(
92                "{} year{}",
93                self.years,
94                if self.years > 1 { "s" } else { "" }
95            ));
96        }
97        if self.months != 0 {
98            parts.push(format!(
99                "{} month{}",
100                self.months,
101                if self.months > 1 { "s" } else { "" }
102            ));
103        }
104        if self.days != 0 {
105            parts.push(format!(
106                "{} day{}",
107                self.days,
108                if self.days > 1 { "s" } else { "" }
109            ));
110        }
111
112        let (hours, minutes, seconds) = (self.hours, self.minutes, self.seconds);
113        if hours != 0 || minutes != 0 || seconds != 0f64 {
114            parts.push(format!("{hours:02}:{minutes:02}:{seconds:02}"));
115        }
116
117        if parts.is_empty() {
118            write!(f, "0 seconds")?;
119        } else {
120            write!(f, "{}", parts.join(" "))?;
121        }
122        Ok(())
123    }
124}
125
126fn interval_value_or_error_i64(value: i64) -> Result<i64, String> {
127    if (-INTERVAL_MAX_VALUE_I..=INTERVAL_MAX_VALUE_I).contains(&value) {
128        return Ok(value);
129    }
130    Err(format!("Interval value out of range {value}"))
131}
132
133fn interval_value_or_error_f64(value: f64) -> Result<f64, String> {
134    if (-INTERVAL_MAX_VALUE_F..=INTERVAL_MAX_VALUE_F).contains(&value) {
135        return Ok(value);
136    }
137    Err("Interval value out of range".to_string())
138}