@@ -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) ]
277285mod 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