-
Notifications
You must be signed in to change notification settings - Fork 1
/
times.py
146 lines (111 loc) · 4.3 KB
/
times.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from urllib.request import urlopen
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
import config
# Detect past-midnight trains that will be given tomorrow's date
DAY_CUTOFF = 4
class Train(object):
def __init__(self, row):
# managed by property
self._timetabled_departure = None
self._actual_departure = None
self.destination = None
self.platform = None
self.running = True
self.delayed = False
self.minutes_late = None
self.__parse_row(row)
@property
def timetabled_departure(self):
return self._timetabled_departure
@timetabled_departure.setter
def timetabled_departure(self, value):
self._timetabled_departure = self.__parse_time(value)
@property
def actual_departure(self):
return self._actual_departure
@actual_departure.setter
def actual_departure(self, value):
self._actual_departure = self.__parse_time(value)
@property
def unknown_delay(self):
return self.delayed and self.minutes_late is None
@property
def severely_delayed(self):
return self.delayed and (
self.unknown_delay or
self.minutes_late >= config.SEVERE_DELAY_THRESHOLD)
@property
def minutes_until(self):
if self.actual_departure:
return int((self.actual_departure - datetime.now()).total_seconds() / 60)
def __str__(self):
return "{0} to {1}".format(
self.timetabled_departure.strftime("%H:%M"),
self.destination)
def will_depart(self):
return self.running and (
self.actual_departure is None or
self.actual_departure > datetime.now())
def __parse_time(self, hhmm):
time = datetime.strptime(hhmm.strip().strip("ad"), "%H:%M").time()
day = datetime.today()
now = datetime.now()
# Looking across the midnight boundary, use tomorrow's date
if time.hour < DAY_CUTOFF and not now.hour < DAY_CUTOFF:
day += timedelta(days=1)
return datetime.combine(day, time)
def __parse_row(self, row):
# ROW: due time, destination, status, platform, details
due, destination, status, platform, _ = row
self.destination = destination[0].strip()
self.timetabled_departure = due[0]
self.platform = platform[0] if platform else ''
status = [s.strip() for s in status]
if 'Cancelled' in status:
self.running = False
elif 'late' in status or 'Delayed' in status:
self.delayed = True
# Read in expected time
try:
self.actual_departure = status[0]
self.minutes_late = int(status[2])
except (ValueError, KeyError):
pass
else:
# On time
self._actual_departure = self._timetabled_departure
class NRETimes(object):
def __init__(self, station_code, directions):
self.station_code = station_code
self.directions = directions
self.error = False
def live_doc(self):
# Yeah, scraping is naughty. Open up your data, NRE!
nre_url = "http://ojp.nationalrail.co.uk/service/ldbboard/dep/%s" % \
self.station_code.upper()
html = urlopen(nre_url).read()
return BeautifulSoup(html)
#open("nre_hsl.html", "w").write(html.decode('utf8'))
def doc(self):
cache = "data/nre_%s.html" % self.station_code
html = open(cache).read()
return BeautifulSoup(html)
def trains(self):
self.error = False
try:
table = self.live_doc().find('table')
if table:
for row in table.tbody.find_all('tr'):
cells = row.find_all("td")
yield Train([list(td.strings) for td in cells])
except Exception as e:
import traceback
traceback.print_exc()
self.error = True
def departing(self):
return (t for t in self.trains() if t.will_depart())
def going_to(self, direction):
"""Return all trains going in a particular direction"""
destinations = [d.lower() for d in self.directions[direction]]
return (t for t in self.departing() if t.destination.lower() in destinations)