Time Windows¶
Time windows constrain when a customer can be served. The vehicle must arrive within the allowed window or wait until it opens.
How It Works¶
- Each customer can have a
time_windowwithstartandendtimes - Each vehicle can have
shift_start(earliest departure) andshift_end(latest return) - If a vehicle arrives at customer before the window opens, it waits until
start - If a vehicle cannot arrive before
end, the constraint is violated
Example¶
from tqrouting import TQRouter, VRPInstance, Customer, VehicleType, Location, TimeWindow
router = TQRouter()
instance = VRPInstance(
customers=[
Customer(
location=Location(matrix_id=1),
delivery_load=10,
service_time="00:02:00",
time_window=TimeWindow(start="08:00:00", end="13:00:00"),
),
Customer(
location=Location(matrix_id=2),
delivery_load=8,
service_time="00:01:00",
time_window=TimeWindow(start="09:00:00", end="12:00:00"),
),
Customer(
location=Location(matrix_id=3),
delivery_load=5,
service_time="00:01:30",
time_window=TimeWindow(start="07:30:00", end="11:00:00"),
),
],
fleet=[
VehicleType(
start_location=Location(matrix_id=0),
end_location=Location(matrix_id=0),
shift_start="07:00:00",
shift_end="18:00:00",
capacity_load=30,
n_vehicles=3,
),
],
duration_matrix=[
[0, 600, 900, 720],
[600, 0, 480, 660],
[900, 480, 0, 360],
[720, 660, 360, 0],
],
)
solution = router.solve(instance)
for route in solution.routes:
print(f"Route {route.id} (waiting: {route.route_waiting_time}s):")
for visit in route.visits:
print(f" Customer {visit.customer_id}:")
print(f" Arrival: {visit.eta}")
print(f" Wait: {visit.waiting_time}")
print(f" Service: {visit.start_service_time} → {visit.end_service_time}")
Using Time Strings¶
Instead of seconds, you can use human-readable HH:MM:SS strings — for every time field of the instance, not just some of them:
Customer(
location=Location(matrix_id=1),
service_time="00:15:00", # 15 minutes
time_window=TimeWindow(start="08:00:00", end="12:00:00"), # 8 AM – 12 PM
)
VehicleType(
start_location=Location(matrix_id=0),
shift_start="07:00:00", # Vehicles leave at 7 AM
shift_end="18:00:00", # Must return by 6 PM
)
Understanding the Output¶
When time windows are present, the solution includes additional timing fields:
| Field | Description |
|---|---|
solution.total_waiting_time |
Total waiting time across all routes |
route.route_waiting_time |
Waiting time for a specific route |
visit.eta |
When the vehicle arrives at the customer |
visit.waiting_time |
Time the vehicle waits before service starts |
visit.start_service_time |
When service begins (≥ time window start) |
visit.end_service_time |
When service ends |
Timing flow at each customer
- Vehicle arrives →
eta - If arrived early, wait →
waiting_time = start_service_time - eta - Service begins →
start_service_time = max(eta, time_window.start) - Service ends →
end_service_time = start_service_time + service_time
Schedule Awareness¶
The solver also computes arrival windows and departure bounds for each route. These let you see how much flexibility exists in the schedule.
Departure Window¶
route = solution.routes[0]
print(f"Earliest departure: {route.route_earliest_departure}")
print(f"Latest departure: {route.route_latest_departure}")
Any departure time between these bounds keeps the entire route feasible.
Arrival Windows per Customer¶
for visit in route.visits:
print(f"Customer {visit.customer_id}:")
print(f" Arrival window: [{visit.earliest_arrival}, {visit.latest_arrival}]")
print(f" No-wait from: {visit.no_wait_arrival}")
earliest_arrival— earliest feasible arrival (respects time windows and service times of all preceding stops)latest_arrival— latest arrival that keeps downstream stops feasibleno_wait_arrival— earliest arrival that causes zero waiting time
Adjusting the Departure Time¶
By default, the solver picks the earliest departure that minimises total waiting time — it departs as early as possible without creating avoidable waiting. You can override this:
solution.set_departure_time(route_id=0, departure_time="07:30:00")
for visit in route.visits:
print(f" {visit.customer_id}: ETA={visit.eta}, wait={visit.waiting_time}")
All ETAs, waiting times, and duration metrics are recalculated for the specified route.
See Reading Results → Schedule Awareness for the full reference.
Vehicle Shift Window¶
shift_start and shift_end define the vehicle's shift:
- Vehicle cannot depart before
shift_start - Vehicle must return to its end location by
shift_end
VehicleType(
start_location=Location(matrix_id=0),
end_location=Location(matrix_id=0),
shift_start="08:00:00", # Shift starts at 8 AM
shift_end="17:00:00", # Must be back by 5 PM
n_vehicles=5,
)
Note
The vehicle shift window is also covered in Route Duration & Distance Limits. Both pages describe the same constraint for convenience.
Tuning¶
If time-window constraints make it hard to find feasible solutions:
To account for idle waiting between stops, set penalty_waiting (off by default) so the solver prices time spent waiting for a window to open:
If a strict window or shift end should be a preference rather than a hard rule, mark it soft — violations are then priced instead of making the solution infeasible:
solution = router.solve(
instance,
soft_constraints=["time_window", "shift_end"],
penalty_time_window=20.0,
penalty_shift_end=15.0,
)
See Soft Constraints.
Interactive Example
See the constraints notebook for a runnable VRPTW example.