mirror of
https://github.com/Xevion/banner.git
synced 2025-12-17 02:11:13 -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.
|
/// Retrieves meeting time information for a course.
|
||||||
pub async fn get_course_meeting_time(
|
pub async fn get_course_meeting_time(
|
||||||
&self,
|
&self,
|
||||||
term: i32,
|
term: &str,
|
||||||
crn: i32,
|
crn: i32,
|
||||||
) -> Result<Vec<MeetingScheduleInfo>> {
|
) -> Result<Vec<MeetingScheduleInfo>> {
|
||||||
let url = format!("{}/searchResults/getFacultyMeetingTimes", self.base_url);
|
let url = format!("{}/searchResults/getFacultyMeetingTimes", self.base_url);
|
||||||
let params = [
|
let params = [("term", term), ("courseReferenceNumber", &crn.to_string())];
|
||||||
("term", &term.to_string()),
|
|
||||||
("courseReferenceNumber", &crn.to_string()),
|
|
||||||
];
|
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.client
|
.client
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{ops::RangeInclusive, str::FromStr};
|
use std::{ops::RangeInclusive, str::FromStr};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use chrono::{Datelike, Local, NaiveDate};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// The current year at the time of compilation
|
/// The current year at the time of compilation
|
||||||
@@ -18,6 +19,15 @@ pub struct Term {
|
|||||||
pub season: Season,
|
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
|
/// Represents a season within a term
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum Season {
|
pub enum Season {
|
||||||
@@ -26,6 +36,149 @@ pub enum Season {
|
|||||||
Summer,
|
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 {
|
impl ToString for Term {
|
||||||
/// Returns the term in the format YYYYXX, where YYYY is the year and XX is the season code
|
/// Returns the term in the format YYYYXX, where YYYY is the year and XX is the season code
|
||||||
fn to_string(&self) -> String {
|
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 {
|
impl FromStr for Season {
|
||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user