[GTFS Fares v2] Semantics clarification#561
Conversation
felixguendling
left a comment
There was a problem hiding this comment.
Thank you for addressing this and improving the standard! 👍
| To process the cost of a multi-leg journey: | ||
|
|
||
| 1. The applicable fare leg groups defined in `fare_leg_rules.txt` should be determined for all individual legs of travel based on the rider’s journey. | ||
| 1. The applicable fare leg groups defined in `fare_leg_rules.txt` should be determined for all individual legs or effective fare legs of travel based on the rider’s journey. |
There was a problem hiding this comment.
| 1. The applicable fare leg groups defined in `fare_leg_rules.txt` should be determined for all individual legs or effective fare legs of travel based on the rider’s journey. | |
| 1. The applicable fare leg groups defined in `fare_leg_rules.txt` should be determined for all effective fare legs of travel based on the rider’s journey. |
Depending on how an "effective fare leg" is defined, it could also be an individual leg. It's a list of 1 to N joined legs. So always just referring to "effective fare leg" (without mentioning "individual legs") would be fine and would allow for a shorter documentation (which usually equals to better readability IMO).
There was a problem hiding this comment.
Effective Fare Leg is currently defined as "A sub-journey of two or more legs" so indeed, this would need to change to support the semantics you've proposed. That said, I think I tend to lean towards the status-quo definition. I think there is value in distinguishing between an individual leg and an effective leg in the spec, as it usually more directly highlights in the text where you need to thing about how an individual leg vs effective leg impacts semantics.
There was a problem hiding this comment.
I will keep both individual leg and effect fare leg in the sentence at the moment.
gtfs/spec/en/reference.md
Outdated
| | `transfer_count` | Non-zero integer | **Conditionally Forbidden** | Defines how many consecutive transfers the transfer rule may be applied to.<br><br>Valid options are:<br>`-1` - No limit.<br>`1` or more - Defines how many transfers the transfer rule may span.<br><br>If a sub-journey matches multiple records with different `transfer_count`s, then the rule with the minimum `transfer_count` that is greater than or equal to the current transfer count of the sub-journey is to be selected.<br><br>Conditionally Forbidden:<br>- **Forbidden** if `fare_transfer_rules.from_leg_group_id` does not equal `fare_transfer_rules.to_leg_group_id`.<br>- **Required** if `fare_transfer_rules.from_leg_group_id` equals `fare_transfer_rules.to_leg_group_id`. | | ||
| | `duration_limit` | Positive integer | Optional | Defines the duration limit of the transfer.<br><br>Must be expressed in integer increments of seconds.<br><br>If there is no duration limit, `fare_transfer_rules.duration_limit` must be empty. | | ||
| | `duration_limit_type` | Enum | **Conditionally Required** | Defines the relative start and end of `fare_transfer_rules.duration_limit`.<br><br>Valid options are:<br>`0` - Between the departure fare validation of the current leg and the arrival fare validation of the next leg.<br>`1` - Between the departure fare validation of the current leg and the departure fare validation of the next leg.<br>`2` - Between the arrival fare validation of the current leg and the departure fare validation of the next leg.<br>`3` - Between the arrival fare validation of the current leg and the arrival fare validation of the next leg.<br><br>Conditionally Required:<br>- **Required** if `fare_transfer_rules.duration_limit` is defined.<br>- **Forbidden** if `fare_transfer_rules.duration_limit` is empty. | | ||
| | `duration_limit_type` | Enum | **Conditionally Required** | Defines the relative start and end of `fare_transfer_rules.duration_limit`.<br><br>Valid options are:<br>`0` - Between the departure fare validation of the current leg and the arrival fare validation of the next leg.<br>`1` - Between the departure fare validation of the current leg and the departure fare validation of the next leg.<br>`2` - Between the arrival fare validation of the current leg and the departure fare validation of the next leg.<br>`3` - Between the arrival fare validation of the current leg and the arrival fare validation of the next leg.<br><br>When a transfer rule with the same `from_leg_group_id` and `to_leg_group_id` is matched multiple times consecutively within a multi-leg journey, the `duration_limit` specified by the rule should be measured starting from the first matched leg.<br><br>Conditionally Required:<br>- **Required** if `fare_transfer_rules.duration_limit` is defined.<br>- **Forbidden** if `fare_transfer_rules.duration_limit` is empty. | |
There was a problem hiding this comment.
| | `duration_limit_type` | Enum | **Conditionally Required** | Defines the relative start and end of `fare_transfer_rules.duration_limit`.<br><br>Valid options are:<br>`0` - Between the departure fare validation of the current leg and the arrival fare validation of the next leg.<br>`1` - Between the departure fare validation of the current leg and the departure fare validation of the next leg.<br>`2` - Between the arrival fare validation of the current leg and the departure fare validation of the next leg.<br>`3` - Between the arrival fare validation of the current leg and the arrival fare validation of the next leg.<br><br>When a transfer rule with the same `from_leg_group_id` and `to_leg_group_id` is matched multiple times consecutively within a multi-leg journey, the `duration_limit` specified by the rule should be measured starting from the first matched leg.<br><br>Conditionally Required:<br>- **Required** if `fare_transfer_rules.duration_limit` is defined.<br>- **Forbidden** if `fare_transfer_rules.duration_limit` is empty. | | |
| | `duration_limit_type` | Enum | **Conditionally Required** | Defines the relative start and end of `fare_transfer_rules.duration_limit`.<br><br>Valid options are:<br>`0` - Between the departure fare validation of the first matched leg and the arrival fare validation of the last matched leg.<br>`1` - Between the departure fare validation of the first matched leg and the departure fare validation of the last matched leg.<br>`2` - Between the arrival fare validation of the first matched leg and the departure fare validation of the last matched leg.<br>`3` - Between the arrival fare validation of the first matched leg and the arrival fare validation of the last matched leg.<br><br>When a transfer rule with the same `from_leg_group_id` and `to_leg_group_id` is matched multiple times consecutively within a multi-leg journey, the `duration_limit` specified by the rule should be measured starting from the first matched leg.<br><br>Conditionally Required:<br>- **Required** if `fare_transfer_rules.duration_limit` is defined.<br>- **Forbidden** if `fare_transfer_rules.duration_limit` is empty. | |
I would eliminate all occurrences of "current leg" and replace them with "first matched leg" to make it clear from the beginning.
There was a problem hiding this comment.
I'd go further and replace both "current"+"next" with "first"+"last". "First matched leg" is ok but wondering if "first leg in transfer sub-journey" would clearer? (Not sure if I feel too strongly on that point)
There was a problem hiding this comment.
I agree. For the duration, it only makes sense to measure time from the first to the last leg of the fare transfer. "current" and "next" are a bit confusing here.
There was a problem hiding this comment.
Replaced "current"+"next" with "first"+"last" - 9ff1c18
| | `service_id` | Foreign ID referencing `calendar.service_id` or `calendar_dates.service_id` | **Required** | Identifies a set of dates that a timeframe is in effect. | | ||
|
|
||
| #### Timeframe Local Time Semantics | ||
| - When evaluating a fare event’s time against [timeframes.txt](#timeframestxt), the event time is computed in local time using the local timezone, as determined by the `stop_timezone`, if specified, of the stop or parent station for the fare event. If not specified, the feed’s agency timezone should be used instead. |
There was a problem hiding this comment.
This would require that the feed has only one agency or at least all agencies to have the same timezone to be non-ambiguous in case there's no stop timezone.
There was a problem hiding this comment.
The specification falls back to the agency time zone, so if there is no stop time zone, it should fallback to the actual agency used.
For example, if there are two agencies, A work on UTC+0, B work on UTC+1, and the current time is UTC 00:30 with a timeframe which start at 01:00 local time, my understanding is that it is in the timeframe if you use a service of B, but not a service of A.
There was a problem hiding this comment.
Per the spec for agency_timezone, if multiple agencies are specified in the dataset, each must have the same agency_timezone. So there should only ever be a single "feed"-level timezone either way.
|
I am initiating a vote for this clarification. Voting ends on 2025-09-15 at 23:59:59 UTC. |
|
+1 MOTIS |
|
+1 Transit |
|
+1 Google |
|
Thanks for participating in the vote! Since the third “+1” vote appears to have been cast a few hours after 2025-09-15 23:59:59 UTC. To avoid any potential governance-related disputes, I am reopening a vote here. Voting ends on 2025-10-06 at 23:59:59 UTC. @felixguendling @jll01 @bdferris-v2 could you please cast your vote again? Thanks🙏 |
|
+1 MOTIS |
|
+1 Transit |
|
+1 Google |
|
The vote passed on 2025-10-06 at 23:59:59 UTC. 3 votes in favour and no votes against. Votes came from: Thank you to everyone who participated! |
Based on recent discussions in the gtfs-fares Slack channel and working group, clarify some fares v2 related semantics including:
Local Timedata type to eliminate potential confusion caused by the existingTimedata type in timeframes.txt.Tagging active working group members for review🙏
@felixguendling @westontrillium @jfabi @skinkie @halbertram @evansiroky @miklcct @jll01 @bdferris-v2