diff --git a/editoast/src/client/stdcm_search_env_commands.rs b/editoast/src/client/stdcm_search_env_commands.rs index efc228fbd8b..5334612623a 100644 --- a/editoast/src/client/stdcm_search_env_commands.rs +++ b/editoast/src/client/stdcm_search_env_commands.rs @@ -8,8 +8,9 @@ use crate::CliError; use crate::Exists; use crate::Model; use crate::Retrieve; +use chrono::DateTime; use chrono::Duration; -use chrono::NaiveDateTime; +use chrono::Utc; use clap::Args; use clap::Subcommand; use editoast_models::DbConnection; @@ -62,10 +63,10 @@ pub struct SetSTDCMSearchEnvFromScenarioArgs { pub work_schedule_group_id: Option, /// If omitted, set to the earliest train start time in the timetable #[arg(long)] - pub search_window_begin: Option, + pub search_window_begin: Option>, /// If omitted, set to the latest train start time in the timetable plus one day #[arg(long)] - pub search_window_end: Option, + pub search_window_end: Option>, } async fn set_stdcm_search_env_from_scenario( @@ -126,10 +127,10 @@ pub struct SetSTDCMSearchEnvFromScratchArgs { #[arg(long)] /// If omitted, set to the earliest train start time in the timetable #[arg(long)] - pub search_window_begin: Option, + pub search_window_begin: Option>, /// If omitted, set to the latest train start time in the timetable plus one day #[arg(long)] - pub search_window_end: Option, + pub search_window_end: Option>, } async fn set_stdcm_search_env_from_scratch( @@ -178,10 +179,10 @@ async fn set_stdcm_search_env_from_scratch( async fn resolve_search_window( timetable_id: i64, - search_window_begin: Option, - search_window_end: Option, + search_window_begin: Option>, + search_window_end: Option>, conn: &mut DbConnection, -) -> Result<(NaiveDateTime, NaiveDateTime), Box> { +) -> Result<(DateTime, DateTime), Box> { let (begin, end) = if let (Some(begin), Some(end)) = (search_window_begin, search_window_end) { (begin, end) } else { @@ -193,8 +194,8 @@ async fn resolve_search_window( return Err(Box::new(CliError::new(1, error_msg))); }; - let begin = search_window_begin.unwrap_or(min.naive_utc()); - let end = search_window_end.unwrap_or(max.naive_utc() + Duration::days(1)); + let begin = search_window_begin.unwrap_or(*min); + let end = search_window_end.unwrap_or(*max + Duration::days(1)); (begin, end) }; @@ -235,22 +236,23 @@ mod tests { }; use super::*; - use chrono::NaiveDateTime; + use chrono::DateTime; + use chrono::Utc; use editoast_models::{DbConnection, DbConnectionPoolV2}; use rstest::rstest; - fn make_naive_datetime(s: &str) -> NaiveDateTime { - NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S").unwrap() + fn make_datetime(s: &str) -> DateTime { + DateTime::parse_from_rfc3339(s).unwrap().to_utc() } async fn create_train_schedules_from_start_times( - start_times: Vec, + start_times: Vec>, timetable_id: i64, conn: &mut DbConnection, ) { for start_time in start_times { simple_train_schedule_changeset(timetable_id) - .start_time(start_time.and_utc()) + .start_time(start_time) .create(conn) .await .expect("Should be able to create train schedules"); @@ -261,32 +263,32 @@ mod tests { #[case::both_none( None, None, - make_naive_datetime("2000-01-01 11:59:59"), - make_naive_datetime("2000-02-03 00:00:01") + make_datetime("2000-01-01 11:59:59Z"), + make_datetime("2000-02-03 00:00:01Z") )] #[case::begin_none( None, - Some(make_naive_datetime("2000-02-01 00:00:00")), - make_naive_datetime("2000-01-01 11:59:59"), - make_naive_datetime("2000-02-01 00:00:00") + Some(make_datetime("2000-02-01 00:00:00Z")), + make_datetime("2000-01-01 11:59:59Z"), + make_datetime("2000-02-01 00:00:00Z") )] #[case::end_none( - Some(make_naive_datetime("2000-02-01 08:00:00")), + Some(make_datetime("2000-02-01 08:00:00Z")), None, - make_naive_datetime("2000-02-01 08:00:00"), - make_naive_datetime("2000-02-03 00:00:01") + make_datetime("2000-02-01 08:00:00Z"), + make_datetime("2000-02-03 00:00:01Z") )] #[case::both_some( - Some(make_naive_datetime("2000-02-01 08:00:00")), - Some(make_naive_datetime("2000-05-22 09:00:50")), - make_naive_datetime("2000-02-01 08:00:00"), - make_naive_datetime("2000-05-22 09:00:50") + Some(make_datetime("2000-02-01 08:00:00Z")), + Some(make_datetime("2000-05-22 09:00:50Z")), + make_datetime("2000-02-01 08:00:00Z"), + make_datetime("2000-05-22 09:00:50Z") )] async fn test_resolve_search_window( - #[case] search_window_begin: Option, - #[case] search_window_end: Option, - #[case] expected_begin: NaiveDateTime, - #[case] expected_end: NaiveDateTime, + #[case] search_window_begin: Option>, + #[case] search_window_end: Option>, + #[case] expected_begin: DateTime, + #[case] expected_end: DateTime, ) { let db_pool = DbConnectionPoolV2::for_tests(); let conn = &mut db_pool.get_ok(); @@ -294,12 +296,12 @@ mod tests { let timetable = create_timetable(conn).await; let start_times = vec![ - make_naive_datetime("2000-01-01 12:00:00"), - make_naive_datetime("2000-02-02 00:00:00"), - make_naive_datetime("2000-01-01 11:59:59"), // earliest - make_naive_datetime("2000-01-15 08:59:59"), - make_naive_datetime("2000-02-02 00:00:01"), // latest - make_naive_datetime("2000-01-19 17:00:00"), + make_datetime("2000-01-01 12:00:00Z"), + make_datetime("2000-02-02 00:00:00Z"), + make_datetime("2000-01-01 11:59:59Z"), // earliest + make_datetime("2000-01-15 08:59:59Z"), + make_datetime("2000-02-02 00:00:01Z"), // latest + make_datetime("2000-01-19 17:00:00Z"), ]; create_train_schedules_from_start_times(start_times, timetable.id, conn).await; @@ -327,14 +329,14 @@ mod tests { #[rstest] #[case::both_some( - Some(make_naive_datetime("2000-02-01 08:00:00")), - Some(make_naive_datetime("2000-02-01 00:00:00")) + Some(make_datetime("2000-02-01 08:00:00Z")), + Some(make_datetime("2000-02-01 00:00:00Z")) )] - #[case::end_none(Some(make_naive_datetime("2000-03-01 00:00:00")), None)] - #[case::begin_none(None, Some(make_naive_datetime("2000-01-01 08:00:00")))] + #[case::end_none(Some(make_datetime("2000-03-01 00:00:00Z")), None)] + #[case::begin_none(None, Some(make_datetime("2000-01-01 08:00:00Z")))] async fn test_resolve_search_window_incompatible_dates( - #[case] search_window_begin: Option, - #[case] search_window_end: Option, + #[case] search_window_begin: Option>, + #[case] search_window_end: Option>, ) { let db_pool = DbConnectionPoolV2::for_tests(); let conn = &mut db_pool.get_ok(); @@ -342,8 +344,8 @@ mod tests { let timetable = create_timetable(conn).await; let start_times = vec![ - make_naive_datetime("2000-01-01 12:00:00"), - make_naive_datetime("2000-02-02 00:00:01"), + make_datetime("2000-01-01 12:00:00Z"), + make_datetime("2000-02-02 00:00:01Z"), ]; create_train_schedules_from_start_times(start_times, timetable.id, conn).await; @@ -366,8 +368,8 @@ mod tests { let work_schedule_group = create_work_schedule_group(conn).await; let start_times = vec![ - make_naive_datetime("2000-01-01 12:00:00"), - make_naive_datetime("2000-02-02 08:00:00"), + make_datetime("2000-01-01 12:00:00Z"), + make_datetime("2000-02-02 08:00:00Z"), ]; create_train_schedules_from_start_times( @@ -394,11 +396,11 @@ mod tests { assert_eq!( search_env.search_window_begin, - make_naive_datetime("2000-01-01 12:00:00") + make_datetime("2000-01-01 12:00:00Z") ); assert_eq!( search_env.search_window_end, - make_naive_datetime("2000-02-03 08:00:00") + make_datetime("2000-02-03 08:00:00Z") ); } @@ -413,8 +415,8 @@ mod tests { let electrical_profile_set = create_electrical_profile_set(conn).await; let start_times = vec![ - make_naive_datetime("2000-01-01 12:00:00"), - make_naive_datetime("2000-02-02 08:00:00"), + make_datetime("2000-01-01 12:00:00Z"), + make_datetime("2000-02-02 08:00:00Z"), ]; create_train_schedules_from_start_times(start_times, timetable.id, conn).await; @@ -438,11 +440,11 @@ mod tests { assert_eq!( search_env.search_window_begin, - make_naive_datetime("2000-01-01 12:00:00") + make_datetime("2000-01-01 12:00:00Z") ); assert_eq!( search_env.search_window_end, - make_naive_datetime("2000-02-03 08:00:00") + make_datetime("2000-02-03 08:00:00Z") ); } } diff --git a/editoast/src/models/stdcm_search_environment.rs b/editoast/src/models/stdcm_search_environment.rs index 7d837807e9f..1729d1e73ce 100644 --- a/editoast/src/models/stdcm_search_environment.rs +++ b/editoast/src/models/stdcm_search_environment.rs @@ -1,4 +1,5 @@ -use chrono::NaiveDateTime; +use chrono::DateTime; +use chrono::Utc; use diesel::ExpressionMethods; use diesel::QueryDsl; use diesel_async::RunQueryDsl; @@ -28,8 +29,8 @@ pub struct StdcmSearchEnvironment { #[serde(skip_serializing_if = "Option::is_none")] pub work_schedule_group_id: Option, pub timetable_id: i64, - pub search_window_begin: NaiveDateTime, - pub search_window_end: NaiveDateTime, + pub search_window_begin: DateTime, + pub search_window_end: DateTime, #[schema(nullable = false)] #[serde(skip_serializing_if = "Option::is_none")] pub temporary_speed_limit_group_id: Option, @@ -65,7 +66,8 @@ impl StdcmSearchEnvironmentChangeset { #[cfg(test)] pub mod tests { - use chrono::NaiveDate; + use chrono::TimeZone; + use chrono::Utc; use pretty_assertions::assert_eq; use rstest::rstest; @@ -127,11 +129,11 @@ pub mod tests { .work_schedule_group_id(Some(work_schedule_group.id)) .temporary_speed_limit_group_id(Some(temporary_speed_limit_group.id)) .timetable_id(timetable.id) - .search_window_begin(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap().into()) - .search_window_end(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap().into()); + .search_window_begin(Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap()) + .search_window_end(Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap()); - let begin = NaiveDate::from_ymd_opt(2024, 1, 16).unwrap().into(); - let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap().into(); + let begin = Utc.with_ymd_and_hms(2024, 1, 16, 0, 0, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2024, 1, 13, 0, 0, 0).unwrap(); let changeset_2 = changeset_1 .clone() @@ -190,16 +192,16 @@ pub mod tests { .work_schedule_group_id(Some(work_schedule_group.id)) .temporary_speed_limit_group_id(Some(temporary_speed_limit_group.id)) .timetable_id(timetable.id) - .search_window_begin(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap().into()) - .search_window_end(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap().into()); + .search_window_begin(Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap()) + .search_window_end(Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap()); let too_young = too_old .clone() - .search_window_begin(NaiveDate::from_ymd_opt(2024, 1, 16).unwrap().into()) - .search_window_end(NaiveDate::from_ymd_opt(2024, 1, 31).unwrap().into()); + .search_window_begin(Utc.with_ymd_and_hms(2024, 1, 16, 0, 0, 0).unwrap()) + .search_window_end(Utc.with_ymd_and_hms(2024, 1, 31, 0, 0, 0).unwrap()); - let begin = NaiveDate::from_ymd_opt(2024, 1, 7).unwrap().into(); - let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap().into(); + let begin = Utc.with_ymd_and_hms(2024, 1, 7, 0, 0, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2024, 1, 31, 0, 0, 0).unwrap(); let the_best = too_old .clone() diff --git a/editoast/src/views/stdcm_search_environment.rs b/editoast/src/views/stdcm_search_environment.rs index e4e35258167..dd2dba6e7f2 100644 --- a/editoast/src/views/stdcm_search_environment.rs +++ b/editoast/src/views/stdcm_search_environment.rs @@ -4,7 +4,8 @@ use axum::http::StatusCode; use axum::response::IntoResponse; use axum::response::Response; use axum::Extension; -use chrono::NaiveDateTime; +use chrono::DateTime; +use chrono::Utc; use editoast_authz::BuiltinRole; use editoast_models::DbConnectionPoolV2; use serde::de::Error as SerdeError; @@ -42,8 +43,8 @@ struct StdcmSearchEnvironmentCreateForm { work_schedule_group_id: Option, temporary_speed_limit_group_id: Option, timetable_id: i64, - search_window_begin: NaiveDateTime, // TODO: move to DateTime - search_window_end: NaiveDateTime, + search_window_begin: DateTime, + search_window_end: DateTime, } impl<'de> Deserialize<'de> for StdcmSearchEnvironmentCreateForm { @@ -59,8 +60,8 @@ impl<'de> Deserialize<'de> for StdcmSearchEnvironmentCreateForm { work_schedule_group_id: Option, temporary_speed_limit_group_id: Option, timetable_id: i64, - search_window_begin: NaiveDateTime, - search_window_end: NaiveDateTime, + search_window_begin: DateTime, + search_window_end: DateTime, } let internal = Internal::deserialize(deserializer)?; @@ -156,7 +157,8 @@ async fn retrieve_latest( #[cfg(test)] pub mod tests { use axum::http::StatusCode; - use chrono::NaiveDate; + use chrono::TimeZone; + use chrono::Utc; use pretty_assertions::assert_eq; use rstest::rstest; @@ -185,8 +187,8 @@ pub mod tests { work_schedule_group_id: Some(work_schedule_group.id), temporary_speed_limit_group_id: Some(temporary_speed_limit_group.id), timetable_id: timetable.id, - search_window_begin: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap().into(), - search_window_end: NaiveDate::from_ymd_opt(2024, 1, 15).unwrap().into(), + search_window_begin: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(), + search_window_end: Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap(), }; let request = app.post("/stdcm/search_environment").json(&form); @@ -224,8 +226,8 @@ pub mod tests { electrical_profile_set, ) = stdcm_search_env_fixtures(&mut pool.get_ok()).await; - let begin = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap().into(); - let end = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap().into(); + let begin = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(); + let end = Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap(); let _ = StdcmSearchEnvironment::changeset() .infra_id(infra.id) diff --git a/front/src/applications/stdcm/hooks/useStdcmEnv.tsx b/front/src/applications/stdcm/hooks/useStdcmEnv.tsx index 82f2a435eb6..01495c98cad 100644 --- a/front/src/applications/stdcm/hooks/useStdcmEnv.tsx +++ b/front/src/applications/stdcm/hooks/useStdcmEnv.tsx @@ -31,7 +31,7 @@ export default function useStdcmEnvironment() { workScheduleGroupId: data.work_schedule_group_id, temporarySpeedLimitGroupId: data.temporary_speed_limit_group_id, searchDatetimeWindow: { - begin: new Date(`${data.search_window_begin}Z`), // TODO: Remove this temporary fix when editoast returns the date in UTC + begin: new Date(data.search_window_begin), end: new Date(data.search_window_end), }, }) diff --git a/front/tests/utils/api-setup.ts b/front/tests/utils/api-setup.ts index 56bc1b41d6a..7bc923eee05 100644 --- a/front/tests/utils/api-setup.ts +++ b/front/tests/utils/api-setup.ts @@ -256,7 +256,7 @@ export async function getStdcmEnvironment(): Promise { // Remove the `id` field to match the StdcmSearchEnvironmentCreateForm schema - const { id, ...stdcmEnvironmentWithoutId } = stdcmEnvironment; + const { id: _id, ...stdcmEnvironmentWithoutId } = stdcmEnvironment; await postApiRequest( '/api/stdcm/search_environment', stdcmEnvironmentWithoutId, diff --git a/front/tests/utils/setup-utils.ts b/front/tests/utils/setup-utils.ts index 691087a235f..de6bbc3af9d 100644 --- a/front/tests/utils/setup-utils.ts +++ b/front/tests/utils/setup-utils.ts @@ -177,8 +177,8 @@ export async function createDataForTests(): Promise { const stdcmEnvironment = { infra_id: smallInfra.id, - search_window_begin: '2024-10-17T00:00:01', - search_window_end: '2024-10-18T23:59:59', + search_window_begin: '2024-10-17T00:00:00Z', + search_window_end: '2024-10-18T23:59:59Z', timetable_id: scenarioTrainSchedule.timetable_id, } as StdcmSearchEnvironment;