<img width="624" alt="image" src="https://github.com/user-attachments/assets/3e6528a0-d649-42a9-9c82-f1e06539ec46"> <img width="617" alt="image" src="https://github.com/user-attachments/assets/370dfa05-e8b1-4aa6-8eda-070d6f4a2661">
181 lines
5.4 KiB
Rust
181 lines
5.4 KiB
Rust
use chrono::{Datelike, Duration, NaiveDate};
|
|
|
|
trait NaiveDateExt {
|
|
fn days_in_month(&self) -> i32;
|
|
fn is_leap_year(&self) -> bool;
|
|
}
|
|
|
|
impl NaiveDateExt for chrono::NaiveDate {
|
|
fn days_in_month(&self) -> i32 {
|
|
let month = self.month();
|
|
match month {
|
|
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
|
|
4 | 6 | 9 | 11 => 30,
|
|
2 => {
|
|
if self.is_leap_year() {
|
|
29
|
|
} else {
|
|
28
|
|
}
|
|
}
|
|
_ => panic!("Invalid month: {}", month),
|
|
}
|
|
}
|
|
|
|
fn is_leap_year(&self) -> bool {
|
|
let year = self.year();
|
|
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn days_in_month(year: i32, month: u32) -> Vec<Vec<NaiveDate>> {
|
|
let mut year = year;
|
|
let mut month = month;
|
|
if month > 12 {
|
|
year += 1;
|
|
month = 1;
|
|
}
|
|
if month < 1 {
|
|
year -= 1;
|
|
month = 12;
|
|
}
|
|
|
|
let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
|
|
let num_days = date.days_in_month();
|
|
let start_weekday = date.weekday().num_days_from_sunday();
|
|
|
|
// Get the days in the month, 2023-02 will returns
|
|
// "29|30|31| 1| 2| 3| 4",
|
|
// " 5| 6| 7| 8| 9|10|11",
|
|
// "12|13|14|15|16|17|18",
|
|
// "19|20|21|22|23|24|25",
|
|
// "26|27|28| 1| 2| 3| 4",
|
|
let mut days = vec![];
|
|
for n in 0..5 {
|
|
let mut week_days = vec![];
|
|
for weekday in 0..7 {
|
|
let (mut y, mut m) = (year, month);
|
|
|
|
// If the day is less than the start weekday, we need to go back to the previous month.
|
|
if n == 0 && weekday < start_weekday {
|
|
m = if m == 1 { 12 } else { m - 1 };
|
|
y = if m == 1 { year - 1 } else { y };
|
|
}
|
|
|
|
// If start_weekday is 3, and n is 0 and weekday is 3, then day is 1.
|
|
// If start_weekday is 3, and n is 1 and weekday is 4, then day is 9.
|
|
let day = n * 7 + weekday as i32 - start_weekday as i32;
|
|
|
|
// If the day is greater than the number of days in the month, we need to go to the next month.
|
|
if day > num_days {
|
|
m = if m == 12 { 1 } else { m + 1 };
|
|
y = if m == 1 { year + 1 } else { y };
|
|
}
|
|
|
|
#[allow(clippy::expect_fun_call)]
|
|
let date = date
|
|
.checked_add_signed(Duration::days(day as i64))
|
|
.expect(&format!("invalid date {}-{} days {}", y, m, day));
|
|
week_days.push(date);
|
|
}
|
|
|
|
days.push(week_days);
|
|
}
|
|
|
|
days
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use chrono::{Datelike, NaiveDate};
|
|
|
|
use super::{days_in_month, NaiveDateExt};
|
|
|
|
#[test]
|
|
fn test_days_in_month() {
|
|
assert_eq!(
|
|
NaiveDate::from_ymd_opt(2024, 2, 1).unwrap().days_in_month(),
|
|
29
|
|
);
|
|
assert_eq!(
|
|
NaiveDate::from_ymd_opt(2023, 2, 1).unwrap().days_in_month(),
|
|
28
|
|
);
|
|
assert_eq!(
|
|
NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().days_in_month(),
|
|
31
|
|
);
|
|
assert_eq!(
|
|
NaiveDate::from_ymd_opt(2023, 4, 1).unwrap().days_in_month(),
|
|
30
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_days() {
|
|
#[track_caller]
|
|
fn assert_case(date: NaiveDate, expected: Vec<&str>) {
|
|
let out = days_in_month(date.year(), date.month())
|
|
.iter()
|
|
.map(|week| {
|
|
week.iter()
|
|
.map(|d| {
|
|
if d.year() == date.year() && d.month() == date.month() {
|
|
format!("{:2}", d.day())
|
|
} else if d.year() == date.year() {
|
|
format!("{}-{}", d.month(), d.day())
|
|
} else {
|
|
format!("{}-{}-{}", d.year(), d.month(), d.day())
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join("|")
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
assert_eq!(out, expected);
|
|
}
|
|
|
|
assert_case(
|
|
NaiveDate::from_ymd_opt(2024, 8, 1).unwrap(),
|
|
vec![
|
|
"7-28|7-29|7-30|7-31| 1| 2| 3",
|
|
" 4| 5| 6| 7| 8| 9|10",
|
|
"11|12|13|14|15|16|17",
|
|
"18|19|20|21|22|23|24",
|
|
"25|26|27|28|29|30|31",
|
|
],
|
|
);
|
|
assert_case(
|
|
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
|
vec![
|
|
"2024-12-29|2024-12-30|2024-12-31| 1| 2| 3| 4",
|
|
" 5| 6| 7| 8| 9|10|11",
|
|
"12|13|14|15|16|17|18",
|
|
"19|20|21|22|23|24|25",
|
|
"26|27|28|29|30|31|2-1",
|
|
],
|
|
);
|
|
|
|
assert_case(
|
|
NaiveDate::from_ymd_opt(2024, 2, 1).unwrap(),
|
|
vec![
|
|
"1-28|1-29|1-30|1-31| 1| 2| 3",
|
|
" 4| 5| 6| 7| 8| 9|10",
|
|
"11|12|13|14|15|16|17",
|
|
"18|19|20|21|22|23|24",
|
|
"25|26|27|28|29|3-1|3-2",
|
|
],
|
|
);
|
|
assert_case(
|
|
NaiveDate::from_ymd_opt(2023, 2, 20).unwrap(),
|
|
vec![
|
|
"1-29|1-30|1-31| 1| 2| 3| 4",
|
|
" 5| 6| 7| 8| 9|10|11",
|
|
"12|13|14|15|16|17|18",
|
|
"19|20|21|22|23|24|25",
|
|
"26|27|28|3-1|3-2|3-3|3-4",
|
|
],
|
|
);
|
|
}
|
|
}
|