@@ -80,6 +80,7 @@ import {
8080 secondsAfterMidnight ,
8181 fromGTFSTime ,
8282 calendarCodeToCalendar ,
83+ calendarToDateList ,
8384} from './time-utils.js' ;
8485import { formatTripNameForCSV } from './template-functions.js' ;
8586
@@ -1022,14 +1023,14 @@ const getCalendarsFromTimetable = (timetable: Timetable) => {
10221023} ;
10231024
10241025/*
1025- * Get all calendar date service ids for an agency between two dates.
1026+ * Get all calendar dates for an agency between two dates.
10261027 */
1027- const getCalendarDatesServiceIds = (
1028+ const getCalendarDatesForDateRange = (
10281029 startDate ?: number | null ,
10291030 endDate ?: number | null ,
10301031) => {
10311032 const db = openDb ( ) ;
1032- const whereClauses = [ 'exception_type = 1' ] ;
1033+ const whereClauses = [ ] ;
10331034
10341035 if ( endDate ) {
10351036 whereClauses . push ( `date <= ${ sqlString . escape ( endDate ) } ` ) ;
@@ -1041,12 +1042,12 @@ const getCalendarDatesServiceIds = (
10411042
10421043 const calendarDates : CalendarDate [ ] = db
10431044 . prepare (
1044- `SELECT DISTINCT service_id FROM calendar_dates WHERE ${ whereClauses . join (
1045+ `SELECT service_id, date, exception_type FROM calendar_dates WHERE ${ whereClauses . join (
10451046 ' AND ' ,
10461047 ) } `,
10471048 )
10481049 . all ( ) ;
1049- return calendarDates . map ( ( calendarDate ) => calendarDate . service_id ) ;
1050+ return calendarDates ;
10501051} ;
10511052
10521053/*
@@ -1398,14 +1399,73 @@ const formatTimetables = (timetables: Timetable[], config: Config) => {
13981399 timetable . warnings = [ ] ;
13991400 const dayList = formatDays ( timetable , config ) ;
14001401 const calendars = getCalendarsFromTimetable ( timetable ) ;
1401- let serviceIds = calendars . map ( ( calendar : Calendar ) => calendar . service_id ) ;
1402+ const serviceIds = new Set ( ) ;
1403+
1404+ // Add all service IDs from calendars
1405+ for ( const calendar of calendars ) {
1406+ serviceIds . add ( calendar . service_id ) ;
1407+ }
14021408
14031409 if ( timetable . include_exceptions === 1 ) {
1404- const calendarDatesServiceIds = getCalendarDatesServiceIds (
1410+ const calendarDates = getCalendarDatesForDateRange (
14051411 timetable . start_date ,
14061412 timetable . end_date ,
14071413 ) ;
1408- serviceIds = uniq ( [ ...serviceIds , ...calendarDatesServiceIds ] ) ;
1414+
1415+ const calendarDateGroups = groupBy ( calendarDates , 'service_id' ) ;
1416+
1417+ for ( const [ serviceId , calendarDateGroup ] of Object . entries (
1418+ calendarDateGroups ,
1419+ ) ) {
1420+ // Check if there is a corresponding calendar.txt entry for this service_id
1421+ const calendar = calendars . find (
1422+ ( c : Calendar ) => c . service_id === serviceId ,
1423+ ) ;
1424+
1425+ // Add service_id if any dates are added
1426+ if (
1427+ calendarDateGroup . some (
1428+ ( calendarDate ) => calendarDate . exception_type === 1 ,
1429+ )
1430+ ) {
1431+ serviceIds . add ( serviceId ) ;
1432+ }
1433+
1434+ const calendarDateGroupExceptionType2 = calendarDateGroup . filter (
1435+ ( calendarDate ) => calendarDate . exception_type === 2 ,
1436+ ) ;
1437+
1438+ // Check if ALL dates are excluded
1439+ if (
1440+ timetable . start_date &&
1441+ timetable . end_date &&
1442+ calendar &&
1443+ calendarDateGroupExceptionType2 . length > 0
1444+ ) {
1445+ const datesDuringDateRange = calendarToDateList (
1446+ calendar ,
1447+ timetable . start_date ,
1448+ timetable . end_date ,
1449+ ) ;
1450+
1451+ // If no dates are are within the date range, remove service_id
1452+ if ( datesDuringDateRange . length === 0 ) {
1453+ serviceIds . delete ( serviceId ) ;
1454+ }
1455+
1456+ // Check if every date is excluded and remove service_id if so
1457+ const everyDateIsExcluded = datesDuringDateRange . every (
1458+ ( dateDuringDateRange ) =>
1459+ calendarDateGroupExceptionType2 . some (
1460+ ( calendarDate ) => calendarDate . date === dateDuringDateRange ,
1461+ ) ,
1462+ ) ;
1463+
1464+ if ( everyDateIsExcluded ) {
1465+ serviceIds . delete ( serviceId ) ;
1466+ }
1467+ }
1468+ }
14091469 }
14101470
14111471 Object . assign ( timetable , {
@@ -1424,7 +1484,7 @@ const formatTimetables = (timetables: Timetable[], config: Config) => {
14241484 noPickupSymbol : config . noPickupSymbol ,
14251485 interpolatedStopSymbol : config . interpolatedStopSymbol ,
14261486 orientation : timetable . orientation || config . defaultOrientation ,
1427- service_ids : serviceIds ,
1487+ service_ids : Array . from ( serviceIds ) ,
14281488 dayList,
14291489 dayListLong : formatDaysLong ( dayList , config ) ,
14301490 } ) ;
0 commit comments