1use chrono::{Datelike, NaiveDate};
2use serde::Deserialize;
3
4const SPRING_TRAINING_FALLBACK_MONTH: u32 = 3;
7const SPRING_TRAINING_FALLBACK_DAY: u32 = 20;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
11pub enum GameType {
12 SpringTraining,
13 RegularSeason,
14}
15
16#[derive(Default, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub(crate) struct SeasonsResponse {
19 pub seasons: Vec<SeasonInfo>,
20}
21
22#[derive(Clone, Debug, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct SeasonInfo {
26 pub regular_season_start_date: NaiveDate,
27}
28
29pub fn game_type_for_date(date: NaiveDate, season_info: Option<&SeasonInfo>) -> GameType {
32 match season_info {
33 Some(info) => {
34 if date < info.regular_season_start_date {
35 GameType::SpringTraining
36 } else {
37 GameType::RegularSeason
38 }
39 }
40 None => {
41 let cutoff = NaiveDate::from_ymd_opt(
42 date.year(),
43 SPRING_TRAINING_FALLBACK_MONTH,
44 SPRING_TRAINING_FALLBACK_DAY,
45 );
46 match cutoff {
47 Some(c) if date < c => GameType::SpringTraining,
48 _ => GameType::RegularSeason,
49 }
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn test_spring_training_with_season_info() {
60 let info = SeasonInfo {
61 regular_season_start_date: NaiveDate::from_ymd_opt(2026, 3, 25).unwrap(),
62 };
63 let spring = NaiveDate::from_ymd_opt(2026, 3, 10).unwrap();
64 let regular = NaiveDate::from_ymd_opt(2026, 3, 25).unwrap();
65 let mid_season = NaiveDate::from_ymd_opt(2026, 7, 1).unwrap();
66
67 assert_eq!(
68 game_type_for_date(spring, Some(&info)),
69 GameType::SpringTraining
70 );
71 assert_eq!(
72 game_type_for_date(regular, Some(&info)),
73 GameType::RegularSeason
74 );
75 assert_eq!(
76 game_type_for_date(mid_season, Some(&info)),
77 GameType::RegularSeason
78 );
79 }
80
81 #[test]
82 fn test_spring_training_fallback_without_season_info() {
83 let before_cutoff = NaiveDate::from_ymd_opt(2026, 3, 1).unwrap();
84 let after_cutoff = NaiveDate::from_ymd_opt(2026, 3, 25).unwrap();
85 let on_cutoff = NaiveDate::from_ymd_opt(2026, 3, 20).unwrap();
86
87 assert_eq!(
88 game_type_for_date(before_cutoff, None),
89 GameType::SpringTraining
90 );
91 assert_eq!(
92 game_type_for_date(after_cutoff, None),
93 GameType::RegularSeason
94 );
95 assert_eq!(game_type_for_date(on_cutoff, None), GameType::RegularSeason);
96 }
97}