Skip to content

Commit

Permalink
editoast: use DateTime with timezone in work schedule API
Browse files Browse the repository at this point in the history
The timezone was stored in the database, but was stripped at the
HTTP API boundary.

This is a breaking change: the timezone is required when submitting
a work schedule, and is always returned when projecting a work
schedule.

Signed-off-by: Simon Ser <[email protected]>
  • Loading branch information
emersion committed Nov 15, 2024
1 parent 0d6e9ad commit 42e785e
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 40 deletions.
2 changes: 1 addition & 1 deletion editoast/src/models/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ pub async fn create_small_infra(conn: &mut DbConnection) -> Infra {
pub async fn create_work_schedule_group(conn: &mut DbConnection) -> WorkScheduleGroup {
WorkScheduleGroup::changeset()
.name("Empty work schedule group".to_string())
.creation_date(Utc::now().naive_utc())
.creation_date(Utc::now())
.create(conn)
.await
.expect("Failed to create empty work schedule group")
Expand Down
9 changes: 5 additions & 4 deletions editoast/src/models/work_schedules.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use chrono::NaiveDateTime;
use chrono::DateTime;
use chrono::Utc;
use editoast_derive::Model;
use editoast_schemas::infra::TrackRange;
use strum::FromRepr;
Expand All @@ -12,7 +13,7 @@ use utoipa::ToSchema;
#[model(gen(ops = crd, batch_ops = c, list))]
pub struct WorkScheduleGroup {
pub id: i64,
pub creation_date: NaiveDateTime,
pub creation_date: DateTime<Utc>,
pub name: String,
}

Expand All @@ -29,8 +30,8 @@ pub enum WorkScheduleType {
#[model(gen(batch_ops = c, list))]
pub struct WorkSchedule {
pub id: i64,
pub start_date_time: NaiveDateTime,
pub end_date_time: NaiveDateTime,
pub start_date_time: DateTime<Utc>,
pub end_date_time: DateTime<Utc>,
#[model(json)]
pub track_ranges: Vec<TrackRange>,
pub obj_id: String,
Expand Down
26 changes: 13 additions & 13 deletions editoast/src/views/timetable/stdcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use axum::extract::Path;
use axum::extract::Query;
use axum::extract::State;
use axum::Extension;
use chrono::Utc;
use chrono::{DateTime, Duration, NaiveDateTime, TimeZone};
use chrono::{DateTime, Duration, Utc};
use editoast_authz::BuiltinRole;
use editoast_derive::EditoastError;
use editoast_models::DbConnection;
Expand Down Expand Up @@ -786,8 +785,8 @@ async fn build_temporary_speed_limits(
Ok(applicable_speed_limits)
}

fn elapsed_since_time_ms(time: &NaiveDateTime, zero: &DateTime<Utc>) -> u64 {
max(0, (Utc.from_utc_datetime(time) - zero).num_milliseconds()) as u64
fn elapsed_since_time_ms(time: &DateTime<Utc>, zero: &DateTime<Utc>) -> u64 {
max(0, (*time - zero).num_milliseconds()) as u64
}

/// Create steps from track_map and waypoints
Expand Down Expand Up @@ -1200,17 +1199,17 @@ mod tests {

#[rstest]
// A day before the 'start_time' -> FILTERED OUT
#[case("2024-03-13 06:00:00", "2024-03-13 12:00:00", true)]
#[case("2024-03-13 06:00:00Z", "2024-03-13 12:00:00Z", true)]
// Finishing just after the 'start_time' -> KEPT
#[case("2024-03-14 06:00:00", "2024-03-14 08:01:00", false)]
#[case("2024-03-14 06:00:00Z", "2024-03-14 08:01:00Z", false)]
// Starting after the 'latest_simulation_end' -> FILTERED OUT
#[case("2024-03-14 10:01:00", "2024-03-14 12:00:00", true)]
#[case("2024-03-14 10:01:00Z", "2024-03-14 12:00:00Z", true)]
// Starting before the 'latest_simulation_end' -> KEPT
#[case("2024-03-14 09:59:00", "2024-03-14 12:00:00", false)]
#[case("2024-03-14 09:59:00Z", "2024-03-14 12:00:00Z", false)]
// Starting before the 'start_time' and finishing after 'latest_simulation_end' -> KEPT
#[case("2024-03-14 06:00:00", "2024-03-14 12:00:00", false)]
#[case("2024-03-14 06:00:00Z", "2024-03-14 12:00:00Z", false)]
// Starting after the 'start_time' and finishing before 'latest_simulation_end' -> KEPT
#[case("2024-03-14 08:30:00", "2024-03-14 09:30:00", false)]
#[case("2024-03-14 08:30:00Z", "2024-03-14 09:30:00Z", false)]
fn filter_stdcm_work_schedules_with_window(
#[case] ws_start_time: &str,
#[case] ws_end_time: &str,
Expand All @@ -1219,9 +1218,10 @@ mod tests {
// GIVEN
let work_schedules = [WorkSchedule {
id: rand::random::<i64>(),
start_date_time: NaiveDateTime::parse_from_str(ws_start_time, "%Y-%m-%d %H:%M:%S")
.unwrap(),
end_date_time: NaiveDateTime::parse_from_str(ws_end_time, "%Y-%m-%d %H:%M:%S").unwrap(),
start_date_time: DateTime::parse_from_rfc3339(ws_start_time)
.unwrap()
.to_utc(),
end_date_time: DateTime::parse_from_rfc3339(ws_end_time).unwrap().to_utc(),
..Default::default()
}];
let start_time = DateTime::parse_from_rfc3339("2024-03-14T08:00:00Z")
Expand Down
36 changes: 19 additions & 17 deletions editoast/src/views/work_schedules.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use axum::extract::Json;
use axum::extract::State;
use axum::Extension;
use chrono::NaiveDateTime;
use chrono::DateTime;
use chrono::Utc;
use derivative::Derivative;
use editoast_authz::BuiltinRole;
Expand Down Expand Up @@ -64,8 +64,8 @@ pub fn map_diesel_error(e: InternalError, name: impl AsRef<str>) -> InternalErro

#[derive(Serialize, Derivative, ToSchema)]
struct WorkScheduleItemForm {
pub start_date_time: NaiveDateTime,
pub end_date_time: NaiveDateTime,
pub start_date_time: DateTime<Utc>,
pub end_date_time: DateTime<Utc>,
pub track_ranges: Vec<TrackRange>,
pub obj_id: String,
#[schema(inline)]
Expand All @@ -80,8 +80,8 @@ impl<'de> Deserialize<'de> for WorkScheduleItemForm {
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Internal {
start_date_time: NaiveDateTime,
end_date_time: NaiveDateTime,
start_date_time: DateTime<Utc>,
end_date_time: DateTime<Utc>,
track_ranges: Vec<TrackRange>,
obj_id: String,
work_schedule_type: WorkScheduleType,
Expand Down Expand Up @@ -163,7 +163,7 @@ async fn create(
// Create the work_schedule_group
let work_schedule_group = WorkScheduleGroup::changeset()
.name(work_schedule_group_name.clone())
.creation_date(Utc::now().naive_utc())
.creation_date(Utc::now())
.create(conn)
.await;
let work_schedule_group =
Expand Down Expand Up @@ -197,9 +197,9 @@ struct WorkScheduleProjection {
/// The type of the work schedule.
pub work_schedule_type: WorkScheduleType,
/// The date and time when the work schedule takes effect.
pub start_date_time: NaiveDateTime,
pub start_date_time: DateTime<Utc>,
/// The date and time when the work schedule ends.
pub end_date_time: NaiveDateTime,
pub end_date_time: DateTime<Utc>,
/// a list of intervals `(a, b)` that represent the projections of the work schedule track ranges:
/// - `a` is the distance from the beginning of the path to the beginning of the track range
/// - `b` is the distance from the beginning of the path to the end of the track range
Expand Down Expand Up @@ -297,8 +297,8 @@ pub mod tests {
let request = app.post("/work_schedules").json(&json!({
"work_schedule_group_name": "work schedule group name",
"work_schedules": [{
"start_date_time": "2024-01-01T08:00:00",
"end_date_time": "2024-01-01T09:00:00",
"start_date_time": "2024-01-01T08:00:00Z",
"end_date_time": "2024-01-01T09:00:00Z",
"track_ranges": [],
"obj_id": "work_schedule_obj_id",
"work_schedule_type": "CATENARY"
Expand Down Expand Up @@ -328,8 +328,8 @@ pub mod tests {
let request = app.post("/work_schedules").json(&json!({
"work_schedule_group_name": "work schedule group name",
"work_schedules": [{
"start_date_time": "2024-01-01T08:00:00",
"end_date_time": "2024-01-01T07:00:00",
"start_date_time": "2024-01-01T08:00:00Z",
"end_date_time": "2024-01-01T07:00:00Z",
"track_ranges": [],
"obj_id": "work_schedule_obj_id",
"work_schedule_type": "CATENARY"
Expand All @@ -348,16 +348,16 @@ pub mod tests {

WorkScheduleGroup::changeset()
.name("duplicated work schedule group name".to_string())
.creation_date(Utc::now().naive_utc())
.creation_date(Utc::now())
.create(&mut pool.get_ok())
.await
.expect("Failed to create work schedule group");

let request = app.post("/work_schedules").json(&json!({
"work_schedule_group_name": "duplicated work schedule group name",
"work_schedules": [{
"start_date_time": "2024-01-01T08:00:00",
"end_date_time": "2024-01-01T09:00:00",
"start_date_time": "2024-01-01T08:00:00Z",
"end_date_time": "2024-01-01T09:00:00Z",
"track_ranges": [],
"obj_id": "work_schedule_obj_id",
"work_schedule_type": "CATENARY"
Expand Down Expand Up @@ -437,12 +437,14 @@ pub mod tests {
NaiveDate::from_ymd_opt(2024, 1, (index + 1).try_into().unwrap())
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
.unwrap()
.and_utc();
let end_date_time =
NaiveDate::from_ymd_opt(2024, 1, (index + 2).try_into().unwrap())
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
.unwrap()
.and_utc();
WorkSchedule::changeset()
.start_date_time(start_date_time)
.end_date_time(end_date_time)
Expand Down
10 changes: 5 additions & 5 deletions tests/tests/test_stdcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def test_between_trains(small_scenario: Scenario, fast_rolling_stock: int):

def test_work_schedules(small_scenario: Scenario, fast_rolling_stock: int):
requests.post(EDITOAST_URL + f"infra/{small_scenario.infra}/load")
start_time = datetime.datetime(2024, 1, 1, 14, 0, 0)
start_time = datetime.datetime(2024, 1, 1, 14, 0, 0, tzinfo=datetime.timezone.utc)
end_time = start_time + datetime.timedelta(days=4)
# TODO: we cannot delete work schedules for now, so let's give a unique name
# to avoid collisions
Expand Down Expand Up @@ -217,10 +217,10 @@ def test_max_running_time(small_scenario: Scenario, fast_rolling_stock: int):
[ ] < departure time window
"""
requests.post(EDITOAST_URL + f"infra/{small_scenario.infra}/load")
origin_start_time = datetime.datetime(2024, 1, 1, 8, 0, 0)
origin_end_time = datetime.datetime(2025, 1, 1, 8, 0, 0)
destination_start_time = datetime.datetime(2023, 1, 1, 8, 0, 0)
destination_end_time = datetime.datetime(2024, 1, 1, 16, 0, 0)
origin_start_time = datetime.datetime(2024, 1, 1, 8, 0, 0, tzinfo=datetime.timezone.utc)
origin_end_time = datetime.datetime(2025, 1, 1, 8, 0, 0, tzinfo=datetime.timezone.utc)
destination_start_time = datetime.datetime(2023, 1, 1, 8, 0, 0, tzinfo=datetime.timezone.utc)
destination_end_time = datetime.datetime(2024, 1, 1, 16, 0, 0, tzinfo=datetime.timezone.utc)
# TODO: we cannot delete work schedules for now, so let's give a unique name
# to avoid collisions
now = datetime.datetime.now()
Expand Down

0 comments on commit 42e785e

Please sign in to comment.