Time windows and opening hours

When a customer needs a technician to visit their home, the visit must be scheduled for a time when the customer will be home to let the technician in. For instance, the customer might only be available in the morning or the afternoon. Some visits have a time period in which the visit must occur, this is called a time window (and opening hours). Often the time window was decided when the customer booked the visit.

Prerequisite

To run the examples in this guide, you need to authenticate with a valid API key:

  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your username, and from the drop-down menu select API Keys.

  3. Copy the default API key.

In the examples, replace <your-api-key> with the API Key you just copied.

1. Time windows

For a customer visit that must occur between 13:00 and 17:00, a timeWindows object is specified with minStartTime and maxEndTime values:

Visits can have multiple time windows.
"visits": [
  {
    "id": "Visit A",
    "location": [33.77301, -84.43838],
    "serviceDuration": "PT1H30M",
    "timeWindows": [
        {
            "minStartTime": "2027-02-01T13:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
        }
    ]
  }
]

minStartTime and maxEndTime use the ISO 8601 date and time format with offset to UTC format.

Below is an example dataset with one vehicle and one visit with a time window:

time-windows-example-1.json
{
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H30M",
        "timeWindows": [
            {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
            }
        ]
      }
    ]
  }
}
Try this example in Timefold Platform by saving the JSON into a file called time-windows-example-1.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <your-api-key>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]

Timefold will assign the run for this dataset a unique ID and a run name.

To locate the run for this dataset in the Timefold UI, open the Dashboard, click Run Model in the Field Service Routing tile, and search for the run name.

To retrieve the solution with the API, append the id to the API endpoint /v1/route-plans/ and create a GET request to retrieve the solution:

curl -X GET -H 'X-API-KEY: <your-api-key>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans/<unique-id>

Below is the solution for this example:

time-windows-example-1-output.json
{
    "run": {
        "id": <unique-id>,
        "name": <unique-name>,
        "submitDateTime": "2024-07-15T04:39:34.792750944Z",
        "startDateTime": "2024-07-15T04:39:40.233674574Z",
        "completeDateTime": "2024-07-15T04:39:42.884169539Z",
        "solverStatus": "SOLVING_COMPLETED",
        "score": "0hard/0medium/-3569soft",
        "tags": null,
        "validationResult": {
            "summary": "OK"
        }
    },
    "modelOutput": {
        "vehicles": [
            {
                "id": "Carl",
                "shifts": [
                    {
                        "id": "Carl-2027-2-1",
                        "startTime": "2027-02-01T09:00:00Z",
                        "itinerary": [
                            {
                                "id": "Visit A",
                                "kind": "VISIT",
                                "arrivalTime": "2027-02-01T09:29:57Z",
                                "startServiceTime": "2027-02-01T13:00:00Z",
                                "departureTime": "2027-02-01T14:30:00Z",
                                "effectiveServiceDuration": "PT1H30M",
                                "travelTimeFromPreviousStandstill": "PT29M57S",
                                "travelDistanceMetersFromPreviousStandstill": 31492,
                                "minStartTravelTime": "2027-02-01T00:00:00Z"
                            }
                        ],
                        "metrics": {
                            "totalTravelTime": "PT59M29S",
                            "travelTimeFromStartLocationToFirstVisit": "PT29M57S",
                            "travelTimeBetweenVisits": "PT0S",
                            "travelTimeFromLastVisitToEndLocation": "PT29M32S",
                            "totalTravelDistanceMeters": 65476,
                            "travelDistanceFromStartLocationToFirstVisitMeters": 31492,
                            "travelDistanceBetweenVisitsMeters": 0,
                            "travelDistanceFromLastVisitToEndLocationMeters": 33984,
                            "endLocationArrivalTime": "2027-02-01T14:59:32Z"
                        }
                    }
                ]
            }
        ]
    },
    "kpis": {
        "totalTravelTime": "PT59M29S",
        "travelTimeFromStartLocationToFirstVisit": "PT29M57S",
        "travelTimeBetweenVisits": "PT0S",
        "travelTimeFromLastVisitToEndLocation": "PT29M32S",
        "totalTravelDistanceMeters": 65476,
        "travelDistanceFromStartLocationToFirstVisitMeters": 31492,
        "travelDistanceBetweenVisitsMeters": 0,
        "travelDistanceFromLastVisitToEndLocationMeters": 33984,
        "totalUnassignedVisits": 0
    }
}

