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

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

            
18
1
pub(super) fn generate_quarter_boundaries(
19
1
    from: DateTime<Utc>,
20
1
    to: DateTime<Utc>,
21
1
) -> Vec<(String, DateTime<Utc>, DateTime<Utc>)> {
22
1
    let mut periods = Vec::new();
23
1
    let mut current = first_of_quarter(from);
24
3
    while current < to {
25
2
        let next = next_quarter(current);
26
2
        let q = (current.month0() / 3) + 1;
27
2
        let label = format!("{}-Q{q}", current.year());
28
2
        periods.push((label, current, next.min(to)));
29
2
        current = next;
30
2
    }
31
1
    periods
32
1
}
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
3
fn first_of_month(dt: DateTime<Utc>) -> DateTime<Utc> {
50
3
    NaiveDate::from_ymd_opt(dt.year(), dt.month(), 1)
51
3
        .unwrap()
52
3
        .and_hms_opt(0, 0, 0)
53
3
        .unwrap()
54
3
        .and_utc()
55
3
}
56

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

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

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