Fix schedules around midnight

Around midnight the app didn't work well because it didn't retrieve the
correct schedules. Also, the way the frontend was architectured, trains
which were scheduled after midnight appeared for 1 second and then
disappeared.

This CL fixes this by changing the following:

- The API now returns arrival and departure times by specifying a UNIX
  timestamp instead of the seconds since midnight. This helps simplify
  the code in the frontend.
- Adapting the frontend to consume the new timestamps, to fix the
  disappearing schedule bug.
- Fixing the SQL query to get the stop times, since it didn't retrieve
  the correct rows.

Fixed: misc:17

Change-Id: I98f1fe20d44163e716d4f57a2018635be74526d0
diff --git a/inc/gtfs.php b/inc/gtfs.php
index ae33d50..c774ad3 100644
--- a/inc/gtfs.php
+++ b/inc/gtfs.php
@@ -25,13 +25,18 @@
     return date("Ymd");
   }
 
-  static function time2seconds($time) {
+  // Converts a time in the format "HH:MM:SS" to the number of seconds. If
+  // |uniqueRepresentative| is true, when HH >= 24, a representative between 0
+  // and 24*60*60 - 1 of the equivalence class mod 24*60*60 will be returned
+  // instead of returning a number >= 24*60*60.
+  static function time2seconds($time, $uniqueRepresentative=true) {
     $timeSinceMidnight = self::timeSinceMidnight();
 
     $boom = explode(":", $time);
     if (count($boom) != 3) return null;
 
-    return ((($boom[0]*60) + $boom[1])*60 + $boom[2]) % (24*60*60);
+    $seconds = (($boom[0]*60) + $boom[1])*60 + $boom[2];
+    return $uniqueRepresentative ? $seconds % (24*60*60) : $seconds;
   }
 
   private function fetchAll($sql) {
@@ -104,9 +109,11 @@
     $stopParameters = array_keys($values);
 
     $rdow = (int)(new DateTime("now"))->format("w");
+    $dow0 = self::$dow[($rdow - 1) % 7]; // Yesterday's day of week
     $dow = self::$dow[$rdow]; // Today's day of week
     $dow2 = self::$dow[($rdow + 1) % 7]; // Tomorrow's day of week
 
+    $values[":yesterday"] = (int)(new DateTime("yesterday"))->format("Ymd"); // Yesterday's date
     $values[":today"] = (int)(new DateTime("now"))->format("Ymd"); // Today's date
     $values[":tomorrow"] = (int)(new DateTime("tomorrow"))->format("Ymd"); // Tomorrow's date
     $values[":now"] = (new DateTime("now"))->format("H:i:s");
@@ -138,7 +145,7 @@
             ) OR
             (
               time(:now) >= time('00:00:00', '-".(int)$timeLimit." minutes') AND
-              st.departure_time BETWEEN time(:now) AND strftime('24:%M:%S', :now, '".(int)$timeLimit." minutes')
+              st.departure_time BETWEEN time(:now) AND ((strftime('%H', :now, '".(int)$timeLimit." minutes') + 24) || strftime(':%M:%S', :now, '".(int)$timeLimit." minutes'))
             )
           ) AND
           (
@@ -162,12 +169,28 @@
             ) OR
             (
               time(:now) >= time('00:00:00', '-".(int)$timeLimit." minutes') AND
-              st.departure_time BETWEEN time(:now) AND strftime('24:%M:%S', :now, '".(int)$timeLimit." minutes')
+              st.departure_time BETWEEN ((strftime('%H', :now) + 24) || strftime(':%M:%S', :now)) AND ((strftime('%H', :now, '".(int)$timeLimit." minutes') + 48) || strftime(':%M:%S', :now, '".(int)$timeLimit." minutes'))
             )
           ) AND
           (
             c.service_id IS NULL OR
             (
+              c.start_date <= :yesterday AND
+              c.end_date >= :yesterday AND
+              c.$dow0 = ".(int)Gtfs\Calendar\CalendarDay::AVAILABLE."
+            )
+          ) AND
+          (
+            cd.service_id IS NULL OR
+            cd.date = :yesterday
+          )
+        ) OR
+        (
+          time(:now) >= time('00:00:00', '-".(int)$timeLimit." minutes') AND
+          st.departure_time BETWEEN time('00:00:00') AND time(:now, '".(int)$timeLimit." minutes') AND
+          (
+            c.service_id IS NULL OR
+            (
               c.start_date <= :tomorrow AND
               c.end_date >= :tomorrow AND
               c.$dow2 = ".(int)Gtfs\Calendar\CalendarDay::AVAILABLE."