every train that runs that day $base = rt_plan($tt, $from, $to, 0, ['date' => $date, 'horizon' => 1440]); foreach ($base['direct'] as $l) $journeys[] = [$l]; // connecting options sampled across the day, de-duplicated $seen = []; foreach ([0, 360, 720, 1080] as $qd) { $r = ($qd === 0) ? $base : rt_plan($tt, $from, $to, $qd, ['date' => $date, 'horizon' => 1440]); foreach ($r['hops'] as $journey) { $sig = ''; foreach ($journey as $l) $sig .= $l['train'] . ':' . $l['board'] . '>'; if (isset($seen[$sig])) continue; $seen[$sig] = 1; $journeys[] = $journey; } } } catch (Throwable $ex) { error_log('router: ' . $ex->getMessage()); $err = 'The journey planner is warming up. Please try again in a moment.'; } } } // ---- Hide trains that have already left the origin (only for today's date, IST) ---- $departedAll = false; if ($journeys) { $nowIst = new DateTime('now', new DateTimeZone('Asia/Kolkata')); if ($date === $nowIst->format('Y-m-d')) { $nowMin = ((int) $nowIst->format('G')) * 60 + (int) $nowIst->format('i'); $before = count($journeys); $journeys = array_values(array_filter($journeys, function ($j) use ($nowMin) { return isset($j[0]['board']) && (int) $j[0]['board'] >= $nowMin; })); $departedAll = ($before > 0 && count($journeys) === 0); } } // ---- analytics: log the search (only when an actual A->B search ran) ---- if ($from !== '' && $to !== '') { $__an = APP_DIR . '/analytics.php'; if (is_file($__an)) { require_once $__an; rr_track('search', ['from_code' => $from, 'to_code' => $to, 'jdate' => $date, 'n' => count($journeys), 'note' => $departedAll ? 'departed' : '']); } } // ---- Names + types ---- $trainNos = []; $codes = [$from, $to]; foreach ($journeys as $j) foreach ($j as $l) { $trainNos[$l['train']] = 1; $codes[] = $l['from']; $codes[] = $l['to']; } $trainNames = []; $trainTypes = []; $stationNames = []; $trainClasses = []; if ($trainNos) { $in = implode(',', array_fill(0, count($trainNos), '?')); $hasClassesCol = true; try { $st = db()->prepare("SELECT train_no, train_name, train_type, classes FROM trains WHERE train_no IN ($in)"); $st->execute(array_keys($trainNos)); } catch (Throwable $e) { $hasClassesCol = false; $st = db()->prepare("SELECT train_no, train_name, train_type FROM trains WHERE train_no IN ($in)"); $st->execute(array_keys($trainNos)); } foreach ($st as $r) { $trainNames[$r['train_no']] = $r['train_name']; $trainTypes[$r['train_no']] = $r['train_type']; if ($hasClassesCol && isset($r['classes']) && $r['classes'] !== '' && $r['classes'] !== '-') { $vals = array_values(array_filter(array_map('trim', explode(',', (string) $r['classes'])))); if ($vals) $trainClasses[$r['train_no']] = ['valid' => $vals, 'invalid' => []]; } } try { $tcq = db()->prepare("SELECT train_no, class, valid FROM train_classes WHERE train_no IN ($in)"); $tcq->execute(array_keys($trainNos)); foreach ($tcq as $r) { $tn = $r['train_no']; if (!isset($trainClasses[$tn])) $trainClasses[$tn] = ['valid' => [], 'invalid' => []]; if ((int) $r['valid'] === 1) $trainClasses[$tn]['valid'][] = $r['class']; else $trainClasses[$tn]['invalid'][] = $r['class']; } } catch (Throwable $e) { /* learning table not created yet */ } } $codes = array_values(array_unique(array_filter($codes))); if ($codes) { $in = implode(',', array_fill(0, count($codes), '?')); $st = db()->prepare("SELECT code, name FROM stations WHERE code IN ($in)"); $st->execute($codes); foreach ($st as $r) $stationNames[$r['code']] = $r['name']; } // (class lists now come from trains.classes, populated by tools/backfill_classes.php — no per-search API call) function sname(string $c): string { global $stationNames; return $stationNames[$c] ?? $c; } function tname(string $t): string { global $trainNames; return $trainNames[$t] ?? ''; } function ttype(string $t): ?string { global $trainTypes; return $trainTypes[$t] ?? null; } function train_class(string $no, ?string $t): array { $u = strtoupper(trim((string) $t)); $isMEMU = strpos($u, 'ELECTRICAL MULTIPLE') !== false || strpos($u, 'MEMU') !== false; $isDEMU = (strpos($u, 'DIESEL') !== false && strpos($u, 'MULTIPLE') !== false) || strpos($u, 'DEMU') !== false; $tokenUnres = $isMEMU || $isDEMU || strpos($u, 'SUBURBAN') !== false || strpos($u, 'PASSENGER') !== false || strpos($u, 'EMU') !== false || in_array($u, ['SUB','PASS','MEX'], true); $reservedType = false; if ($u !== '') { foreach (['RAJDHANI','SHATABDI','DURONTO','SUPERFAST','SUF','GARIB RATH','HUMSAFAR','HMSR', 'VANDE BHARAT','VBEX','TEJAS','DOUBLE DECKER','MAIL EXPRESS','INTERCITY','SAMPARK KRANTI', 'SUVIDHA','RAJ','DRNT'] as $rk) { if (strpos($u, $rk) !== false) { $reservedType = true; break; } } } $d = ($no !== '') ? $no[0] : ''; $noUnres = in_array($d, ['3','4','5','6','7','9'], true); if ($reservedType) $unreserved = false; elseif ($tokenUnres) $unreserved = true; else $unreserved = $noUnres; $map = [ 'MAIL EXPRESS'=>'Mail/Express','SUPERFAST'=>'Superfast','SUF'=>'Superfast', 'INTERCITY'=>'Intercity','INT'=>'Intercity','GARIB RATH'=>'Garib Rath', 'SAMPARK KRANTI'=>'Sampark Kranti','SHATABDI'=>'Shatabdi','JAN SHATABDI'=>'Jan Shatabdi', 'RAJDHANI'=>'Rajdhani','RAJ'=>'Rajdhani','DURONTO'=>'Duronto','DRNT'=>'Duronto', 'HUMSAFAR'=>'Humsafar','HMSR'=>'Humsafar','VANDE BHARAT EXPRESS'=>'Vande Bharat','VBEX'=>'Vande Bharat', 'TEJAS EXPRESS'=>'Tejas','DOUBLE DECKER'=>'Double Decker','DD'=>'Double Decker', 'SUBURBAN'=>'Suburban','SUB'=>'Suburban','PASSENGER'=>'Passenger','PASS'=>'Passenger','MEX'=>'MEMU', ]; if ($isMEMU) $label = 'MEMU'; elseif ($isDEMU) $label = 'DEMU'; elseif (isset($map[$u])) $label = $map[$u]; elseif ($u !== '') $label = ucwords(strtolower($u)); else $label = ['3'=>'Suburban','4'=>'Suburban','5'=>'Passenger','6'=>'MEMU','7'=>'DEMU','9'=>'Suburban'][$d] ?? ''; return [$label, $unreserved]; } function rd_tag(array $l): string { if (!empty($l['harvested'])) { $days = $l['days'] ?? '—'; if (!empty($l['stale'])) return ' · Runs: ' . e($days) . ' · re-checking'; return ' · Runs: ' . e($days) . ''; } return ' · days not verified — assumed daily'; } $travelTs = strtotime($date . ' 00:00'); function whenstr(int $absMin): string { global $travelTs; $ts = $travelTs + $absMin * 60; return date('H:i', $ts) . ' · ' . date('D j M', $ts); } function station_node(string $code, string $kind, ?int $layover): void { ?>
🚉
wait
total tap for details ▾close ▴
🚆
dep arr
0): $w = $leg['board'] - $legs[$i - 1]['arr']; ?>
Change at () — wait
 Unreserved — general / unreserved ticket; book online on the RailOne app

dep

arr

Journeys

New search
Times are indicative (based on a reference timetable). All trains running on the selected date are shown. Always confirm on the official source before booking.
Reserved train (bookable online) Unreserved train (book on the RailOne app)
Travelling unreserved (general class)? Book your ticket online on Indian Railways’ official RailOne app. Google Play App Store

Enter an origin and destination to see journeys.

All trains from for today have already departed. Try tomorrow's date.No trains found for on . Try a nearby date or a different pair of stations.