src/main/java/com/conveyal/gtfs/loader/JdbcGtfsSnapshotter.java
private TableLoadResult createScheduleExceptionsTable() {
        // check to see if the schedule_exceptions table exists
        boolean scheduleExceptionsTableExists = tableExists(feedIdToSnapshot, "schedule_exceptions");
        String scheduleExceptionsTableName = tablePrefix + "schedule_exceptions";

        if (scheduleExceptionsTableExists) {
            // schedule_exceptions table already exists in namespace being copied from.  Therefore, we simply copy it.
            return copy(Table.SCHEDULE_EXCEPTIONS, true);
        } else {
            // schedule_exceptions does not exist.  Therefore, we generate schedule_exceptions from the calendar_dates.
            TableLoadResult tableLoadResult = new TableLoadResult();
            try {
                Table.SCHEDULE_EXCEPTIONS.createSqlTable(
                    connection,
                    tablePrefix.replace(".", ""),
                    true
                );
                String sql = String.format(
                    "insert into %s (name, dates, exemplar, added_service, removed_service) values (?, ?, ?, ?, ?)",
                    scheduleExceptionsTableName
                );
                PreparedStatement scheduleExceptionsStatement = connection.prepareStatement(sql);
                final BatchTracker scheduleExceptionsTracker = new BatchTracker(
                    "schedule_exceptions",
                    scheduleExceptionsStatement
                );

                JDBCTableReader<CalendarDate> calendarDatesReader = new JDBCTableReader(
                    Table.CALENDAR_DATES,
                    dataSource,
                    feedIdToSnapshot + ".",
                    EntityPopulator.CALENDAR_DATE
                );
                Iterable<CalendarDate> calendarDates = calendarDatesReader.getAll();

                // Keep track of calendars by service id in case we need to add dummy calendar entries.
                Map<String, Calendar> calendarsByServiceId = new HashMap<>();

                // Iterate through calendar dates to build up to get maps from exceptions to their dates.
                Multimap<String, String> removedServiceForDate = HashMultimap.create();
                Multimap<String, String> addedServiceForDate = HashMultimap.create();
                for (CalendarDate calendarDate : calendarDates) {
                    // Skip any null dates
                    if (calendarDate.date == null) {
                        LOG.warn("Encountered calendar date record with null value for date field. Skipping.");
                        continue;
                    }
                    String date = calendarDate.date.format(DateTimeFormatter.BASIC_ISO_DATE);
                    if (calendarDate.exception_type == 1) {
                        addedServiceForDate.put(date, calendarDate.service_id);
                        // create (if needed) and extend range of dummy calendar that would need to be created if we are
                        // copying from a feed that doesn't have the calendar.txt file
                        Calendar calendar = calendarsByServiceId.getOrDefault(calendarDate.service_id, new Calendar());
                        calendar.service_id = calendarDate.service_id;
                        if (calendar.start_date == null || calendar.start_date.isAfter(calendarDate.date)) {
                            calendar.start_date = calendarDate.date;
                        }
                        if (calendar.end_date == null || calendar.end_date.isBefore(calendarDate.date)) {
                            calendar.end_date = calendarDate.date;
                        }
                        calendarsByServiceId.put(calendarDate.service_id, calendar);
                    } else {
                        removedServiceForDate.put(date, calendarDate.service_id);
                    }
                }
                // Iterate through dates with added or removed service and add to database.
                // For usability and simplicity of code, don't attempt to find all dates with similar
                // added and removed services, but simply create an entry for each found date.
                for (String date : Sets.union(removedServiceForDate.keySet(), addedServiceForDate.keySet())) {
                    scheduleExceptionsStatement.setString(1, date);
                    String[] dates = {date};
                    scheduleExceptionsStatement.setArray(2, connection.createArrayOf("text", dates));
                    scheduleExceptionsStatement.setInt(3, 9); // FIXME use better static type
                    scheduleExceptionsStatement.setArray(
                        4,
                        connection.createArrayOf("text", addedServiceForDate.get(date).toArray())
                    );
                    scheduleExceptionsStatement.setArray(
                        5,
                        connection.createArrayOf("text", removedServiceForDate.get(date).toArray())
                    );
                    scheduleExceptionsTracker.addBatch();
                }
                scheduleExceptionsTracker.executeRemaining();

                // determine if we appear to be working with a calendar_dates-only feed.
                // If so, we must also add dummy entries to the calendar table
                if (
                    feedIdToSnapshot != null &&
                    !tableExists(feedIdToSnapshot, "calendar") &&
                    calendarDatesReader.getRowCount() > 0
                ) {
                    sql = String.format(
                        "insert into %s (service_id, description, start_date, end_date, " +
                            "monday, tuesday, wednesday, thursday, friday, saturday, sunday)" +
                            "values (?, ?, ?, ?, 0, 0, 0, 0, 0, 0, 0)",
                        tablePrefix + "calendar"
                    );
                    PreparedStatement calendarStatement = connection.prepareStatement(sql);
                    final BatchTracker calendarsTracker = new BatchTracker(
                        "calendar",
                        calendarStatement
                    );
                    for (Calendar calendar : calendarsByServiceId.values()) {
                        calendarStatement.setString(1, calendar.service_id);
                        calendarStatement.setString(
                            2,
                            String.format("%s (auto-generated)", calendar.service_id)
                        );
                        calendarStatement.setString(
                            3,
                            calendar.start_date.format(DateTimeFormatter.BASIC_ISO_DATE)
                        );
                        calendarStatement.setString(
                            4,
                            calendar.end_date.format(DateTimeFormatter.BASIC_ISO_DATE)
                        );
                        calendarsTracker.addBatch();
                    }
                    calendarsTracker.executeRemaining();
                }

                connection.commit();
            } catch (Exception e) {
                tableLoadResult.fatalException = e.toString();
                LOG.error("Error creating schedule Exceptions: ", e);
                e.printStackTrace();
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            LOG.info("done creating schedule exceptions");
            return tableLoadResult;
        }
    }
private TableLoadResult createScheduleExceptionsTable() {
        // check to see if the schedule_exceptions table exists
        boolean scheduleExceptionsTableExists = tableExists(feedIdToSnapshot, "schedule_exceptions");
        String scheduleExceptionsTableName = tablePrefix + "schedule_exceptions";

        if (scheduleExceptionsTableExists) {
            // schedule_exceptions table already exists in namespace being copied from.  Therefore, we simply copy it.
            return copy(Table.SCHEDULE_EXCEPTIONS, true);
        } else {
            // schedule_exceptions does not exist.  Therefore, we generate schedule_exceptions from the calendar_dates.
            TableLoadResult tableLoadResult = new TableLoadResult();
            try {
                Table.SCHEDULE_EXCEPTIONS.createSqlTable(
                    connection,
                    tablePrefix.replace(".", ""),
                    true
                );
                String sql = String.format(
                    "insert into %s (name, dates, exemplar, added_service, removed_service) values (?, ?, ?, ?, ?)",
                    scheduleExceptionsTableName
                );
                PreparedStatement scheduleExceptionsStatement = connection.prepareStatement(sql);
                final BatchTracker scheduleExceptionsTracker = new BatchTracker(
                    "schedule_exceptions",
                    scheduleExceptionsStatement
                );

                JDBCTableReader<CalendarDate> calendarDatesReader = new JDBCTableReader(
                    Table.CALENDAR_DATES,
                    dataSource,
                    feedIdToSnapshot + ".",
                    EntityPopulator.CALENDAR_DATE
                );
                Iterable<CalendarDate> calendarDates = calendarDatesReader.getAll();

                // Keep track of calendars by service id in case we need to add dummy calendar entries.
                Map<String, Calendar> calendarsByServiceId = new HashMap<>();

                // Iterate through calendar dates to build up to get maps from exceptions to their dates.
                Multimap<String, String> removedServiceForDate = HashMultimap.create();
                Multimap<String, String> addedServiceForDate = HashMultimap.create();
                for (CalendarDate calendarDate : calendarDates) {
                    // Skip any null dates
                    if (calendarDate.date == null) {
                        LOG.warn("Encountered calendar date record with null value for date field. Skipping.");
                        continue;
                    }
                    String date = calendarDate.date.format(DateTimeFormatter.BASIC_ISO_DATE);
                    if (calendarDate.exception_type == 1) {
                        addedServiceForDate.put(date, calendarDate.service_id);
                        // create (if needed) and extend range of dummy calendar that would need to be created if we are
                        // copying from a feed that doesn't have the calendar.txt file
                        Calendar calendar = calendarsByServiceId.getOrDefault(calendarDate.service_id, new Calendar());
                        calendar.service_id = calendarDate.service_id;
                        if (calendar.start_date == null || calendar.start_date.isAfter(calendarDate.date)) {
                            calendar.start_date = calendarDate.date;
                        }
                        if (calendar.end_date == null || calendar.end_date.isBefore(calendarDate.date)) {
                            calendar.end_date = calendarDate.date;
                        }
                        calendarsByServiceId.put(calendarDate.service_id, calendar);
                    } else {
                        removedServiceForDate.put(date, calendarDate.service_id);
                    }
                }
                // Iterate through dates with added or removed service and add to database.
                // For usability and simplicity of code, don't attempt to find all dates with similar
                // added and removed services, but simply create an entry for each found date.
                for (String date : Sets.union(removedServiceForDate.keySet(), addedServiceForDate.keySet())) {
                    scheduleExceptionsStatement.setString(1, date);
                    String[] dates = {date};
                    scheduleExceptionsStatement.setArray(2, connection.createArrayOf("text", dates));
                    scheduleExceptionsStatement.setInt(3, 9); // FIXME use better static type
                    scheduleExceptionsStatement.setArray(
                        4,
                        connection.createArrayOf("text", addedServiceForDate.get(date).toArray())
                    );
                    scheduleExceptionsStatement.setArray(
                        5,
                        connection.createArrayOf("text", removedServiceForDate.get(date).toArray())
                    );
                    scheduleExceptionsTracker.addBatch();
                }
                scheduleExceptionsTracker.executeRemaining();

                // determine if we appear to be working with a calendar_dates-only feed.
                // If so, we must also add dummy entries to the calendar table
                if (
                    feedIdToSnapshot != null &&
                    !tableExists(feedIdToSnapshot, "calendar") &&
                    calendarDatesReader.getRowCount() > 0
                ) {
                    sql = String.format(
                        "insert into %s (service_id, description, start_date, end_date, " +
                            "monday, tuesday, wednesday, thursday, friday, saturday, sunday)" +
                            "values (?, ?, ?, ?, 0, 0, 0, 0, 0, 0, 0)",
                        tablePrefix + "calendar"
                    );
                    PreparedStatement calendarStatement = connection.prepareStatement(sql);
                    final BatchTracker calendarsTracker = new BatchTracker(
                        "calendar",
                        calendarStatement
                    );
                    for (Calendar calendar : calendarsByServiceId.values()) {
                        calendarStatement.setString(1, calendar.service_id);
                        calendarStatement.setString(
                            2,
                            String.format("%s (auto-generated)", calendar.service_id)
                        );
                        calendarStatement.setString(
                            3,
                            calendar.start_date.format(DateTimeFormatter.BASIC_ISO_DATE)
                        );
                        calendarStatement.setString(
                            4,
                            calendar.end_date.format(DateTimeFormatter.BASIC_ISO_DATE)
                        );
                        calendarsTracker.addBatch();
                    }
                    calendarsTracker.executeRemaining();
                }

                connection.commit();
            } catch (Exception e) {
                tableLoadResult.fatalException = e.toString();
                LOG.error("Error creating schedule Exceptions: ", e);
                e.printStackTrace();
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            LOG.info("done creating schedule exceptions");
            return tableLoadResult;
        }
    }
Special logic is needed for creating the schedule_exceptions table.

gtfs-lib has some more advanced data types in addition to those available in the calendar_dates.txt file of the
GTFS specification.  The schedule_exceptions table is the source of truth for exceptions to regular schedules.
When exporting to a GTFS, the calendar_dates table is completely ignored if the schedule_exceptions table exists.

When creating a snapshot, if the schedule_exceptions table doesn't currently exist, it is assumed that the feed
being copied has just been imported and additional data to explain schedule_exceptions has not been generated yet.
If the schedule_exceptions does already exist, that table is simply copied over.

If the calendar table does not exist in the feed being copied from, it might have been the case that the
imported feed did not have a calendar.txt file. If that was the case, then the schedule exceptions need to be
generated from the calendar_dates table and also the calendar table needs to be populated with dummy entries so
that it is possible to export the data to a GTFS because of current logic in JdbcGtfsExporter.  The way
JdbcGtfsExporter will only export calendar_dates that have a corresponding entry in the calendar table and have a
service span that applies to a schedule_exception.  Furthermore, the dummy calendar entries are currently needed
for the downstream library datatools-server/datatools-ui to work properly.