Skip to content

Commit 692e957

Browse files
committed
Add same range check for weekdays sessions
1 parent 7d53bb0 commit 692e957

1 file changed

Lines changed: 126 additions & 50 deletions

File tree

crates/hotfix/src/session_schedule.rs

Lines changed: 126 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ impl SessionSchedule {
6262
));
6363
}
6464

65-
let (start, end) = self.get_session_start_and_end(dt1)?;
65+
let (start, end) = self.get_session_bounds(dt1)?;
6666
Ok(start <= *dt2 && *dt2 < end)
6767
}
6868

69-
fn get_session_start_and_end(
69+
fn get_session_bounds(
7070
&self,
7171
datetime: &DateTime<Utc>,
7272
) -> Result<(DateTime<Utc>, DateTime<Utc>)> {
@@ -78,58 +78,17 @@ impl SessionSchedule {
7878
start_time,
7979
end_time,
8080
timezone,
81-
} => {
82-
let local_datetime = datetime.with_timezone(timezone);
83-
84-
if local_datetime.time() >= *start_time {
85-
// if the datetime is greater than the start_time, they fall on the same day
86-
let start =
87-
Self::construct_utc(&local_datetime.date_naive(), start_time, timezone)?;
88-
89-
// if the end_time is smaller than the start_time, then it must be the next day
90-
let end_date = if end_time < start_time {
91-
local_datetime
92-
.date_naive()
93-
.checked_add_days(Days::new(1))
94-
.ok_or_else(|| {
95-
SessionError::InvalidSchedule("Failed to add day".to_string())
96-
})?
97-
} else {
98-
local_datetime.date_naive()
99-
};
100-
let end = Self::construct_utc(&end_date, end_time, timezone)?;
101-
Ok((start, end))
102-
} else {
103-
// if the datetime is lesser than the start_time, it must fall on the previous day
104-
let start_date = local_datetime
105-
.date_naive()
106-
.checked_sub_days(Days::new(1))
107-
.ok_or_else(|| {
108-
SessionError::InvalidSchedule("Failed to get previous day".to_string())
109-
})?;
110-
let start = Self::construct_utc(&start_date, start_time, timezone)?;
111-
let end =
112-
Self::construct_utc(&local_datetime.date_naive(), end_time, timezone)?;
113-
Ok((start, end))
114-
}
115-
}
116-
SessionSchedule::Weekdays { .. } => unimplemented!(),
81+
} => calculate_single_day_session_bounds(datetime, start_time, end_time, timezone),
82+
SessionSchedule::Weekdays {
83+
start_time,
84+
end_time,
85+
timezone,
86+
weekdays: _,
87+
} => calculate_single_day_session_bounds(datetime, start_time, end_time, timezone),
11788
SessionSchedule::Weekly { .. } => unimplemented!(),
11889
}
11990
}
12091

121-
fn construct_utc(date: &NaiveDate, time: &NaiveTime, timezone: &Tz) -> Result<DateTime<Utc>> {
122-
// TODO: do we want to handle Ambiguous and None outcomes?
123-
// these variants correspond to Python's gap and fold: https://peps.python.org/pep-0495/#terminology
124-
if let Some(dt) = date.and_time(*time).and_local_timezone(*timezone).single() {
125-
Ok(dt.to_utc())
126-
} else {
127-
Err(SessionError::InvalidSchedule(
128-
"Invalid schedule configuration: invalid time".to_string(),
129-
))
130-
}
131-
}
132-
13392
fn check_daily_schedule(
13493
datetime: &DateTime<Tz>,
13594
start_time: &NaiveTime,
@@ -273,6 +232,55 @@ impl TryFrom<Option<&ScheduleConfig>> for SessionSchedule {
273232
}
274233
}
275234

