mirror of
https://github.com/Xevion/banner.git
synced 2025-12-15 08:11:11 -06:00
feat: add current term identification, term point state machine
This commit is contained in:
@@ -198,14 +198,11 @@ impl BannerApi {
|
||||
/// Retrieves meeting time information for a course.
|
||||
pub async fn get_course_meeting_time(
|
||||
&self,
|
||||
term: i32,
|
||||
term: &str,
|
||||
crn: i32,
|
||||
) -> Result<Vec<MeetingScheduleInfo>> {
|
||||
let url = format!("{}/searchResults/getFacultyMeetingTimes", self.base_url);
|
||||
let params = [
|
||||
("term", &term.to_string()),
|
||||
("courseReferenceNumber", &crn.to_string()),
|
||||
];
|
||||
let params = [("term", term), ("courseReferenceNumber", &crn.to_string())];
|
||||
|
||||
let response = self
|
||||
.client
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{ops::RangeInclusive, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The current year at the time of compilation
|
||||
@@ -18,6 +19,15 @@ pub struct Term {
|
||||
pub season: Season,
|
||||
}
|
||||
|
||||
/// Represents the term status at a specific point in time
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TermPoint {
|
||||
/// Currently in a term
|
||||
InTerm { current: Term },
|
||||
/// Between terms, with the next term specified
|
||||
BetweenTerms { next: Term },
|
||||
}
|
||||
|
||||
/// Represents a season within a term
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Season {
|
||||
@@ -26,6 +36,149 @@ pub enum Season {
|
||||
Summer,
|
||||
}
|
||||
|
||||
impl Term {
|
||||
/// Returns the current term status - either currently in a term or between terms
|
||||
pub fn get_current() -> TermPoint {
|
||||
let now = Local::now().naive_local();
|
||||
Self::get_status_for_date(now.date())
|
||||
}
|
||||
|
||||
/// Returns the current term status for a specific date
|
||||
pub fn get_status_for_date(date: NaiveDate) -> TermPoint {
|
||||
let literal_year = date.year() as u32;
|
||||
let day_of_year = date.ordinal() as u32;
|
||||
let ranges = Self::get_season_ranges(literal_year);
|
||||
|
||||
// If we're past the end of the summer term, we're 'in' the next school year.
|
||||
let term_year = if day_of_year > ranges.summer.end {
|
||||
literal_year + 1
|
||||
} else {
|
||||
literal_year
|
||||
};
|
||||
|
||||
if (day_of_year < ranges.spring.start) || (day_of_year >= ranges.fall.end) {
|
||||
// Fall over, Spring not yet begun
|
||||
TermPoint::BetweenTerms {
|
||||
next: Term {
|
||||
year: term_year,
|
||||
season: Season::Spring,
|
||||
},
|
||||
}
|
||||
} else if (day_of_year >= ranges.spring.start) && (day_of_year < ranges.spring.end) {
|
||||
// Spring
|
||||
TermPoint::InTerm {
|
||||
current: Term {
|
||||
year: term_year,
|
||||
season: Season::Spring,
|
||||
},
|
||||
}
|
||||
} else if day_of_year < ranges.summer.start {
|
||||
// Spring over, Summer not yet begun
|
||||
TermPoint::BetweenTerms {
|
||||
next: Term {
|
||||
year: term_year,
|
||||
season: Season::Summer,
|
||||
},
|
||||
}
|
||||
} else if (day_of_year >= ranges.summer.start) && (day_of_year < ranges.summer.end) {
|
||||
// Summer
|
||||
TermPoint::InTerm {
|
||||
current: Term {
|
||||
year: term_year,
|
||||
season: Season::Summer,
|
||||
},
|
||||
}
|
||||
} else if day_of_year < ranges.fall.start {
|
||||
// Summer over, Fall not yet begun
|
||||
TermPoint::BetweenTerms {
|
||||
next: Term {
|
||||
year: term_year,
|
||||
season: Season::Fall,
|
||||
},
|
||||
}
|
||||
} else if (day_of_year >= ranges.fall.start) && (day_of_year < ranges.fall.end) {
|
||||
// Fall
|
||||
TermPoint::InTerm {
|
||||
current: Term {
|
||||
year: term_year,
|
||||
season: Season::Fall,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// This should never happen, but Rust requires exhaustive matching
|
||||
panic!("Impossible code reached (dayOfYear: {})", day_of_year);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the start and end day of each term for the given year.
|
||||
/// The ranges are inclusive of the start day and exclusive of the end day.
|
||||
fn get_season_ranges(year: u32) -> SeasonRanges {
|
||||
let spring_start = NaiveDate::from_ymd_opt(year as i32, 1, 14)
|
||||
.unwrap()
|
||||
.ordinal() as u32;
|
||||
let spring_end = NaiveDate::from_ymd_opt(year as i32, 5, 1)
|
||||
.unwrap()
|
||||
.ordinal() as u32;
|
||||
let summer_start = NaiveDate::from_ymd_opt(year as i32, 5, 25)
|
||||
.unwrap()
|
||||
.ordinal() as u32;
|
||||
let summer_end = NaiveDate::from_ymd_opt(year as i32, 8, 15)
|
||||
.unwrap()
|
||||
.ordinal() as u32;
|
||||
let fall_start = NaiveDate::from_ymd_opt(year as i32, 8, 18)
|
||||
.unwrap()
|
||||
.ordinal() as u32;
|
||||
let fall_end = NaiveDate::from_ymd_opt(year as i32, 12, 10)
|
||||
.unwrap()
|
||||
.ordinal() as u32;
|
||||
|
||||
SeasonRanges {
|
||||
spring: YearDayRange {
|
||||
start: spring_start,
|
||||
end: spring_end,
|
||||
},
|
||||
summer: YearDayRange {
|
||||
start: summer_start,
|
||||
end: summer_end,
|
||||
},
|
||||
fall: YearDayRange {
|
||||
start: fall_start,
|
||||
end: fall_end,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a long string representation of the term (e.g., "Fall 2025")
|
||||
pub fn to_long_string(&self) -> String {
|
||||
format!("{} {}", self.season, self.year)
|
||||
}
|
||||
}
|
||||
|
||||
impl TermPoint {
|
||||
/// Returns the inner Term regardless of the status
|
||||
pub fn inner(&self) -> &Term {
|
||||
match self {
|
||||
TermPoint::InTerm { current } => current,
|
||||
TermPoint::BetweenTerms { next } => next,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the start and end day of each term within a year
|
||||
#[derive(Debug, Clone)]
|
||||
struct SeasonRanges {
|
||||
spring: YearDayRange,
|
||||
summer: YearDayRange,
|
||||
fall: YearDayRange,
|
||||
}
|
||||
|
||||
/// Represents the start and end day of a term within a year
|
||||
#[derive(Debug, Clone)]
|
||||
struct YearDayRange {
|
||||
start: u32,
|
||||
end: u32,
|
||||
}
|
||||
|
||||
impl ToString for Term {
|
||||
/// Returns the term in the format YYYYXX, where YYYY is the year and XX is the season code
|
||||
fn to_string(&self) -> String {
|
||||
@@ -44,6 +197,16 @@ impl Season {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Season {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Season::Fall => write!(f, "Fall"),
|
||||
Season::Spring => write!(f, "Spring"),
|
||||
Season::Summer => write!(f, "Summer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Season {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user