One job (job-4821) has a hard time window the truck must hit exactly. Another (job-4822) has a soft window with early_penalty/late_penalty, so the solver can arrive outside it if that is cheaper overall, paying a cost instead of failing.
Time Windows
Every TimeWindow has a start and end (in the same time units as the travel matrices, here seconds from midnight). By default those bounds are hard: arriving outside them makes the assignment infeasible for that vehicle. Setting early_penalty and/or late_penalty turns the corresponding bound soft: the solver may violate it, but pays the penalty per second of violation, and the resulting time_window_violation_s shows up on the activity.
Setup
One truck, one depot at index 0, two jobs:
- job-4821 (index 1) — hard window
08:00–08:30(28800–30600s). The truck must arrive in that window or the job cannot be assigned to this vehicle at all. - job-4822 (index 2) — soft window
09:00–09:15(32400–33300s) withlate_penalty: 50per second late. Travel from job-4821 to job-4822 takes 1400s, so if the truck leaves job-4821 right at the end of its window (30600 + 300s service = 30900) it arrives at 32300, comfortably inside the soft window. The penalty only bites if upstream delays push arrival past 33300.
Request
{
"metadata": { "problem_id": "time-windows-example" },
"travel": {
"distance_matrix": [
[0, 5200, 6500],
[5200, 0, 1400],
[6500, 1400, 0]
],
"time_matrix": [
[0, 480, 600],
[480, 0, 130],
[600, 130, 0]
]
},
"resources": [
{ "id": "truck-1", "depot_index": 0 }
],
"jobs": [
{
"id": "job-4821",
"location_index": 1,
"service_time": 300,
"time_windows": [
{ "start": 28800, "end": 30600 }
]
},
{
"id": "job-4822",
"location_index": 2,
"service_time": 300,
"time_windows": [
{ "start": 32400, "end": 33300, "late_penalty": 50 }
]
}
],
"options": { "time_limit_seconds": 5 }
}
Response
{
"status": "optimal",
"problem_id": "time-windows-example",
"solve_time_ms": 21,
"routes": [
{
"resource_id": "truck-1",
"day": null,
"activities": [
{ "type": "start", "location_index": 0, "departure_time": 28320 },
{
"type": "service",
"job_id": "job-4821",
"location_index": 1,
"arrival_time": 28800,
"departure_time": 29100,
"waiting_time": 0,
"service_time": 300,
"distance_from_prev": 5200,
"time_from_prev": 480,
"time_window_violation_s": 0
},
{
"type": "service",
"job_id": "job-4822",
"location_index": 2,
"arrival_time": 29230,
"departure_time": 32700,
"waiting_time": 3170,
"service_time": 300,
"distance_from_prev": 1400,
"time_from_prev": 130,
"time_window_violation_s": 0
},
{ "type": "end", "location_index": 0, "arrival_time": 33300, "distance_from_prev": 6500, "time_from_prev": 600 }
],
"summary": {
"total_distance": 13100,
"total_travel_time": 1210,
"total_service_time": 600,
"total_waiting_time": 3170,
"total_time": 4980,
"num_stops": 2,
"num_jobs": 2,
"cost": 13100.0
},
"violations": []
}
],
"unserved": [],
"score": {
"total": 13100.0,
"travel_distance": 13100.0,
"travel_time": 1210.0,
"fixed_vehicle_cost": 0.0,
"penalty_dropped": 0.0,
"fairness_cost": 0.0,
"num_vehicles_used": 1
},
"warnings": []
}
How the solver behaved differently
The truck departs the depot at exactly 28320 so it arrives at 28800, the earliest legal moment — it has no reason to arrive any later inside the window since there is no early or late penalty, and arriving before 28800 would violate the hard lower bound. If no departure time existed that lands inside [28800, 30600], this job would be infeasible for the vehicle and (since mandatory defaults to true) the whole request would fail to solve rather than dropping it.
The truck physically arrives at job-4822 at 29230 (right after finishing job-4821), well before the soft window opens at 32400. Rather than treating this as infeasible, the vehicle simply waits — waiting_time: 3170 — and starts service at 32700. Because the window's lower bound here has no early_penalty, waiting is free; only arriving after 33300 would cost 50 per second via late_penalty. time_window_violation_s is 0 on both stops because the truck never breaches either bound.
If job-4822's window were hard instead (no late_penalty) the solver's behavior here would look identical, since it never actually violates the bound — the difference only shows up when satisfying the window is physically impossible: a hard window makes the job unassignable to any vehicle that can't make it, while a soft window lets the solver blow through the deadline and pay late_penalty × seconds_late instead.
Unbounded waiting (like the 3170s above) is allowed by default. Set max_wait_before_service on the job, resource, or globally on problem to cap how long a vehicle may idle at a stop before its window opens — useful for keeping drivers from sitting around for hours waiting on an early-morning slot.
- Skills & Capacities — combine time windows with proficiency-based eligibility.
- OptimizationRequest schema — full
TimeWindowfield reference.