235+
fn construct_utc(date: &NaiveDate, time: &NaiveTime, timezone: &Tz) -> Result<DateTime<Utc>> {
236+
// TODO: do we want to handle Ambiguous and None outcomes?
237+
// these variants correspond to Python's gap and fold: https://peps.python.org/pep-0495/#terminology
238+
if let Some(dt) = date.and_time(*time).and_local_timezone(*timezone).single() {
239+
Ok(dt.to_utc())
240+
} else {
241+
Err(SessionError::InvalidSchedule(
242+
"Invalid schedule configuration: invalid time".to_string(),
243+
))
244+
}
245+
}
246+
247+
fn calculate_single_day_session_bounds(
248+
datetime: &DateTime<Utc>,
249+
start_time: &NaiveTime,
250+
end_time: &NaiveTime,
251+
timezone: &Tz,
252+
) -> Result<(DateTime<Utc>, DateTime<Utc>)> {
253+
let local_datetime = datetime.with_timezone(timezone);
254+
255+
if local_datetime.time() >= *start_time {
256+
// if the datetime is greater than the start_time, they fall on the same day
257+
let start = construct_utc(&local_datetime.date_naive(), start_time, timezone)?;
258+
259+
// if the end_time is smaller than the start_time, then it must be the next day
260+
let end_date = if end_time < start_time {
261+
local_datetime
262+
.date_naive()
263+
.checked_add_days(Days::new(1))
264+
.ok_or_else(|| SessionError::InvalidSchedule("Failed to add day".to_string()))?
265+
} else {
266+
local_datetime.date_naive()
267+
};
268+
let end = construct_utc(&end_date, end_time, timezone)?;
269+
Ok((start, end))
270+
} else {
271+
// if the datetime is lesser than the start_time, it must fall on the previous day
272+
let start_date = local_datetime
273+
.date_naive()
274+
.checked_sub_days(Days::new(1))
275+
.ok_or_else(|| {
276+
SessionError::InvalidSchedule("Failed to get previous day".to_string())
277+
})?;
278+
let start = construct_utc(&start_date, start_time, timezone)?;
279+
let end = construct_utc(&local_datetime.date_naive(), end_time, timezone)?;
280+
Ok((start, end))
281+
}
282+
}
283+
276284
#[cfg(test)]
277285
mod tests {
278286
use super::*;
@@ -1227,4 +1235,72 @@ mod tests {
12271235
.to_utc();
12281236
assert!(schedule.is_same_session_period(&dt7, &dt8).is_err());
12291237
}
1238+
1239+
#[test]
1240+
fn test_is_same_session_period_weekdays_utc() {
1241+
let schedule = SessionSchedule::Weekdays {
1242+
start_time: NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
1243+
end_time: NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
1244+
weekdays: vec![
1245+
Weekday::Mon,
1246+
Weekday::Tue,
1247+
Weekday::Wed,
1248+
Weekday::Thu,
1249+
Weekday::Fri,
1250+
],
1251+
timezone: Tz::UTC,
1252+
};
1253+
1254+
// 10/07/2025 is a Thursday
1255+
// two times within the same session period return true
1256+
let dt1 = DateTime::from_naive_utc_and_offset(
1257+
NaiveDate::from_ymd_opt(2025, 7, 10)
1258+
.unwrap()
1259+
.and_hms_opt(10, 0, 0)
1260+
.unwrap(),
1261+
Utc,
1262+
);
1263+
let dt2 = DateTime::from_naive_utc_and_offset(
1264+
NaiveDate::from_ymd_opt(2025, 7, 10)
1265+
.unwrap()
1266+
.and_hms_opt(15, 0, 0)
1267+
.unwrap(),
1268+
Utc,
1269+
);
1270+
assert!(schedule.is_same_session_period(&dt1, &dt2).unwrap());
1271+
assert!(schedule.is_same_session_period(&dt2, &dt1).unwrap());
1272+
1273+
// time for the next session period returns false
1274+
let dt3 = DateTime::from_naive_utc_and_offset(
1275+
NaiveDate::from_ymd_opt(2025, 7, 11)
1276+
.unwrap()
1277+
.and_hms_opt(10, 0, 0)
1278+
.unwrap(),
1279+
Utc,
1280+
);
1281+
assert!(!schedule.is_same_session_period(&dt1, &dt3).unwrap());
1282+
assert!(!schedule.is_same_session_period(&dt3, &dt1).unwrap());
1283+
1284+
// time on the same day but outside session time returns error
1285+
let dt4 = DateTime::from_naive_utc_and_offset(
1286+
NaiveDate::from_ymd_opt(2025, 7, 10)
1287+
.unwrap()
1288+
.and_hms_opt(19, 0, 0)
1289+
.unwrap(),
1290+
Utc,
1291+
);
1292+
assert!(schedule.is_same_session_period(&dt1, &dt4).is_err());
1293+
assert!(schedule.is_same_session_period(&dt4, &dt1).is_err());
1294+
1295+
// time falls on the Saturday (outside session period) returns error
1296+
let dt4 = DateTime::from_naive_utc_and_offset(
1297+
NaiveDate::from_ymd_opt(2025, 7, 12)
1298+
.unwrap()
1299+
.and_hms_opt(13, 0, 0)
1300+
.unwrap(),
1301+
Utc,
1302+
);
1303+
assert!(schedule.is_same_session_period(&dt1, &dt4).is_err());
1304+
assert!(schedule.is_same_session_period(&dt4, &dt1).is_err());
1305+
}
12301306
}

0 commit comments

Comments
 (0)