In this example, Carl’s shift begins at 9:00, but the time window for the visit isn’t until 13:00. Carl’s travel to the visit is scheduled so that he arrives at 13:00. The visit duration lasts for 1 hour and 30 minutes "PT1H30M". This is the only visit on Carl’s itinerary today, so when Visit A is completed, he is scheduled to travel back to his startLocation.

visit time windows basic

2. Time windows and other visits

Carl had a quiet shift in our first example, but he usually has more than one visit to complete per shift.

When there are multiple visits in a shift, Timefold assigns visits in an order that conforms to the specified time windows.

Below is an example dataset with one vehicle, two visits without time windows, and one visit with a time window.

time-windows-example-2.json
{
  "modelInput": {
    "vehicles": [
      {
        "id": "Carl",
        "shifts": [
          {
            "id": "Carl-2027-2-1",
            "startLocation": [33.68786, -84.18487],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-01T17:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Visit A",
        "location": [33.77301, -84.43838],
        "serviceDuration": "PT1H30M",
        "timeWindows": [
            {
                "minStartTime": "2027-02-01T13:00:00Z",
                "maxEndTime": "2027-02-01T17:00:00Z"
            }
        ]
      },
      {
        "id": "Visit B",
        "location": [33.87673, -84.26024],
        "serviceDuration": "PT1H"
      },
      {
        "id": "Visit C",
        "location": [33.88664, -84.28118],
        "serviceDuration": "PT1H30M"
      }
    ]
  }
}
Try this example in Timefold Platform by saving the JSON into a file called time-windows-example-2.json and make the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <your-api-key>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]

Below is the solution for this example:

time-windows-example-2-output.json
{
    "run": {
        "id": <unique-id>,
        "name": <unique-name>,
        "submitDateTime": "2024-07-15T06:29:07.287034163Z",
        "startDateTime": "2024-07-15T06:29:13.075791707Z",
        "completeDateTime": "2024-07-15T06:33:11.696296779Z",
        "solverStatus": "SOLVING_COMPLETED",
        "score": "0hard/0medium/-5017soft",
        "tags": null,
        "validationResult": {
            "summary": "OK"
        }
    },
    "modelOutput": {
        "vehicles": [
            {
                "id": "Carl",
                "shifts": [
                    {
                        "id": "Carl-2027-2-1",
                        "startTime": "2027-02-01T09:00:00Z",
                        "itinerary": [
                            {
                                "id": "Visit B",
                                "kind": "VISIT",
                                "arrivalTime": "2027-02-01T09:26:11Z",
                                "startServiceTime": "2027-02-01T09:26:11Z",
                                "departureTime": "2027-02-01T10:26:11Z",
                                "effectiveServiceDuration": "PT1H",
                                "travelTimeFromPreviousStandstill": "PT26M11S",
                                "travelDistanceMetersFromPreviousStandstill": 29015,
                                "minStartTravelTime": "2027-02-01T00:00:00Z"
                            },
                            {
                                "id": "Visit C",
                                "kind": "VISIT",
                                "arrivalTime": "2027-02-01T10:30:36Z",
                                "startServiceTime": "2027-02-01T10:30:36Z",
                                "departureTime": "2027-02-01T12:00:36Z",
                                "effectiveServiceDuration": "PT1H30M",
                                "travelTimeFromPreviousStandstill": "PT4M25S",
                                "travelDistanceMetersFromPreviousStandstill": 2702,
                                "minStartTravelTime": "2027-02-01T00:00:00Z"
                            },
                            {
                                "id": "Visit A",
                                "kind": "VISIT",
                                "arrivalTime": "2027-02-01T12:24:05Z",
                                "startServiceTime": "2027-02-01T13:00:00Z",
                                "departureTime": "2027-02-01T14:30:00Z",
                                "effectiveServiceDuration": "PT1H30M",
                                "travelTimeFromPreviousStandstill": "PT23M29S",
                                "travelDistanceMetersFromPreviousStandstill": 24210,
                                "minStartTravelTime": "2027-02-01T00:00:00Z"
                            }
                        ],
                        "metrics": {
                            "totalTravelTime": "PT1H23M37S",
                            "travelTimeFromStartLocationToFirstVisit": "PT26M11S",
                            "travelTimeBetweenVisits": "PT27M54S",
                            "travelTimeFromLastVisitToEndLocation": "PT29M32S",
                            "totalTravelDistanceMeters": 89911,
                            "travelDistanceFromStartLocationToFirstVisitMeters": 29015,
                            "travelDistanceBetweenVisitsMeters": 26912,
                            "travelDistanceFromLastVisitToEndLocationMeters": 33984,
                            "endLocationArrivalTime": "2027-02-01T14:59:32Z"
                        }
                    }
                ]
            }
        ]
    },
    "kpis": {
        "totalTravelTime": "PT1H23M37S",
        "travelTimeFromStartLocationToFirstVisit": "PT26M11S",
        "travelTimeBetweenVisits": "PT27M54S",
        "travelTimeFromLastVisitToEndLocation": "PT29M32S",
        "totalTravelDistanceMeters": 89911,
        "travelDistanceFromStartLocationToFirstVisitMeters": 29015,
        "travelDistanceBetweenVisitsMeters": 26912,
        "travelDistanceFromLastVisitToEndLocationMeters": 33984,
        "totalUnassignedVisits": 0
    }
}

In the output, you can see Timefold assigns visit A as Carl’s 3rd visit that shift.

In the example below, Carl arrives at 14:00 to start work. 14:00 is after the minStartTime of 13:00, and the visit will be completed before the maxEndTime which is 17:00.

visit time windows

3. Too early (wait time)

With time windows it is sometimes necessary for a technician to arrive early and wait.

For example, if Carl arrives too early at 12:00, before the minStartTime of 13:00, he needs to wait for one hour to start work.

visit time windows too early

Despite the lost time spent waiting, this can be the most productive solution.

Time windows automatically incentivizes Timefold to fill up any waiting time with other visits. However, the Field Service Routing model attempts to minimize travel time and maximize productivity so wait times are kept to a minimum.

See constraints for more information.

visit time windows the wait time paradox

4. Too late

If Carl arrives too late, he can’t finish the job in time. In the example below, if he arrives at 12:30 for a job that ends at 13:00, he won’t have time to finish the job. This is an infeasible schedule.

visit time windows too late

Because the end time is earlier than Carl would finish, Timefold is incentivized to assign that visit earlier, or leave it unassigned.

Timefold uses constraints to determine whether a schedule is feasible or not. See constraints for more information.

5. Multiple time windows (opening hours)

A visit can have multiple time windows when it’s possible to schedule a visit.

For example, Visit A could occur on:

  • Monday 1-feb between 8:30 and 11:30

  • Monday 1-feb between 13:30 and 17:00

  • Tuesday 2-feb between 09:30 and 12:30

Timefold assigns that visit to a vehicle shift during one of those time windows, for instance, to Carl on his Monday shift:

visit multiple time windows

Multiple time windows are assigned to a single visit as follows:

"visits": [
{
  "id": "Visit A",
  "timeWindows": [
    {
      "minStartTime": "2027-02-01T08:30:00Z",
      "maxEndTime": "2027-02-01T11:30:00Z"
    },
    {
      "minStartTime": "2027-02-01T13:30:00Z",
      "maxEndTime": "2027-02-01T17:00:00Z"
    },
    {
      "minStartTime": "2027-02-02T09:30:00Z",
      "maxEndTime": "2027-02-02T12:30:00Z"
    }
  ],
}
The examples assume the visit time windows use the UTC time zone offset. Please see Time zones and daylight-saving time (DST) chapter for more details.