amtrak 1.2.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ ]