Skip to content

[GTFS-Fares v2] Add duration limit to fare_leg_join_rules.txt - #639

Open
skalexch wants to merge 6 commits into
google:masterfrom
MobilityData:add-duration-limit-to-leg-join-rules
Open

[GTFS-Fares v2] Add duration limit to fare_leg_join_rules.txt#639
skalexch wants to merge 6 commits into
google:masterfrom
MobilityData:add-duration-limit-to-leg-join-rules

Conversation

@skalexch

@skalexch skalexch commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

This proposal:

  • Adds duration_limit and duration_limit_type to join rules in fare_leg_join_rules.txt.
  • Suggests situations where it's possible to use either fare_leg_join_rules.txt or fare_transfer_rules.txt.
  • Forbids overlap between fare_leg_join_rules.txt and fare_transfer_rules.txt for joins based on network_id.

Describe the Problem

This was part of the distance-based fares proposal. We decided to separate it in its own proposal for a few reasons:

  1. The addition can actually help the fare implementation of many fare structures that are not distance-based (mentioned in Use Cases).
  2. The distance-based fares proposal is moving slower than is required by the need for adding duration parameters to fare_leg_join_rules.txt.

Use Cases

This is mainly to allow the usage of fare_leg_join_rules.txt as a simpler way to define transfers in fare structures such as:

Proposed Solution

Adding duration_limit and duration_limit_type to join rules in fare_leg_join_rules.txt and forbidding overlap between fare_leg_join_rules.txt and fare_transfer_rules.txt for joins based on network_id.

Type of change

GTFS Schedule

  • Functional Change
  • Non-Functional Change
  • Documentation Maintenance

GTFS Realtime

  • Specification Change
  • Specification Change (Experimental Field)

Additional Information

More research can be found in the research document.

Proposed Discussion Period

We propose a discussion period of at least 2 weeks before starting an official review period. We will start the review period later in June.

Testing Details

  • Consumer(s):
  • Producer(s):
  • Estimated Testing Period:

Proposal Update Tracker

Date Update Description
(2026-06-03) (Discussion period opened)

Checklist

@skalexch skalexch changed the title Add duration limit to leg join rules [GTFS-Fares v2] Add duration limit to fare_leg_join_rules.txt Jun 3, 2026
@skalexch skalexch added GTFS Schedule Issues and Pull Requests that focus on GTFS Schedule GTFS-Fares Issues and Pull Requests that focus on GTFS-Fares Extension Discussion Period The community engages in conversations to help refine and develop the proposal. Change type: Functional Refers to modifications that significantly affect specification functionalities. labels Jun 3, 2026
@etienne0101

Copy link
Copy Markdown
Collaborator

Hi @BodoMinea @jfabi, @felixguendling, @jasonad123, @halbertram @skinkie @bdferris-v2 @miklcct @jll01 @davidlewis-ito

Following previous Fares Working Group discussions in 2025, we have decoupled the duration limit fields from the distance-based fares proposal into a standalone Pull Request.

We believe this addition is broadly useful for various fare structures beyond distance-based ones, especially for simplifying transfer definitions.

We are planning to initiate a "vote-to-test" in mid-July.
Please take a look at the PR and let us know if you have any feedback or if you are interested in testing these new fields.

Thank you in advance for your contribution!

Comment thread gtfs/spec/en/reference.md
| `from_stop_id` | Foreign ID referencing `stops.stop_id`| **Conditionally Required** | Matches a pre-transfer leg that ends at the specified stop (`location_type=0` or empty) or station (`location_type=1`).<br><br>Conditionally Required:<br> - **Required** if `to_stop_id` is defined.<br> - Optional otherwise. |
| `to_stop_id` | Foreign ID referencing `stops.stop_id`| **Conditionally Required** | Matches a post-transfer leg that starts at the specified stop (`location_type=0` or empty) or station (`location_type=1`).<br><br>Conditionally Required:<br> - **Required** if `from_stop_id` is defined.<br> - Optional otherwise. |
| `duration_limit` | Positive integer | **Optional** | Defines the duration limit of the transfer between the legs that constitute the effective leg. <br><br>Must be expressed in integer increments of seconds.<br><br>If there is no duration limit, `fare_leg_join_rules.duration_limit` must be empty. |
| `duration_limit_type` | Enum | **Conditionally Required** | Defines the relative start and end of `fare_leg_join_rules.duration_limit`.<br><br>Valid options are:<br>`0` - Between the departure fare validation of the first leg in the effective leg and the arrival fare validation of the last leg in the effective leg.<br>`1` - Between the departure fare validation of the first leg in the effective leg and the departure fare validation of the last leg in the effective leg.<br>`2` - Between the arrival fare validation of the first leg in the effective leg and the departure fare validation of the last leg in the effective leg.<br>`3` - Between the arrival fare validation of the first leg in the effective leg and the arrival fare validation of the last leg in the effective leg.<br><br>When an effective leg with the same `from_network_id` and `to_network_id` is matched multiple times consecutively within a multi-leg journey, the `duration_limit` specified by the effective leg should be measured starting from the first matched leg.<br><br>Conditionally Required:<br>- **Required** if `fare_leg_join_rules.duration_limit` is defined.<br>- **Forbidden** if `fare_leg_join_rules.duration_limit` is empty. |

@felixguendling felixguendling Jun 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The router doesn't know when exactly the ticket will be validated (i.e. walking time between fare gate and departure/arrival of the vehicle vice versa).

So I would change departure fare validation to departure time (same for arrival).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sharing your feedback @felixguendling. In fact, the wording is copied verbatim from the definition of duration_limit_type in fare_transfer_rules.txt. Therefore, both need to be changed if need be.

However, this was talked about in a few 2023 Fare Working Group Meetings when timeframes were being discussed.

The consensus seemed to be that in a real-world situation, the timeframe (or fare product transfer duration since it's similar) starts when tapping on the bus (0 min of delay) or at a fare gate (a few minutes of delay based on the size of the station, how long the rider will linger or if they are taking the first train). So the time limit for a fare product starts counting down when the rider taps. Therefore, the wording "fare validation" accurately describes the mechanism in real life.

In terms of router behaviour, there are ways of approximating the duration of the journey's walking-legs to be able to compare directly with duration_limit, among others:

  • Using pathways if they exist.
  • Approximating in-station walking time using OSM.
  • Providing default in-station walking times.

This is left to trip planners to choose how they approximate it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is left to trip planners to choose how they approximate it.

A good standard doesn't leave room for interpretation.

So I would propose to:

  • either state that the timetable times (arrival / departure) can be used as they are. E.g. because the ticketing system itself leaves some room for walking by having more permissive limits or an average walking time is already subtracted from the duration limit
  • or it's clearly stated that in-station walking times have to be calculated using GTFS pathways from fare gates (probably pathway_mode=6?) to tracks (or vice versa for arrival) - which would require a Fares v2 feed to always come with proper GTFS pathways.

I think that adding a duration_limit without a clearly defined way to measure durations (e.g. based on GTFS pathways; OSM is out-of-scope here I guess) isn't helpful if you want to consume the data.

@skalexch

skalexch commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

@felixguendling I am planning to add this to the proposal both in the fare_transfer_rules.txt and fare_leg_join_rules.txt sections. We wanted to clarify that the duration_limit strictly adheres to the time limit on transfers defined by the agency. It is better to not introduce any approximations to the field itself. Therefore, pathways is the only way provided by the spec to calculate the station crossing time by trip planners for large stations.

Snippet to be added:

Calculation of duration_limit

As implied by duration_limit_type, duration_limit starts and ends at fare validation events (e.g. a tap at a fare gate or onboard validator), not at the moment a rider physically boards or alights a vehicle. The duration_limit does not account for the time it takes a rider to cross between a fare gate and a platform.

This means duration_limit should be set to the same theoretical transfer time limit that the agency publishes to riders (e.g., "transfers must be completed within 90 minutes"), rather than to a measured or estimated travel time.

If the feed contains pathways.txt, data consumers can estimate this crossing time using pathways.traversal_time, pathways.length or pathways.stair_count.

I'd like to hear your feedback on this!

@felixguendling

Copy link
Copy Markdown

This is way better. However, it's still not on a level that would guarantee that if route planner (or any other consumer like a ticketing system) A and B both follow this definition it's guaranteed that the same ticket options will be shown in all cases. IMO this should be the goal with such a definition.

@skalexch

Copy link
Copy Markdown
Collaborator Author

@felixguendling thanks for the feedback! I've updated the description and committed the change. Aside from keeping the same definition of duration_limit as the theoretical transfer validity time, I emphasized that consumers should use traversal_time when pathways exist. This guarantees that if producers and consumers follow this best practice, they'll arrive at the same result in the journey planner.

We didn't restrict consumers from using other fields when traversal_time isn't available, for two reasons: 1) we wanted to preserve that flexibility for consumers, and 2) traversal_time is not always populated unfortunately. In an ideal state, traversal_time would be used universally to estimate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Change type: Functional Refers to modifications that significantly affect specification functionalities. Discussion Period The community engages in conversations to help refine and develop the proposal. GTFS Schedule Issues and Pull Requests that focus on GTFS Schedule GTFS-Fares Issues and Pull Requests that focus on GTFS-Fares Extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants