amtrak 1.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (23) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/amtrak.gemspec +2 -1
  4. data/lib/amtrak.rb +6 -0
  5. data/lib/amtrak/train_fetcher.rb +21 -7
  6. data/lib/amtrak/train_fetcher/main_page.rb +28 -25
  7. data/lib/amtrak/train_fetcher/train_page.rb +17 -14
  8. data/lib/amtrak/version.rb +1 -1
  9. data/spec/amtrak/train_fetcher/main_page_spec.rb +5 -27
  10. data/spec/amtrak/train_fetcher/train_page_spec.rb +2 -21
  11. data/spec/amtrak/train_fetcher_spec.rb +3 -3
  12. data/spec/amtrak_spec.rb +2 -1
  13. data/spec/fixtures/json/_get.json +54 -1
  14. data/spec/fixtures/vcr/Amtrak/_get/returns_a_list_of_train_times.yml +1020 -1127
  15. data/spec/fixtures/vcr/Amtrak_TrainFetcher/_get/does_the_same_as_get.yml +1376 -930
  16. data/spec/fixtures/vcr/Amtrak_TrainFetcher/_get/given_a_valid_date_and_invalid_train_stations/does_not_include_various_classes_and_includes_an_error.yml +875 -659
  17. data/spec/fixtures/vcr/Amtrak_TrainFetcher/_get/given_a_valid_date_and_train_stations/includes_various_classes.yml +1376 -925
  18. data/spec/fixtures/vcr/Amtrak_TrainFetcher/_get/given_an_invalid_date_and_valid_train_stations/does_not_include_various_classes_and_includes_an_error.yml +878 -663
  19. data/spec/fixtures/vcr/Amtrak_TrainFetcher_MainPage/_session_id/pulls_the_session_id_from_the_cookies.yml +905 -845
  20. data/spec/fixtures/vcr/Amtrak_TrainFetcher_MainPage/_total_pages/when_more_than_one_page_exists_on_the_website/returns_2.yml +1033 -781
  21. data/spec/fixtures/vcr/Amtrak_TrainFetcher_MainPage/_total_pages/when_only_one_page_exists_on_the_website/returns_1.yml +936 -790
  22. data/spec/support/vcr.rb +2 -1
  23. metadata +22 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a54ef0f3c09e8114c0dd5b822f30c6f28ceae3a9
4
- data.tar.gz: 6289270b60dfcea4db75badb5f1f5a03ea94b67a
3
+ metadata.gz: f6b65ee543c0ab3f6c50c3f6af2d873fdb6ea8bf
4
+ data.tar.gz: 0bbe04375b423a44476f15dceaa7f85a293756fd
5
5
  SHA512:
6
- metadata.gz: 25490cf1b1d64518236723a508dc1825f3eb1d8d316d0c399d7a9f2122be65c390be18569979929f371ba7012721e87883d8bf74d0833c9f14280439be0d8b22
7
- data.tar.gz: e0ccb8fd344a69f1a10de8b903bc1ac5ac3062071f3d35d2270dda186537600cb7b6ab3ed324cd406393ff1a1784b6a3f49ebd5334ad4a11fd6d7c2dc6c38e53
6
+ metadata.gz: 458227e5403222869fe87f704ee5ea19aeb61c936299fd5a4684979531ec473240fb5bec3df3bbd6bf7de963b2d0b7483d360cb0bdefed152ef333c49fe01a0e
7
+ data.tar.gz: 7e3ca8eea40bda322a3f5a7edefbf090e5a50cd6933726eb81a2919da430fc39b860fb276d3e7b8036f984c9715120f54482649c83e4edad33ab743e530840da
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_dependency 'excon', '~> 0.41.0'
20
+ spec.add_dependency 'mechanize', '~> 2.7'
21
21
  spec.add_dependency 'nokogiri', '~> 1.6.4'
22
22
 
23
23
  spec.add_development_dependency 'rake'
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'rubocop', '~> 0.25.0'
27
27
  spec.add_development_dependency 'simplecov', '~> 0.8.2'
28
28
  spec.add_development_dependency 'vcr', '~> 2.9.2'
29
+ spec.add_development_dependency 'webmock', '~> 1.24'
29
30
  end
@@ -1,7 +1,13 @@
1
+ require 'logger'
2
+
1
3
  # Main Amtrak module
2
4
  module Amtrak
3
5
  class Error < StandardError; end
4
6
 
7
+ def self.logger
8
+ @logger ||= Logger.new(STDOUT)
9
+ end
10
+
5
11
  def self.get(from, to, date: nil)
6
12
  Amtrak::TrainFetcher.get(from, to, date: date).map do |html|
7
13
  Amtrak::TrainParser.parse(html)
@@ -1,3 +1,5 @@
1
+ require 'mechanize'
2
+
1
3
  module Amtrak
2
4
  # Service for getting train time HTML page from the Amtrak website
3
5
  class TrainFetcher
@@ -20,21 +22,33 @@ module Amtrak
20
22
  end
21
23
 
22
24
  def get
23
- (1..total_pages).map do |page|
24
- Amtrak::TrainFetcher::TrainPage.get(session_id, page)
25
+ requests = [first_page.body]
26
+
27
+ (2..total_pages).each do |page|
28
+ requests << Amtrak::TrainFetcher::TrainPage.get(agent, page)
25
29
  end
30
+
31
+ requests
26
32
  end
27
33
 
28
34
  def first_page
29
- @first_page ||= Amtrak::TrainFetcher::MainPage.new(from, to, date: date)
35
+ @first_page ||= Amtrak::TrainFetcher::MainPage.new(agent, from, to, date: date)
30
36
  end
31
37
 
32
- def session_id
33
- @session_id ||= first_page.session_id
38
+ def total_pages
39
+ return @total_pages if @total_pages
40
+
41
+ total_pages = first_page.total_pages
42
+ Amtrak.logger.debug "Total pages: #{total_pages}"
43
+
44
+ @total_pages = total_pages
34
45
  end
35
46
 
36
- def total_pages
37
- @total_pages ||= first_page.total_pages
47
+ def agent
48
+ @agent ||= Mechanize.new.tap do |m|
49
+ m.user_agent_alias = 'Mac Safari'
50
+ m.log = Amtrak.logger
51
+ end
38
52
  end
39
53
  end
40
54
  end
@@ -1,13 +1,13 @@
1
- require 'excon'
2
1
  require 'date'
3
2
 
4
3
  module Amtrak
5
4
  class TrainFetcher
6
5
  # Service for getting train time results/cookies from the Amtrak website
7
6
  class MainPage
8
- attr_reader :from, :to
7
+ attr_reader :agent, :from, :to
9
8
 
10
- def initialize(from, to, date: nil)
9
+ def initialize(agent, from, to, date: nil)
10
+ @agent = agent
11
11
  @from = from
12
12
  @to = to
13
13
  @date = date
@@ -16,20 +16,19 @@ module Amtrak
16
16
  def request
17
17
  retries ||= 3
18
18
  _request
19
- rescue Excon::Errors::SocketError, Excon::Errors::Timeout
19
+ rescue Mechanize::ResponseCodeError
20
20
  retries -= 1
21
- retry unless retries.zero?
21
+ raise if retries.zero?
22
+
23
+ retry
22
24
  end
23
25
 
24
26
  def _request
25
- @request ||= Excon.post(
26
- 'http://tickets.amtrak.com/itd/amtrak',
27
- headers: headers,
28
- body: URI.encode_www_form(body),
29
- expects: [200]
27
+ @request ||= agent.post(
28
+ 'https://tickets.amtrak.com/itd/amtrak',
29
+ request_body,
30
+ headers
30
31
  )
31
- rescue Excon::Errors::ClientError, Excon::Errors::ServerError => ex
32
- raise Amtrak::TrainFetcher::Error, "#{ex.class} #{ex.message}"
33
32
  end
34
33
 
35
34
  def headers
@@ -37,17 +36,17 @@ module Amtrak
37
36
  end
38
37
 
39
38
  # rubocop:disable all
40
- def body
39
+ def request_body
41
40
  {
42
- "_handler=amtrak.presentation.handler.request.rail.AmtrakRailTrainStatusSearchRequestHandler/_xpath=/sessionWorkflow/productWorkflow[@product='Rail']" => '',
41
+ "_handler=amtrak.presentation.handler.request.rail.AmtrakRailTrainStatusSearchRequestHandler/_xpath=/sessionWorkflow/productWorkflow[@product='Rail']" => "",
43
42
  "/sessionWorkflow/productWorkflow[@product='Rail']/tripRequirements/journeyRequirements[1]/departDate.usdate" => departure_date,
44
- "/sessionWorkflow/productWorkflow[@product='Rail']/tripRequirements/@trainStatusType" => 'statusByCityPair',
45
- "/sessionWorkflow/productWorkflow[@product='Rail']/tripRequirements/journeyRequirements[1]/departDate/@radioSelect" => 'arrivalTime',
46
- 'requestor' => 'amtrak.presentation.handler.page.rail.AmtrakRailGetTrainStatusPageHandler',
47
- 'xwdf_origin' => "/sessionWorkflow/productWorkflow[@product='Rail']/travelSelection/journeySelection[1]/departLocation/search",
48
- 'wdf_origin' => from.to_s,
49
- 'xwdf_destination' => "/sessionWorkflow/productWorkflow[@product='Rail']/travelSelection/journeySelection[1]/arriveLocation/search",
50
- 'wdf_destination' => to.to_s,
43
+ "/sessionWorkflow/productWorkflow[@product='Rail']/tripRequirements/@trainStatusType" => "statusByCityPair",
44
+ "/sessionWorkflow/productWorkflow[@product='Rail']/tripRequirements/journeyRequirements[1]/departDate/@radioSelect" => "arrivalTime",
45
+ "requestor" => "amtrak.presentation.handler.page.rail.AmtrakRailGetTrainStatusPageHandler",
46
+ "xwdf_origin" => "/sessionWorkflow/productWorkflow[@product='Rail']/travelSelection/journeySelection[1]/departLocation/search",
47
+ "wdf_origin" => from.to_s,
48
+ "xwdf_destination" => "/sessionWorkflow/productWorkflow[@product='Rail']/travelSelection/journeySelection[1]/arriveLocation/search",
49
+ "wdf_destination" => to.to_s,
51
50
  }
52
51
  end
53
52
  # rubocop:enable all
@@ -60,8 +59,8 @@ module Amtrak
60
59
  @date ||= Date.today
61
60
  end
62
61
 
63
- def session_id
64
- request.headers['Set-Cookie'].match(/JSESSIONID=([^;]*)/)[1]
62
+ def body
63
+ request.body
65
64
  end
66
65
 
67
66
  def total_pages
@@ -69,11 +68,15 @@ module Amtrak
69
68
  end
70
69
 
71
70
  def extract_listing_length
72
- request.body.match(/var availabilityLength = '(\d+)';/)[1]
71
+ if matches = body.match(/var availabilityLength = '(\d+)';/)
72
+ matches[1]
73
+ else
74
+ 0
75
+ end
73
76
  end
74
77
 
75
78
  def release
76
- request.body.match(/"Amtrak Release ([^"]+)"/)[1]
79
+ body.match(/"Amtrak Release ([^"]+)"/)[1]
77
80
  end
78
81
  end
79
82
  end
@@ -1,5 +1,3 @@
1
- require 'excon'
2
-
3
1
  module Amtrak
4
2
  class TrainFetcher
5
3
  # Service for getting per page train time HTML from the Amtrak website
@@ -8,10 +6,10 @@ module Amtrak
8
6
  new(*args).get
9
7
  end
10
8
 
11
- attr_reader :session_id, :page
9
+ attr_reader :agent, :page
12
10
 
13
- def initialize(session_id, page)
14
- @session_id = session_id
11
+ def initialize(agent, page)
12
+ @agent = agent
15
13
  @page = page
16
14
  end
17
15
 
@@ -22,23 +20,28 @@ module Amtrak
22
20
  def request
23
21
  retries ||= 3
24
22
  _request
25
- rescue SocketError, TimeoutError
23
+ rescue Mechanize::ResponseCodeError
26
24
  retries -= 1
27
- retry unless retries.zero?
25
+ raise if retries.zero?
26
+
27
+ retry
28
28
  end
29
29
 
30
30
  def _request
31
- @request ||= Excon.get(
32
- 'https://tickets.amtrak.com/itd/amtrak/TrainStatusRequest',
33
- headers: headers,
34
- query: query
31
+ @request ||= agent.get(
32
+ "https://tickets.amtrak.com/itd/amtrak/TrainStatusRequest?&_trainstatuspage=#{page}",
33
+ [],
34
+ nil,
35
+ headers
35
36
  )
36
- rescue Excon::Errors::ClientError, Excon::Errors::ServerError => ex
37
- raise Amtrak::TrainFetcher::Error, "#{ex.class} #{ex.message}"
38
37
  end
39
38
 
40
39
  def headers
41
- { 'Cookie' => "JSESSIONID=#{session_id}" }
40
+ {
41
+ 'ADRUM' => 'isAjax:true',
42
+ 'X-Prototype-Version' => '1.6.0.3',
43
+ 'X-Requested-With' => 'XMLHttpRequest'
44
+ }
42
45
  end
43
46
 
44
47
  def query
@@ -1,6 +1,6 @@
1
1
  module Amtrak
2
2
  # This module holds the Amtrak version
3
3
  module Version
4
- VERSION = '1.2.2'
4
+ VERSION = '2.0.0'
5
5
  end
6
6
  end
@@ -1,8 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Amtrak::TrainFetcher::MainPage do
4
+ let(:agent) { Mechanize.new }
4
5
  describe '#departure_date' do
5
- subject { described_class.new('', '', date: date) }
6
+ subject { described_class.new(nil, '', '', date: date) }
6
7
  let(:date) { Date.parse('2014-11-12') }
7
8
 
8
9
  it 'prints out a formatted date' do
@@ -11,7 +12,7 @@ describe Amtrak::TrainFetcher::MainPage do
11
12
  end
12
13
 
13
14
  describe '#date' do
14
- subject { described_class.new('', '', date: date) }
15
+ subject { described_class.new(nil, '', '', date: date) }
15
16
 
16
17
  context 'on an instance with a date' do
17
18
  let(:date) { Date.parse('2014-11-12') }
@@ -34,7 +35,7 @@ describe Amtrak::TrainFetcher::MainPage do
34
35
 
35
36
  describe '#total_pages' do
36
37
  context 'when only one page exists on the website' do
37
- subject { described_class.new('pvd', 'bby', date: Date.parse('2014-12-07')) }
38
+ subject { described_class.new(agent, 'pvd', 'bby', date: Date.parse('2016-01-03')) }
38
39
 
39
40
  it 'returns 1', :vcr do
40
41
  expect(subject.total_pages).to eq(1)
@@ -42,34 +43,11 @@ describe Amtrak::TrainFetcher::MainPage do
42
43
  end
43
44
 
44
45
  context 'when more than one page exists on the website' do
45
- subject { described_class.new('pvd', 'bby', date: Date.parse('2014-12-06')) }
46
+ subject { described_class.new(agent, 'pvd', 'bby', date: Date.parse('2015-12-31')) }
46
47
 
47
48
  it 'returns 2', :vcr do
48
49
  expect(subject.total_pages).to eq(2)
49
50
  end
50
51
  end
51
52
  end
52
-
53
- describe '#session_id' do
54
- subject { described_class.new('pvd', 'bby', date: Date.parse('2014-12-06')) }
55
- it 'pulls the session id from the cookies', :vcr do
56
- expect(subject.session_id).to eq('0000faPsyYZwp2n8Wb_4BDhukyg:187j59p07')
57
- end
58
- end
59
-
60
- describe '#request' do
61
- context 'when Excon raises' do
62
- subject { described_class.new('', '') }
63
-
64
- it 'returns a TrainFetcher::Error' do
65
- expect(Excon).to receive(:post) { fail Excon::Errors::ClientError, '' }
66
- expect { subject.request }.to raise_error(Amtrak::TrainFetcher::Error)
67
- end
68
-
69
- it 'returns a TrainFetcher::Error' do
70
- expect(Excon).to receive(:post) { fail Excon::Errors::ServerError, '' }
71
- expect { subject.request }.to raise_error(Amtrak::TrainFetcher::Error)
72
- end
73
- end
74
- end
75
53
  end
@@ -1,11 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Amtrak::TrainFetcher::TrainPage do
4
- subject { described_class.get(session_id, page) }
4
+ let(:agent) { Mechanize.new }
5
+ subject { described_class.get(agent, page) }
5
6
 
6
7
  describe '#get', :vcr do
7
8
  context 'when it works' do
8
- let(:session_id) { '0000faPsyYZwp2n8Wb_4BDhukyg:187j59p07' }
9
9
  let(:page) { '1' }
10
10
 
11
11
  it 'includes various classes' do
@@ -14,24 +14,5 @@ describe Amtrak::TrainFetcher::TrainPage do
14
14
  expect(subject).to include('resp_by_citypair_arrive_status_details')
15
15
  end
16
16
  end
17
-
18
- context 'when Excon raises an error' do
19
- let(:session_id) { '0000faPsyYZwp2n8Wb_4BDhukyg:187j59p07' }
20
- let(:page) { '1' }
21
-
22
- it 'reraises as a TrainFetcher::Error' do
23
- expect(Excon).to receive(:get) {
24
- fail Excon::Errors::ClientError, ''
25
- }
26
- expect { subject }.to raise_error(Amtrak::TrainFetcher::Error)
27
- end
28
-
29
- it 'reraises as a TrainFetcher::Error' do
30
- expect(Excon).to receive(:get) {
31
- fail Excon::Errors::ServerError, ''
32
- }
33
- expect { subject }.to raise_error(Amtrak::TrainFetcher::Error)
34
- end
35
- end
36
17
  end
37
18
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Amtrak::TrainFetcher do
4
4
  describe '.get', :vcr do
5
5
  let(:output) do
6
- described_class.get('pvd', 'bby', date: Date.parse('2014-12-07'))
6
+ described_class.get('pvd', 'bby', date: Date.parse('2015-12-31'))
7
7
  end
8
8
 
9
9
  it 'does the same as #get' do
@@ -16,7 +16,7 @@ describe Amtrak::TrainFetcher do
16
16
  describe '#get', :vcr do
17
17
  describe 'given a valid date and train stations' do
18
18
  subject do
19
- described_class.new('pvd', 'bby', date: Date.parse('2014-12-07'))
19
+ described_class.new('pvd', 'bby', date: Date.parse('2015-12-31'))
20
20
  end
21
21
 
22
22
  let(:output) { subject.get }
@@ -44,7 +44,7 @@ describe Amtrak::TrainFetcher do
44
44
 
45
45
  describe 'given a valid date and invalid train stations' do
46
46
  subject do
47
- described_class.new('askdf', 'bby', date: Date.parse('2014-12-07'))
47
+ described_class.new('askdf', 'bby', date: Date.parse('2015-12-31'))
48
48
  end
49
49
 
50
50
  let(:output) { subject.get }
@@ -4,7 +4,7 @@ describe Amtrak do
4
4
  context '.get' do
5
5
  let(:from) { 'pvd' }
6
6
  let(:to) { 'bby' }
7
- let(:date) { Date.parse('Fri, Dec 5, 2014') }
7
+ let(:date) { Date.parse('Sat, Jan 3, 2016') }
8
8
  let(:output) { described_class.get(from, to, date: date) }
9
9
  let(:expected) do
10
10
  JSON.parse(
@@ -14,6 +14,7 @@ describe Amtrak do
14
14
  end
15
15
 
16
16
  it 'returns a list of train times', :vcr do
17
+ puts JSON.pretty_generate(output)
17
18
  expect(output).to eq(expected)
18
19
  end
19
20
  end
@@ -1 +1,54 @@
1
- [{"number":178,"departure":{"date":"Thu, Dec 4 2014","scheduled_time":"11:37 pm","estimated_time":"11:37 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"12:24 am","estimated_time":"12:08 am"}},{"number":66,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"6:56 am","estimated_time":"6:57 am"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"7:53 am","estimated_time":"7:46 am"}},{"number":2190,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"9:18 am","estimated_time":"9:30 am"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"9:59 am","estimated_time":"9:59 am"}},{"number":190,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"10:17 am","estimated_time":"10:30 am"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"10:59 am","estimated_time":"11:01 am"}},{"number":2150,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"10:53 am","estimated_time":"11:09 am"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"11:34 am","estimated_time":"11:38 am"}},{"number":170,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"11:58 am","estimated_time":"12:14 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"12:39 pm","estimated_time":"12:48 pm"}},{"number":2154,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"12:55 pm","estimated_time":"1:04 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"1:34 pm","estimated_time":"1:39 pm"}},{"number":172,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"2:23 pm","estimated_time":"2:47 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"3:09 pm","estimated_time":"3:21 pm"}},{"number":2158,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"2:54 pm","estimated_time":"3:09 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"3:36 pm","estimated_time":"3:40 pm"}},{"number":86,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"3:47 pm","estimated_time":"3:47 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"4:28 pm","estimated_time":"4:19 pm"}},{"number":2160,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"3:54 pm","estimated_time":"4:26 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"4:34 pm","estimated_time":"4:58 pm"}},{"number":174,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"5:30 pm","estimated_time":"5:36 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"6:25 pm","estimated_time":"6:32 pm"}},{"number":2164,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"5:50 pm","estimated_time":"5:59 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"6:34 pm","estimated_time":""}},{"number":2166,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"6:54 pm","estimated_time":"7:13 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"7:35 pm","estimated_time":"7:45 pm"}},{"number":176,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"7:19 pm","estimated_time":"8:45 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"8:06 pm","estimated_time":"9:29 pm"}},{"number":2168,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"7:56 pm","estimated_time":""},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"8:38 pm","estimated_time":""}},{"number":2170,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"8:54 pm","estimated_time":"9:41 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"9:34 pm","estimated_time":""}},{"number":94,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"9:16 pm","estimated_time":"10:21 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"10:02 pm","estimated_time":"11:02 pm"}},{"number":2172,"departure":{"date":"Fri, Dec 5 2014","scheduled_time":"9:54 pm","estimated_time":"10:08 pm"},"arrival":{"date":"Fri, Dec 5 2014","scheduled_time":"10:35 pm","estimated_time":"10:42 pm"}}]
1
+ [
2
+ {
3
+ "number": 194,
4
+ "departure": {
5
+ "date": "Sun, Jan 3 2016",
6
+ "scheduled_time": "8:25 pm",
7
+ "estimated_time": "8:29 pm"
8
+ },
9
+ "arrival": {
10
+ "date": "Sun, Jan 3 2016",
11
+ "scheduled_time": "9:10 pm",
12
+ "estimated_time": "9:00 pm"
13
+ }
14
+ },
15
+ {
16
+ "number": 2256,
17
+ "departure": {
18
+ "date": "Sun, Jan 3 2016",
19
+ "scheduled_time": "8:55 pm",
20
+ "estimated_time": ""
21
+ },
22
+ "arrival": {
23
+ "date": "Sun, Jan 3 2016",
24
+ "scheduled_time": "9:39 pm",
25
+ "estimated_time": ""
26
+ }
27
+ },
28
+ {
29
+ "number": 2258,
30
+ "departure": {
31
+ "date": "Sun, Jan 3 2016",
32
+ "scheduled_time": "9:54 pm",
33
+ "estimated_time": "9:59 pm"
34
+ },
35
+ "arrival": {
36
+ "date": "Sun, Jan 3 2016",
37
+ "scheduled_time": "10:40 pm",
38
+ "estimated_time": "10:38 pm"
39
+ }
40
+ },
41
+ {
42
+ "number": 132,
43
+ "departure": {
44
+ "date": "Sun, Jan 3 2016",
45
+ "scheduled_time": "11:02 pm",
46
+ "estimated_time": "11:03 pm"
47
+ },
48
+ "arrival": {
49
+ "date": "Sun, Jan 3 2016",
50
+ "scheduled_time": "11:49 pm",
51
+ "estimated_time": "11:35 pm"
52
+ }
53
+ }
54
+ ]