From ff381878bc848cb5ccd751f463a314351258a052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 28 Jul 2026 10:55:28 -0400 Subject: [PATCH 1/4] Fix `calendar_dates` outside of `calendars` coverage issue. Some transit agencies #TTC publishes special days service in `calendar_dates` outside of the `calendars` coverage. The issue was that the app data contains a random day schedule in the future w/o the other days in the middle which will show as "no service" instead of displaying previous week(s) schedule. --- .../mtransit/parser/DefaultAgencyTools.java | 18 ++++------ .../org/mtransit/parser/gtfs/GReader.java | 33 ++++++++++++++----- .../org/mtransit/parser/gtfs/data/GSpecExt.kt | 13 ++++++++ 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/mtransit/parser/DefaultAgencyTools.java b/src/main/java/org/mtransit/parser/DefaultAgencyTools.java index 923dd699..acbb636a 100644 --- a/src/main/java/org/mtransit/parser/DefaultAgencyTools.java +++ b/src/main/java/org/mtransit/parser/DefaultAgencyTools.java @@ -129,12 +129,6 @@ public class DefaultAgencyTools implements GAgencyTools { OVERRIDE_DATE = null; // yyyyMMdd } - private static final boolean TOMORROW; - - static { - TOMORROW = false; - } - private static final DateFormat DATE_FORMAT = GFieldTypes.makeDateFormat(); @Nullable @@ -143,6 +137,11 @@ public class DefaultAgencyTools implements GAgencyTools { @SuppressWarnings("WeakerAccess") public int getTodayDateInt() { if (todayDateInt == null) { + //noinspection ConstantValue + if (OVERRIDE_DATE != null) { + todayDateInt = OVERRIDE_DATE; + return todayDateInt; + } todayDateInt = Integer.parseInt(DATE_FORMAT.format(Calendar.getInstance().getTime())); } return todayDateInt; @@ -1742,11 +1741,8 @@ public static HashSet extractUsefulServiceIdInts( boolean isCurrent = "current_".equalsIgnoreCase(args[2]); boolean isNext = "next_".equalsIgnoreCase(args[2]); boolean isCurrentOrNext = isCurrent || isNext; - Calendar c = Calendar.getInstance(); - if (!isCurrentOrNext && TOMORROW) { - c.add(Calendar.DAY_OF_MONTH, 1); // TOMORROW (too late to publish today's schedule) - } - usefulPeriod.setTodayStringInt(Integer.valueOf(DATE_FORMAT.format(c.getTime()))); + final Calendar c = Calendar.getInstance(); + usefulPeriod.setTodayStringInt(agencyTools.getTodayDateInt()); if (!isCurrentOrNext && OVERRIDE_DATE != null) { usefulPeriod.setTodayStringInt(OVERRIDE_DATE); } diff --git a/src/main/java/org/mtransit/parser/gtfs/GReader.java b/src/main/java/org/mtransit/parser/gtfs/GReader.java index c00953d6..a9f1d41c 100644 --- a/src/main/java/org/mtransit/parser/gtfs/GReader.java +++ b/src/main/java/org/mtransit/parser/gtfs/GReader.java @@ -1,6 +1,9 @@ package org.mtransit.parser.gtfs; import static org.mtransit.commons.Constants.EMPTY; +import static org.mtransit.parser.gtfs.data.GSpecExtKt.getCalendarsMaxStartDate; +import static org.mtransit.parser.gtfs.data.GSpecExtKt.getCalendarsMinStartDate; +import static org.mtransit.parser.gtfs.data.GSpecExtKt.isInsideGCalendars; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; @@ -90,14 +93,16 @@ public static GSpec readGtfsZipFile( processAgency(agencyTools, gSpec, line) ); } - // CALENDAR DATES (-> non-excluded service IDs) - boolean hasCalendarDates = readFile(gtfsDir, GCalendarDate.FILENAME, false, line -> - processCalendarDate(agencyTools, gSpec, line) - ); - // CALENDAR (-> non-excluded service IDs) + // CALENDAR (-> non-excluded service IDs) (before calendar dates) boolean hasCalendars = readFile(gtfsDir, GCalendar.FILENAME, false, line -> processCalendar(agencyTools, gSpec, line) ); + final @Nullable Integer calendarsMinStartDate = getCalendarsMinStartDate(gSpec); + final @Nullable Integer calendarsMaxStartDate = getCalendarsMaxStartDate(gSpec); + // CALENDAR DATES (-> non-excluded service IDs) (after calendar) + boolean hasCalendarDates = readFile(gtfsDir, GCalendarDate.FILENAME, false, line -> + processCalendarDate(agencyTools, gSpec, line, calendarsMinStartDate, calendarsMaxStartDate) + ); boolean hasCalendar = hasCalendarDates || hasCalendars; if (!hasCalendar) { throw new MTLog.Fatal("'%s' & '%s' file do not exist!", GCalendar.FILENAME, GCalendarDate.FILENAME); @@ -431,18 +436,28 @@ private static void processAgency(GAgencyTools agencyTools, GSpec gSpec, HashMap DateUtils.getEndOfYear(DateUtils.addYears(new Date(), 3)) // 3 years // else local DB slow to deploy )); - private static void processCalendarDate(GAgencyTools agencyTools, GSpec gSpec, HashMap line) { + private static void processCalendarDate( + GAgencyTools agencyTools, + GSpec gSpec, + HashMap line, + @Nullable Integer calendarsMinStartDate, + @Nullable Integer calendarsMaxStartDate + ) { try { final GCalendarDate gCalendarDate = GCalendarDate.fromLine(line); if (gCalendarDate == null) { - MTLog.log("Empty calendar dates ignored (%s).", line); + MTLog.log("Empty calendar date ignored (%s).", line); + return; + } + if (Boolean.FALSE.equals(isInsideGCalendars(gSpec, gCalendarDate, () -> calendarsMinStartDate, () -> calendarsMaxStartDate))) { + MTLog.logDebug("Out of calendar coverage calendar date ignored (%s).", line); return; } if (gCalendarDate.isBefore(MIN_CALENDAR_DATE)) { - MTLog.log("Too old calendar dates ignored (%s).", line); + MTLog.log("Too old calendar date ignored (%s).", line); return; } else if (gCalendarDate.isAfter(MAX_CALENDAR_DATE)) { - MTLog.log("Too much in the future calendar dates ignored (%s).", line); + MTLog.log("Too much in the future calendar date ignored (%s).", line); return; } if (agencyTools.excludeCalendarDate(gCalendarDate)) { diff --git a/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt b/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt index e4816a85..d52ec433 100644 --- a/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt +++ b/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt @@ -1,3 +1,16 @@ package org.mtransit.parser.gtfs.data fun GSpec.getRoute(gTrip: GTrip) = this.getRoute(gTrip.routeIdInt) + +val GSpec.calendarsMinStartDate: Int? get() = this.allCalendars.takeIf { it.isNotEmpty() }?.minByOrNull { it.startDate }?.startDate +val GSpec.calendarsMaxStartDate: Int? get() = this.allCalendars.takeIf { it.isNotEmpty() }?.maxByOrNull { it.endDate }?.endDate + +fun GSpec.isInsideGCalendars( + gCalendarDate: GCalendarDate, + calendarsMinStartDate: () -> Int? = this::calendarsMinStartDate, + calendarsMaxStartDate: () -> Int? = this::calendarsMaxStartDate +): Boolean? { + val calendarsMinStartDate = calendarsMinStartDate() ?: return null // no calendars (only calendar dates inside GTFS) + val calendarsMaxStartDate = calendarsMaxStartDate() ?: return null // no calendars (only calendar dates inside GTFS) + return gCalendarDate.isBetween(calendarsMinStartDate, calendarsMaxStartDate) +} From eee0cbcb4864fd93cbb35e59fd3d69405fade41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 28 Jul 2026 10:56:52 -0400 Subject: [PATCH 2/4] fix --- src/main/java/org/mtransit/parser/gtfs/GReader.java | 10 +++++----- .../java/org/mtransit/parser/gtfs/data/GSpecExt.kt | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mtransit/parser/gtfs/GReader.java b/src/main/java/org/mtransit/parser/gtfs/GReader.java index a9f1d41c..dbd4a093 100644 --- a/src/main/java/org/mtransit/parser/gtfs/GReader.java +++ b/src/main/java/org/mtransit/parser/gtfs/GReader.java @@ -1,7 +1,7 @@ package org.mtransit.parser.gtfs; import static org.mtransit.commons.Constants.EMPTY; -import static org.mtransit.parser.gtfs.data.GSpecExtKt.getCalendarsMaxStartDate; +import static org.mtransit.parser.gtfs.data.GSpecExtKt.getCalendarsMaxEndDate; import static org.mtransit.parser.gtfs.data.GSpecExtKt.getCalendarsMinStartDate; import static org.mtransit.parser.gtfs.data.GSpecExtKt.isInsideGCalendars; @@ -98,10 +98,10 @@ public static GSpec readGtfsZipFile( processCalendar(agencyTools, gSpec, line) ); final @Nullable Integer calendarsMinStartDate = getCalendarsMinStartDate(gSpec); - final @Nullable Integer calendarsMaxStartDate = getCalendarsMaxStartDate(gSpec); + final @Nullable Integer calendarsMaxEndDate = getCalendarsMaxEndDate(gSpec); // CALENDAR DATES (-> non-excluded service IDs) (after calendar) boolean hasCalendarDates = readFile(gtfsDir, GCalendarDate.FILENAME, false, line -> - processCalendarDate(agencyTools, gSpec, line, calendarsMinStartDate, calendarsMaxStartDate) + processCalendarDate(agencyTools, gSpec, line, calendarsMinStartDate, calendarsMaxEndDate) ); boolean hasCalendar = hasCalendarDates || hasCalendars; if (!hasCalendar) { @@ -441,7 +441,7 @@ private static void processCalendarDate( GSpec gSpec, HashMap line, @Nullable Integer calendarsMinStartDate, - @Nullable Integer calendarsMaxStartDate + @Nullable Integer calendarsMaxEndDate ) { try { final GCalendarDate gCalendarDate = GCalendarDate.fromLine(line); @@ -449,7 +449,7 @@ private static void processCalendarDate( MTLog.log("Empty calendar date ignored (%s).", line); return; } - if (Boolean.FALSE.equals(isInsideGCalendars(gSpec, gCalendarDate, () -> calendarsMinStartDate, () -> calendarsMaxStartDate))) { + if (Boolean.FALSE.equals(isInsideGCalendars(gSpec, gCalendarDate, () -> calendarsMinStartDate, () -> calendarsMaxEndDate))) { MTLog.logDebug("Out of calendar coverage calendar date ignored (%s).", line); return; } diff --git a/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt b/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt index d52ec433..7f8ad74a 100644 --- a/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt +++ b/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt @@ -3,14 +3,14 @@ package org.mtransit.parser.gtfs.data fun GSpec.getRoute(gTrip: GTrip) = this.getRoute(gTrip.routeIdInt) val GSpec.calendarsMinStartDate: Int? get() = this.allCalendars.takeIf { it.isNotEmpty() }?.minByOrNull { it.startDate }?.startDate -val GSpec.calendarsMaxStartDate: Int? get() = this.allCalendars.takeIf { it.isNotEmpty() }?.maxByOrNull { it.endDate }?.endDate +val GSpec.calendarsMaxEndDate: Int? get() = this.allCalendars.takeIf { it.isNotEmpty() }?.maxByOrNull { it.endDate }?.endDate fun GSpec.isInsideGCalendars( gCalendarDate: GCalendarDate, calendarsMinStartDate: () -> Int? = this::calendarsMinStartDate, - calendarsMaxStartDate: () -> Int? = this::calendarsMaxStartDate + calendarsMaxEndDate: () -> Int? = this::calendarsMaxEndDate ): Boolean? { val calendarsMinStartDate = calendarsMinStartDate() ?: return null // no calendars (only calendar dates inside GTFS) - val calendarsMaxStartDate = calendarsMaxStartDate() ?: return null // no calendars (only calendar dates inside GTFS) + val calendarsMaxStartDate = calendarsMaxEndDate() ?: return null // no calendars (only calendar dates inside GTFS) return gCalendarDate.isBetween(calendarsMinStartDate, calendarsMaxStartDate) } From 3a1f82962c28947e40ef6b2b49f48cbbe7600029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 28 Jul 2026 11:06:08 -0400 Subject: [PATCH 3/4] fix --- src/main/java/org/mtransit/parser/DefaultAgencyTools.java | 3 --- src/main/java/org/mtransit/parser/gtfs/GReader.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/org/mtransit/parser/DefaultAgencyTools.java b/src/main/java/org/mtransit/parser/DefaultAgencyTools.java index acbb636a..03c705e4 100644 --- a/src/main/java/org/mtransit/parser/DefaultAgencyTools.java +++ b/src/main/java/org/mtransit/parser/DefaultAgencyTools.java @@ -1743,9 +1743,6 @@ public static HashSet extractUsefulServiceIdInts( boolean isCurrentOrNext = isCurrent || isNext; final Calendar c = Calendar.getInstance(); usefulPeriod.setTodayStringInt(agencyTools.getTodayDateInt()); - if (!isCurrentOrNext && OVERRIDE_DATE != null) { - usefulPeriod.setTodayStringInt(OVERRIDE_DATE); - } GSpec gtfs = GReader.readGtfsZipFile(args[0], agencyTools, !agencyFilter, agencyFilter); MDataChangedManager.avoidCalendarDatesDataChanged(lastServiceDates, gtfs, agencyTools); if (agencyFilter) { diff --git a/src/main/java/org/mtransit/parser/gtfs/GReader.java b/src/main/java/org/mtransit/parser/gtfs/GReader.java index dbd4a093..13f47c1c 100644 --- a/src/main/java/org/mtransit/parser/gtfs/GReader.java +++ b/src/main/java/org/mtransit/parser/gtfs/GReader.java @@ -99,7 +99,7 @@ public static GSpec readGtfsZipFile( ); final @Nullable Integer calendarsMinStartDate = getCalendarsMinStartDate(gSpec); final @Nullable Integer calendarsMaxEndDate = getCalendarsMaxEndDate(gSpec); - // CALENDAR DATES (-> non-excluded service IDs) (after calendar) + // CALENDAR DATES (-> non-excluded service IDs) (after calendars) boolean hasCalendarDates = readFile(gtfsDir, GCalendarDate.FILENAME, false, line -> processCalendarDate(agencyTools, gSpec, line, calendarsMinStartDate, calendarsMaxEndDate) ); From 8e6f7fbfc611f25f69c0612152cce5eb432556a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 28 Jul 2026 11:20:09 -0400 Subject: [PATCH 4/4] cleanup --- src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt b/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt index 7f8ad74a..ba530d15 100644 --- a/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt +++ b/src/main/java/org/mtransit/parser/gtfs/data/GSpecExt.kt @@ -11,6 +11,6 @@ fun GSpec.isInsideGCalendars( calendarsMaxEndDate: () -> Int? = this::calendarsMaxEndDate ): Boolean? { val calendarsMinStartDate = calendarsMinStartDate() ?: return null // no calendars (only calendar dates inside GTFS) - val calendarsMaxStartDate = calendarsMaxEndDate() ?: return null // no calendars (only calendar dates inside GTFS) - return gCalendarDate.isBetween(calendarsMinStartDate, calendarsMaxStartDate) + val calendarsMaxEndDate = calendarsMaxEndDate() ?: return null // no calendars (only calendar dates inside GTFS) + return gCalendarDate.isBetween(calendarsMinStartDate, calendarsMaxEndDate) }