diff --git a/docs/journeys.md b/docs/journeys.md index 86446da8..0656934c 100644 --- a/docs/journeys.md +++ b/docs/journeys.md @@ -41,6 +41,8 @@ With `opt`, you can override the default options, which look like this: ```js { when: new Date(), + earlierThan: null, // ref to get journeys earlier than the last query + laterThan: null, // ref to get journeys later than the last query results: 5, // how many journeys? via: null, // let journeys pass this station passedStations: false, // return stations on the way? @@ -85,123 +87,127 @@ client.journeys('900000003201', '900000100008', { The response may look like this: ```js -[ { - legs: [ { - id: '1|31041|35|86|17122017', +[ + { + legs: [ { + id: '1|31041|35|86|17122017', + origin: { + type: 'station', + id: '900000003201', + name: 'S+U Berlin Hauptbahnhof', + location: { + type: 'location', + latitude: 52.52585, + longitude: 13.368928 + }, + products: { + suburban: true, + subway: true, + tram: true, + bus: true, + ferry: false, + express: true, + regional: true + } + }, + departure: '2017-12-17T19:07:00.000+01:00', + departurePlatform: '16', + destination: { + type: 'station', + id: '900000024101', + name: 'S Charlottenburg', + location: { + type: 'location', + latitude: 52.504806, + longitude: 13.303846 + }, + products: { + suburban: true, + subway: false, + tram: false, + bus: true, + ferry: false, + express: false, + regional: true + } + }, + arrival: '2017-12-17T19:47:00.000+01:00', + arrivalPlatform: '8', + arrivalDelay: 30, + line: { + type: 'line', + id: '16845', + name: 'S7', + public: true, + mode: 'train', + product: 'suburban', + symbol: 'S', + nr: 7, + metro: false, + express: false, + night: false, + productCode: 0, + operator: { + type: 'operator', + id: 's-bahn-berlin-gmbh', + name: 'S-Bahn Berlin GmbH' + } + }, + direction: 'S Potsdam Hauptbahnhof', + passed: [ { + station: { + type: 'station', + id: '900000003201', + name: 'S+U Berlin Hauptbahnhof', + location: { /* … */ }, + products: { /* … */ } + }, + arrival: null, + departure: null, + cancelled: true + }, { + station: { + type: 'station', + id: '900000003102', + name: 'S Bellevue', + location: { /* … */ }, + products: { /* … */ } + }, + arrival: '2017-12-17T19:09:00.000+01:00', + departure: '2017-12-17T19:09:00.000+01:00' + }, /* … */ { + station: { + type: 'station', + id: '900000024101', + name: 'S Charlottenburg', + location: { /* … */ }, + products: { /* … */ } + }, + arrival: '2017-12-17T19:17:00.000+01:00', + departure: '2017-12-17T19:17:00.000+01:00' + } ] + } ], origin: { type: 'station', id: '900000003201', name: 'S+U Berlin Hauptbahnhof', - location: { - type: 'location', - latitude: 52.52585, - longitude: 13.368928 - }, - products: { - suburban: true, - subway: true, - tram: true, - bus: true, - ferry: false, - express: true, - regional: true - } + location: { /* … */ }, + products: { /* … */ } }, departure: '2017-12-17T19:07:00.000+01:00', - departurePlatform: '16', destination: { type: 'station', id: '900000024101', name: 'S Charlottenburg', - location: { - type: 'location', - latitude: 52.504806, - longitude: 13.303846 - }, - products: { - suburban: true, - subway: false, - tram: false, - bus: true, - ferry: false, - express: false, - regional: true - } + location: { /* … */ }, + products: { /* … */ } }, arrival: '2017-12-17T19:47:00.000+01:00', - arrivalPlatform: '8', - arrivalDelay: 30, - line: { - type: 'line', - id: '16845', - name: 'S7', - public: true, - mode: 'train', - product: 'suburban', - symbol: 'S', - nr: 7, - metro: false, - express: false, - night: false, - productCode: 0, - operator: { - type: 'operator', - id: 's-bahn-berlin-gmbh', - name: 'S-Bahn Berlin GmbH' - } - }, - direction: 'S Potsdam Hauptbahnhof', - passed: [ { - station: { - type: 'station', - id: '900000003201', - name: 'S+U Berlin Hauptbahnhof', - location: { /* … */ }, - products: { /* … */ } - }, - arrival: null, - departure: null, - cancelled: true - }, { - station: { - type: 'station', - id: '900000003102', - name: 'S Bellevue', - location: { /* … */ }, - products: { /* … */ } - }, - arrival: '2017-12-17T19:09:00.000+01:00', - departure: '2017-12-17T19:09:00.000+01:00' - }, /* … */ { - station: { - type: 'station', - id: '900000024101', - name: 'S Charlottenburg', - location: { /* … */ }, - products: { /* … */ } - }, - arrival: '2017-12-17T19:17:00.000+01:00', - departure: '2017-12-17T19:17:00.000+01:00' - } ] - } ], - origin: { - type: 'station', - id: '900000003201', - name: 'S+U Berlin Hauptbahnhof', - location: { /* … */ }, - products: { /* … */ } + arrivalDelay: 30 }, - departure: '2017-12-17T19:07:00.000+01:00', - destination: { - type: 'station', - id: '900000024101', - name: 'S Charlottenburg', - location: { /* … */ }, - products: { /* … */ } - }, - arrival: '2017-12-17T19:47:00.000+01:00', - arrivalDelay: 30 -} ] + earlierRef: '…', // use with the `earlierThan` option + laterRef: '…' // use with the `laterThan` option +] ``` Some [profiles](../p) are able to parse the ticket information, if returned by the API. For example, if you pass `tickets: true` with the [VBB profile](../p/vbb), each `journey` will have a tickets array that looks like this: @@ -242,3 +248,31 @@ Some [profiles](../p) are able to parse the ticket information, if returned by t ``` If a journey leg has been cancelled, a `cancelled: true` will be added. Also, `departure`/`departureDelay`/`departurePlatform` and `arrival`/`arrivalDelay`/`arrivalPlatform` will be `null`. + +To get more journeys earlier/later than the current set of results, use `journey.earlierRef`/`journey.laterRef` as follows: + +```js +const hbf = '900000003201' +const heinrichHeineStr = '900000100008' + +client.journeys(hbf, heinrichHeineStr) +.then((journeys) => { + const lastJourney = journeys[journeys.length - 1] + console.log('departure of last journey', lastJourney.departure) + + // get later journeys + return client.journeys(hbf, heinrichHeineStr, { + laterThan: journeys.laterRef + }) +}) +.then((laterourneys) => { + const firstJourney = laterourneys[laterourneys.length - 1] + console.log('departure of first (later) journey', firstJourney.departure) +}) +.catch(console.error) +``` + +``` +departure of last journey 2017-12-17T19:07:00.000+01:00 +departure of first (later) journey 2017-12-17T19:19:00.000+01:00 +``` diff --git a/index.js b/index.js index 1a7ce2a5..5e38e696 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ const defaultProfile = require('./lib/default-profile') const _request = require('./lib/request') const isObj = o => o !== null && 'object' === typeof o && !Array.isArray(o) +const isNonEmptyString = str => 'string' === typeof str && str.length > 0 const createClient = (profile, request = _request) => { profile = Object.assign({}, defaultProfile, profile) @@ -51,6 +52,29 @@ const createClient = (profile, request = _request) => { from = profile.formatLocation(profile, from) to = profile.formatLocation(profile, to) + if (('earlierThan' in opt) && ('laterThan' in opt)) { + throw new Error('opt.laterThan and opt.laterThan are mutually exclusive.') + } + let journeysRef = null + if ('earlierThan' in opt) { + if (!isNonEmptyString(opt.earlierThan)) { + throw new Error('opt.earlierThan must be a non-empty string.') + } + if ('when' in opt) { + throw new Error('opt.earlierThan and opt.when are mutually exclusive.') + } + journeysRef = opt.earlierThan + } + if ('laterThan' in opt) { + if (!isNonEmptyString(opt.laterThan)) { + throw new Error('opt.laterThan must be a non-empty string.') + } + if ('when' in opt) { + throw new Error('opt.laterThan and opt.when are mutually exclusive.') + } + journeysRef = opt.laterThan + } + opt = Object.assign({ results: 5, // how many journeys? via: null, // let journeys pass this station? @@ -80,6 +104,7 @@ const createClient = (profile, request = _request) => { const query = profile.transformJourneysQuery({ outDate: profile.formatDate(profile, opt.when), outTime: profile.formatTime(profile, opt.when), + ctxScr: journeysRef, numF: opt.results, getPasslist: !!opt.passedStations, maxChg: opt.transfers, @@ -105,12 +130,16 @@ const createClient = (profile, request = _request) => { .then((d) => { if (!Array.isArray(d.outConL)) return [] const parse = profile.parseJourney(profile, d.locations, d.lines, d.remarks) - return d.outConL.map(parse) + const res = d.outConL.map(parse) + + if (d.outCtxScrB) res.earlierRef = d.outCtxScrB + if (d.outCtxScrF) res.laterRef = d.outCtxScrF + return res }) } const locations = (query, opt = {}) => { - if ('string' !== typeof query || !query) { + if (!isNonEmptyString(query)) { throw new Error('query must be a non-empty string.') } opt = Object.assign({ @@ -204,10 +233,10 @@ const createClient = (profile, request = _request) => { } const journeyLeg = (ref, lineName, opt = {}) => { - if ('string' !== typeof ref || !ref) { + if (!isNonEmptyString(ref)) { throw new Error('ref must be a non-empty string.') } - if ('string' !== typeof lineName || !lineName) { + if (!isNonEmptyString(lineName)) { throw new Error('lineName must be a non-empty string.') } opt = Object.assign({ diff --git a/test/db.js b/test/db.js index 17b1a140..f38d2ddd 100644 --- a/test/db.js +++ b/test/db.js @@ -53,7 +53,7 @@ const findStation = (id) => new Promise((yay, nay) => { const isJungfernheide = (s) => { return s.type === 'station' && - (s.id === '008011167' || s.id === '8011167') && + (s.id === '008011167' || s.id === jungfernh) && s.name === 'Berlin Jungfernheide' && s.location && isRoughlyEqual(s.location.latitude, 52.530408, .0005) && @@ -62,7 +62,7 @@ const isJungfernheide = (s) => { const assertIsJungfernheide = (t, s) => { t.equal(s.type, 'station') - t.ok(s.id === '008011167' || s.id === '8011167', 'id should be 8011167') + t.ok(s.id === '008011167' || s.id === jungfernh, 'id should be 8011167') t.equal(s.name, 'Berlin Jungfernheide') t.ok(s.location) t.ok(isRoughlyEqual(s.location.latitude, 52.530408, .0005)) @@ -92,8 +92,14 @@ const assertValidPrice = (t, p) => { const test = tapePromise(tape) const client = createClient(dbProfile) +const jungfernh = '8011167' +const berlinHbf = '8011160' +const münchenHbf = '8000261' +const hannoverHbf = '8000152' +const regensburgHbf = '8000309' + test('Berlin Jungfernheide to München Hbf', co(function* (t) { - const journeys = yield client.journeys('8011167', '8000261', { + const journeys = yield client.journeys(jungfernh, münchenHbf, { when, passedStations: true }) @@ -154,7 +160,7 @@ test('Berlin Jungfernheide to München Hbf', co(function* (t) { })) test('Berlin Jungfernheide to Torfstraße 17', co(function* (t) { - const journeys = yield client.journeys('8011167', { + const journeys = yield client.journeys(jungfernh, { type: 'location', address: 'Torfstraße 17', latitude: 52.5416823, longitude: 13.3491223 }, {when}) @@ -183,7 +189,7 @@ test('Berlin Jungfernheide to Torfstraße 17', co(function* (t) { })) test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) { - const journeys = yield client.journeys('8011167', { + const journeys = yield client.journeys(jungfernh, { type: 'location', id: '991598902', name: 'ATZE Musiktheater', latitude: 52.542417, longitude: 13.350437 }, {when}) @@ -212,9 +218,6 @@ test('Berlin Jungfernheide to ATZE Musiktheater', co(function* (t) { })) test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t) { - const berlinHbf = '8011160' - const münchenHbf = '8000261' - const hannoverHbf = '8000152' const [journey] = yield client.journeys(berlinHbf, münchenHbf, { via: hannoverHbf, results: 1 @@ -230,8 +233,58 @@ test('Berlin Hbf to München Hbf with stopover at Hannover Hbf', co(function* (t t.end() })) +test('earlier/later journeys, Jungfernheide -> München Hbf', co(function* (t) { + const model = yield client.journeys(jungfernh, münchenHbf, { + results: 3, when + }) + + t.equal(typeof model.earlierRef, 'string') + t.ok(model.earlierRef) + t.equal(typeof model.laterRef, 'string') + t.ok(model.laterRef) + + // when and earlierThan/laterThan should be mutually exclusive + t.throws(() => { + client.journeys(jungfernh, münchenHbf, { + when, earlierThan: model.earlierRef + }) + }) + t.throws(() => { + client.journeys(jungfernh, münchenHbf, { + when, laterThan: model.laterRef + }) + }) + + let earliestDep = Infinity, latestDep = -Infinity + for (let j of model) { + const dep = +new Date(j.departure) + if (dep < earliestDep) earliestDep = dep + else if (dep > latestDep) latestDep = dep + } + + const earlier = yield client.journeys(jungfernh, münchenHbf, { + results: 3, + // todo: single journey ref? + earlierThan: model.earlierRef + }) + for (let j of earlier) { + t.ok(new Date(j.departure) < earliestDep) + } + + const later = yield client.journeys(jungfernh, münchenHbf, { + results: 3, + // todo: single journey ref? + laterThan: model.laterRef + }) + for (let j of later) { + t.ok(new Date(j.departure) > latestDep) + } + + t.end() +})) + test('departures at Berlin Jungfernheide', co(function* (t) { - const deps = yield client.departures('8011167', { + const deps = yield client.departures(jungfernh, { duration: 5, when }) @@ -252,7 +305,7 @@ test('departures at Berlin Jungfernheide', co(function* (t) { test('departures with station object', co(function* (t) { yield client.departures({ type: 'station', - id: '8011167', + id: jungfernh, name: 'Berlin Jungfernheide', location: { type: 'location', @@ -308,7 +361,6 @@ test('locations named Jungfernheide', co(function* (t) { })) test('location', co(function* (t) { - const regensburgHbf = '8000309' const loc = yield client.location(regensburgHbf) assertValidStation(t, loc) diff --git a/test/oebb.js b/test/oebb.js index 31ba75d4..178c58db 100644 --- a/test/oebb.js +++ b/test/oebb.js @@ -110,9 +110,14 @@ const assertValidLine = (t, l) => { // with optional mode const test = tapePromise(tape) const client = createClient(oebbProfile) +const salzburgHbf = '8100002' +const wienWestbahnhof = '1291501' +const wien = '1190100' +const klagenfurtHbf = '8100085' +const muenchenHbf = '8000261' +const grazHbf = '8100173' + test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) { - const salzburgHbf = '8100002' - const wienWestbahnhof = '1291501' const journeys = yield client.journeys(salzburgHbf, wienWestbahnhof, { when, passedStations: true }) @@ -178,7 +183,6 @@ test('Salzburg Hbf to Wien Westbahnhof', co(function* (t) { })) test('Salzburg Hbf to 1220 Wien, Wagramer Straße 5', co(function* (t) { - const salzburgHbf = '8100002' const wagramerStr = { type: 'location', latitude: 48.236216, @@ -223,7 +227,6 @@ test('Albertina to Salzburg Hbf', co(function* (t) { name: 'Albertina', id: '975900003' } - const salzburgHbf = '8100002' const journeys = yield client.journeys(albertina, salzburgHbf, {when}) t.ok(Array.isArray(journeys)) @@ -255,9 +258,6 @@ test('Albertina to Salzburg Hbf', co(function* (t) { })) test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) { - const wien = '1190100' - const klagenfurtHbf = '8100085' - const salzburgHbf = '8100002' const [journey] = yield client.journeys(wien, klagenfurtHbf, { via: salzburgHbf, results: 1, @@ -274,9 +274,57 @@ test('Wien to Klagenfurt Hbf with stopover at Salzburg Hbf', co(function* (t) { t.end() })) +test('earlier/later journeys, Salzburg Hbf -> Wien Westbahnhof', co(function* (t) { + const model = yield client.journeys(salzburgHbf, wienWestbahnhof, { + results: 3, when + }) + + t.equal(typeof model.earlierRef, 'string') + t.ok(model.earlierRef) + t.equal(typeof model.laterRef, 'string') + t.ok(model.laterRef) + + // when and earlierThan/laterThan should be mutually exclusive + t.throws(() => { + client.journeys(salzburgHbf, wienWestbahnhof, { + when, earlierThan: model.earlierRef + }) + }) + t.throws(() => { + client.journeys(salzburgHbf, wienWestbahnhof, { + when, laterThan: model.laterRef + }) + }) + + let earliestDep = Infinity, latestDep = -Infinity + for (let j of model) { + const dep = +new Date(j.departure) + if (dep < earliestDep) earliestDep = dep + else if (dep > latestDep) latestDep = dep + } + + const earlier = yield client.journeys(salzburgHbf, wienWestbahnhof, { + results: 3, + // todo: single journey ref? + earlierThan: model.earlierRef + }) + for (let j of earlier) { + t.ok(new Date(j.departure) < earliestDep) + } + + const later = yield client.journeys(salzburgHbf, wienWestbahnhof, { + results: 3, + // todo: single journey ref? + laterThan: model.laterRef + }) + for (let j of later) { + t.ok(new Date(j.departure) > latestDep) + } + + t.end() +})) + test('leg details for Wien Westbahnhof to München Hbf', co(function* (t) { - const wienWestbahnhof = '1291501' - const muenchenHbf = '8000261' const journeys = yield client.journeys(wienWestbahnhof, muenchenHbf, { results: 1, when }) @@ -301,7 +349,6 @@ test('leg details for Wien Westbahnhof to München Hbf', co(function* (t) { })) test('departures at Salzburg Hbf', co(function* (t) { - const salzburgHbf = '8100002' const deps = yield client.departures(salzburgHbf, { duration: 5, when }) @@ -365,7 +412,6 @@ test('locations named Salzburg', co(function* (t) { })) test('location', co(function* (t) { - const grazHbf = '8100173' const loc = yield client.location(grazHbf) assertValidStation(t, loc) diff --git a/test/vbb.js b/test/vbb.js index 1205b344..f2b87c22 100644 --- a/test/vbb.js +++ b/test/vbb.js @@ -166,6 +166,56 @@ test('journeys – fails with no product', co(function* (t) { } })) +test('earlier/later journeys', co(function* (t) { + const model = yield client.journeys(spichernstr, bismarckstr, { + results: 3, when + }) + + t.equal(typeof model.earlierRef, 'string') + t.ok(model.earlierRef) + t.equal(typeof model.laterRef, 'string') + t.ok(model.laterRef) + + // when and earlierThan/laterThan should be mutually exclusive + t.throws(() => { + client.journeys(spichernstr, bismarckstr, { + when, earlierThan: model.earlierRef + }) + }) + t.throws(() => { + client.journeys(spichernstr, bismarckstr, { + when, laterThan: model.laterRef + }) + }) + + let earliestDep = Infinity, latestDep = -Infinity + for (let j of model) { + const dep = +new Date(j.departure) + if (dep < earliestDep) earliestDep = dep + else if (dep > latestDep) latestDep = dep + } + + const earlier = yield client.journeys(spichernstr, bismarckstr, { + results: 3, + // todo: single journey ref? + earlierThan: model.earlierRef + }) + for (let j of earlier) { + t.ok(new Date(j.departure) < earliestDep) + } + + const later = yield client.journeys(spichernstr, bismarckstr, { + results: 3, + // todo: single journey ref? + laterThan: model.laterRef + }) + for (let j of later) { + t.ok(new Date(j.departure) > latestDep) + } + + t.end() +})) + test('journey leg details', co(function* (t) { const journeys = yield client.journeys(spichernstr, amrumerStr, { results: 1, when