+# frozen_string_literal: true
+
+require "date"
+require "pry"
+require "sqlite3"
+
+class Gtfs
+ def initialize
+ @calendar = nil
+ end
+
+ def active_schedules(date_hash: nil)
+ target_date = date_hash || today
+ base_schedules = calendar_schedules(target_date)
+ overrides = calendar_overrides(target_date)
+
+ base_schedules + overrides[:add] - overrides[:remove]
+ end
+
+ def trips_for_stop(stop_code:, date_hash: nil)
+ result = {}
+
+ schedules = active_schedules(date_hash: date_hash)
+ placeholders = (["?"] * schedules.count).join(",")
+ stop_id = db.query("SELECT stop_id FROM stops WHERE stop_code=?", stop_code).first.first
+
+ sql = <<~EOS
+ SELECT r.route_short_name,st.departure_time
+ FROM routes r
+ INNER JOIN trips t ON t.route_id=r.route_id
+ INNER JOIN stop_times st ON st.trip_id=t.trip_id
+ WHERE st.stop_id=?
+ AND service_id IN (#{placeholders})
+ ORDER BY r.route_short_name,st.departure_time;
+ EOS
+
+ db.query(sql, stop_id, schedules).each do |row|
+ stop_id = row[0]
+ departure_time = row[1]
+
+ result[stop_id] = [] if result[stop_id].nil?
+ result[stop_id] << departure_time
+ end
+
+ # Sometimes OC Transpo lists the same departure under two separate schedules, *both* of which are
+ # simulteneously in effect. (?)
+ # Here we compensate for that by eliminating duplicates of the same departure time.
+ result.map do |route, departure_times|
+ [route, departure_times.uniq]
+ end.to_h
+ end
+
+ private
+
+ def calendar
+ @calendar ||= begin
+ sql = <<~EOS
+ SELECT service_id, monday, tuesday, wednesday, thursday, friday, saturday, sunday, start_date, end_date
+ FROM calendar
+ ORDER BY start_date, end_date
+ EOS
+ db.query(sql).to_a
+ end
+ end
+
+ def calendar_overrides(target_date)
+ result = {add: [], remove: []}
+ datestamp = date_for(target_date)
+ sql = <<~EOS
+ SELECT service_id, exception_type
+ FROM calendar_dates
+ WHERE calendar_date = ?
+ EOS
+
+ db.query(sql).each do |row|
+ case row[1]
+ when "1"
+ result[:add] << row[0]
+ when "2"
+ result[:remove] << row[0]
+ else
+ raise "Unexpected exception_type #{row[1].inspect} found in calendar_dates"
+ end
+ end
+
+ result
+ end
+
+ def calendar_schedules(target_date)
+ result = []
+ datestamp = date_for(target_date)
+ weekday = weekday_for(target_date)
+ calendar.each do |service_id, mon, tue, wed, thu, fri, sat, sun, start_date, end_date|
+ if datestamp >= start_date && datestamp <= end_date
+ if (0 == weekday && "1" == sun) ||
+ (1 == weekday && "1" == mon) ||
+ (2 == weekday && "1" == tue) ||
+ (3 == weekday && "1" == wed) ||
+ (4 == weekday && "1" == thu) ||
+ (5 == weekday && "1" == fri) ||
+ (6 == weekday && "1" == sat)
+ result << service_id
+ end
+ end
+ end
+ result
+ end
+
+ def date_for(date_hash)
+ "#{date_hash[:year]}#{date_hash[:month].to_s.rjust(2, "0")}#{date_hash[:day].to_s.rjust(2, "0")}"
+ end
+
+ def db
+ @db ||= SQLite3::Database.new("gtfs.db")
+ end
+
+ def today
+ # Beware: This assumes your system is in America/Toronto time.
+ now = Date.today
+ {
+ year: now.year,
+ month: now.month,
+ day: now.day,
+ }
+ end
+
+ def weekday_for(date_hash)
+ Date.new(date_hash[:year], date_hash[:month], date_hash[:day]).wday
+ end
+end