1
use chrono::{DateTime, Datelike, NaiveDate, Utc};
2

            
3
2
pub(super) fn generate_month_boundaries(
4
2
    from: DateTime<Utc>,
5
2
    to: DateTime<Utc>,
6
2
) -> Vec<(String, DateTime<Utc>, DateTime<Utc>)> {
7
2
    let mut periods = Vec::new();
8
2
    let mut current = first_of_month(from);
9
7
    while current < to {
10
5
        let next = next_month(current);
11
5
        let label = current.format("%Y-%m").to_string();
12
5
        periods.push((label, current, next.min(to)));
13
5
        current = next;
14
5
    }
15
2
    periods
16
2
}
17

            
18
2
pub(super) fn generate_quarter_boundaries(
19
2
    from: DateTime<Utc>,
20
2
    to: DateTime<Utc>,
21
2
) -> Vec<(String, DateTime<Utc>, DateTime<Utc>)> {
22
2
    let mut periods = Vec::new();
23
2
    let mut current = first_of_quarter(from);
24
6
    while current < to {
25
4
        let next = next_quarter(current);
26
4
        let q = (current.month0() / 3) + 1;
27
4
        let label = format!("{}-Q{q}", current.year());
28
4
        periods.push((label, current, next.min(to)));
29
4
        current = next;
30
4
    }
31
2
    periods
32
2
}
33

            
34
1
pub(super) fn generate_year_boundaries(
35
1
    from: DateTime<Utc>,
36
1
    to: DateTime<Utc>,
37
1
) -> Vec<(String, DateTime<Utc>, DateTime<Utc>)> {
38
1
    let mut periods = Vec::new();
39
1
    let mut current = first_of_year(from);
40
4
    while current < to {
41
3
        let next = next_year(current);
42
3
        let label = current.year().to_string();
43
3
        periods.push((label, current, next.min(to)));
44
3
        current = next;
45
3
    }
46
1
    periods
47
1
}
48

            
49
2
fn first_of_month(dt: DateTime<Utc>) -> DateTime<Utc> {
50
2
    NaiveDate::from_ymd_opt(dt.year(), dt.month(), 1)
51
2
        .unwrap()
52
2
        .and_hms_opt(0, 0, 0)
53
2
        .unwrap()
54
2
        .and_utc()
55
2
}
56

            
57
5
fn next_month(dt: DateTime<Utc>) -> DateTime<Utc> {
58
5
    let (y, m) = if dt.month() == 12 {
59
        (dt.year() + 1, 1)
60
    } else {
61
5
        (dt.year(), dt.month() + 1)
62
    };
63
5
    NaiveDate::from_ymd_opt(y, m, 1)
64
5
        .unwrap()
65
5
        .and_hms_opt(0, 0, 0)
66
5
        .unwrap()
67
5
        .and_utc()
68
5
}
69

            
70
2
fn first_of_quarter(dt: DateTime<Utc>) -> DateTime<Utc> {
71
2
    let qm = (dt.month0() / 3) * 3 + 1;
72
2
    NaiveDate::from_ymd_opt(dt.year(), qm, 1)
73
2
        .unwrap()
74
2
        .and_hms_opt(0, 0, 0)
75
2
        .unwrap()
76
2
        .and_utc()
77
2
}
78

            
79
4
fn next_quarter(dt: DateTime<Utc>) -> DateTime<Utc> {
80
4
    let qm = (dt.month0() / 3) * 3 + 1;
81
4
    let (y, m) = if qm + 3 > 12 {
82
        (dt.year() + 1, qm + 3 - 12)
83
    } else {
84
4
        (dt.year(), qm + 3)
85
    };
86
4
    NaiveDate::from_ymd_opt(y, m, 1)
87
4
        .unwrap()
88
4
        .and_hms_opt(0, 0, 0)
89
4
        .unwrap()
90
4
        .and_utc()
91
4
}
92

            
93
1
fn first_of_year(dt: DateTime<Utc>) -> DateTime<Utc> {
94
1
    NaiveDate::from_ymd_opt(dt.year(), 1, 1)
95
1
        .unwrap()
96
1
        .and_hms_opt(0, 0, 0)
97
1
        .unwrap()
98
1
        .and_utc()
99
1
}
100

            
101
3
fn next_year(dt: DateTime<Utc>) -> DateTime<Utc> {
102
3
    NaiveDate::from_ymd_opt(dt.year() + 1, 1, 1)
103
3
        .unwrap()
104
3
        .and_hms_opt(0, 0, 0)
105
3
        .unwrap()
106
3
        .and_utc()
107
3
}