mirror of
				https://github.com/public-transport/db-vendo-client.git
				synced 2025-11-04 10:06:32 +02:00 
			
		
		
		
	parseProductsBitmask via profile, pass ctx into parse fns 💥
This commit is contained in:
		
							parent
							
								
									29d7bd4299
								
							
						
					
					
						commit
						fb7a5653e3
					
				
					 30 changed files with 501 additions and 487 deletions
				
			
		
							
								
								
									
										4
									
								
								index.js
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								index.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -7,7 +7,6 @@ const sortBy = require('lodash/sortBy')
 | 
			
		|||
const pRetry = require('p-retry')
 | 
			
		||||
 | 
			
		||||
const defaultProfile = require('./lib/default-profile')
 | 
			
		||||
const createParseBitmask = require('./parse/products-bitmask')
 | 
			
		||||
const createFormatProductsFilter = require('./format/products-filter')
 | 
			
		||||
const validateProfile = require('./lib/validate-profile')
 | 
			
		||||
const _request = require('./lib/request')
 | 
			
		||||
| 
						 | 
				
			
			@ -32,9 +31,6 @@ const defaults = {
 | 
			
		|||
 | 
			
		||||
const createClient = (profile, userAgent, opt = {}) => {
 | 
			
		||||
	profile = Object.assign({}, defaultProfile, profile)
 | 
			
		||||
	if (!profile.parseProducts) {
 | 
			
		||||
		profile.parseProducts = createParseBitmask(profile)
 | 
			
		||||
	}
 | 
			
		||||
	if (!profile.formatProductsFilter) {
 | 
			
		||||
		profile.formatProductsFilter = createFormatProductsFilter(profile)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
const parseDateTime = require('../parse/date-time')
 | 
			
		||||
const parsePlatform = require('../parse/platform')
 | 
			
		||||
const parseProductsBitmask = require('../parse/products-bitmask')
 | 
			
		||||
const parseIcon = require('../parse/icon')
 | 
			
		||||
const parseWhen = require('../parse/when')
 | 
			
		||||
const parseDeparture = require('../parse/departure')
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +45,7 @@ const defaultProfile = {
 | 
			
		|||
 | 
			
		||||
	parseDateTime,
 | 
			
		||||
	parsePlatform,
 | 
			
		||||
	parseProductsBitmask,
 | 
			
		||||
	parseIcon,
 | 
			
		||||
	parseWhen,
 | 
			
		||||
	parseDeparture,
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +53,7 @@ const defaultProfile = {
 | 
			
		|||
	parseJourneyLeg,
 | 
			
		||||
	parseJourney,
 | 
			
		||||
	parseLine,
 | 
			
		||||
	parseStationName: id,
 | 
			
		||||
	parseStationName: (_, name) => name,
 | 
			
		||||
	parseLocation,
 | 
			
		||||
	parseCommon,
 | 
			
		||||
	parsePolyline,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,22 +10,26 @@ const DEPARTURE = 'd'
 | 
			
		|||
// todo: what is d.jny.dirFlg?
 | 
			
		||||
// todo: d.stbStop.dProgType/d.stbStop.aProgType
 | 
			
		||||
 | 
			
		||||
const createParseArrOrDep = (profile, opt, data, prefix) => {
 | 
			
		||||
const createParseArrOrDep = (prefix) => {
 | 
			
		||||
	if (prefix !== ARRIVAL && prefix !== DEPARTURE) throw new Error('invalid prefix')
 | 
			
		||||
 | 
			
		||||
	const parseArrOrDep = (d) => {
 | 
			
		||||
	const parseArrOrDep = (ctx, d) => { // d = raw arrival/departure
 | 
			
		||||
		const {profile, opt} = ctx
 | 
			
		||||
 | 
			
		||||
		const tPlanned = d.stbStop[prefix + 'TimeS']
 | 
			
		||||
		const tPrognosed = d.stbStop[prefix + 'TimeR']
 | 
			
		||||
		const tzOffset = d.stbStop[prefix + 'TZOffset'] || null
 | 
			
		||||
		const cancelled = !!d.stbStop[prefix + 'Cncl']
 | 
			
		||||
		const plPlanned = d.stbStop[prefix + 'PlatfS']
 | 
			
		||||
		const plPrognosed = d.stbStop[prefix + 'PlatfR']
 | 
			
		||||
 | 
			
		||||
		const res = {
 | 
			
		||||
			tripId: d.jid,
 | 
			
		||||
			stop: d.stbStop.location || null,
 | 
			
		||||
			...parseWhen(profile, d.date, tPlanned, tPrognosed, tzOffset, cancelled),
 | 
			
		||||
			...parsePlatform(profile, d.stbStop[prefix + 'PlatfS'], d.stbStop[prefix + 'PlatfR'], cancelled),
 | 
			
		||||
			...profile.parseWhen(ctx, d.date, tPlanned, tPrognosed, tzOffset, cancelled),
 | 
			
		||||
			...profile.parsePlatform(ctx, plPlanned, plPrognosed, cancelled),
 | 
			
		||||
			// todo: for arrivals, this is the *origin*, not the *direction*
 | 
			
		||||
			direction: prefix === DEPARTURE && d.dirTxt && profile.parseStationName(d.dirTxt) || null,
 | 
			
		||||
			direction: prefix === DEPARTURE && d.dirTxt && profile.parseStationName(ctx, d.dirTxt) || null,
 | 
			
		||||
			line: d.line || null,
 | 
			
		||||
			remarks: []
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -43,9 +47,10 @@ const createParseArrOrDep = (profile, opt, data, prefix) => {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
   		if (opt.stopovers && Array.isArray(d.stopL)) {
 | 
			
		||||
  			const parse = profile.parseStopover(profile, opt, data, d.date)
 | 
			
		||||
  			// Filter stations the train passes without stopping, as this doesn't comply with FPTF (yet).
 | 
			
		||||
  			const stopovers = d.stopL.map(parse).filter(st => !st.passBy)
 | 
			
		||||
  			const stopovers = d.stopL
 | 
			
		||||
  			.map(st => profile.parseStopover(ctx, st, d.date))
 | 
			
		||||
  			.filter(st => !st.passBy)
 | 
			
		||||
  			if (prefix === ARRIVAL) res.previousStopovers = stopovers
 | 
			
		||||
			else if (prefix === DEPARTURE) res.nextStopovers = stopovers
 | 
			
		||||
  		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,6 @@
 | 
			
		|||
const createParseArrOrDep = require('./arrival-or-departure')
 | 
			
		||||
 | 
			
		||||
const ARRIVAL = 'a'
 | 
			
		||||
const createParseArrival = (profile, opt, data) => {
 | 
			
		||||
	return createParseArrOrDep(profile, opt, data, ARRIVAL)
 | 
			
		||||
}
 | 
			
		||||
const parseArrival = createParseArrOrDep(ARRIVAL)
 | 
			
		||||
 | 
			
		||||
module.exports = createParseArrival
 | 
			
		||||
module.exports = parseArrival
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,102 +3,103 @@
 | 
			
		|||
const get = require('lodash/get')
 | 
			
		||||
const findInTree = require('../lib/find-in-tree')
 | 
			
		||||
 | 
			
		||||
const parseCommonData = (profile, opt, res) => {
 | 
			
		||||
const parseCommonData = (_ctx) => {
 | 
			
		||||
	const {profile, opt, res} = _ctx
 | 
			
		||||
	const c = res.common || {}
 | 
			
		||||
 | 
			
		||||
	res.operators = []
 | 
			
		||||
	const common = {}
 | 
			
		||||
	const ctx = {..._ctx, common}
 | 
			
		||||
 | 
			
		||||
	common.operators = []
 | 
			
		||||
	if (Array.isArray(c.opL)) {
 | 
			
		||||
		res.operators = c.opL.map(op => profile.parseOperator(profile, op))
 | 
			
		||||
		common.operators = c.opL.map(op => profile.parseOperator(ctx, op))
 | 
			
		||||
		findInTree(res, '**.oprX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.operator = res.operators[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.operator = common.operators[idx]
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.icons = []
 | 
			
		||||
	common.icons = []
 | 
			
		||||
	if (Array.isArray(c.icoL)) {
 | 
			
		||||
		res.icons = c.icoL.map(icon => profile.parseIcon(profile, icon))
 | 
			
		||||
		common.icons = c.icoL.map(icon => profile.parseIcon(ctx, icon))
 | 
			
		||||
		findInTree(res, '**.icoX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.icon = res.icons[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.icon = common.icons[idx]
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.lines = []
 | 
			
		||||
	common.lines = []
 | 
			
		||||
	if (Array.isArray(c.prodL)) {
 | 
			
		||||
		const parse = profile.parseLine(profile, opt, res)
 | 
			
		||||
		res.lines = c.prodL.map(parse)
 | 
			
		||||
		common.lines = c.prodL.map(l => profile.parseLine(ctx, l))
 | 
			
		||||
 | 
			
		||||
		findInTree(res, '**.prodX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.line = res.lines[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.line = common.lines[idx]
 | 
			
		||||
		})
 | 
			
		||||
		findInTree(res, '**.pRefL', (idxs, parent) => {
 | 
			
		||||
			parent.lines = idxs.filter(idx => !!res.lines[idx]).map(idx => res.lines[idx])
 | 
			
		||||
			parent.lines = idxs.filter(idx => !!common.lines[idx]).map(idx => common.lines[idx])
 | 
			
		||||
		})
 | 
			
		||||
		// todo
 | 
			
		||||
		// **.dep.dProdX: departureLine -> res.lines[idx]
 | 
			
		||||
		// **.arr.aProdX: arrivalLine -> res.lines[idx]
 | 
			
		||||
		// **.dep.dProdX: departureLine -> common.lines[idx]
 | 
			
		||||
		// **.arr.aProdX: arrivalLine -> common.lines[idx]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.locations = []
 | 
			
		||||
	common.locations = []
 | 
			
		||||
	if (Array.isArray(c.locL)) {
 | 
			
		||||
		const parse = loc => profile.parseLocation(profile, opt, res, loc)
 | 
			
		||||
		res.locations = c.locL.map(parse)
 | 
			
		||||
		common.locations = c.locL.map(loc => profile.parseLocation(ctx, loc))
 | 
			
		||||
 | 
			
		||||
		for (let i = 0; i < res.locations.length; i++) {
 | 
			
		||||
		for (let i = 0; i < common.locations.length; i++) {
 | 
			
		||||
			const raw = c.locL[i]
 | 
			
		||||
			const loc = res.locations[i]
 | 
			
		||||
			const loc = common.locations[i]
 | 
			
		||||
			if ('number' === typeof raw.mMastLocX) {
 | 
			
		||||
				loc.station = Object.assign({}, res.locations[raw.mMastLocX])
 | 
			
		||||
				loc.station = Object.assign({}, common.locations[raw.mMastLocX])
 | 
			
		||||
				loc.station.type = 'station'
 | 
			
		||||
			} else if (raw.isMainMast) loc.type = 'station'
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// todo: correct props?
 | 
			
		||||
		findInTree(res, '**.locX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.location = res.locations[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.location = common.locations[idx]
 | 
			
		||||
		})
 | 
			
		||||
		findInTree(res, '**.ani.fLocX', (idxs, parent) => {
 | 
			
		||||
			parent.fromLocations = idxs.map(idx => res.locations[idx])
 | 
			
		||||
			parent.fromLocations = idxs.map(idx => common.locations[idx])
 | 
			
		||||
		})
 | 
			
		||||
		findInTree(res, '**.ani.tLocX', (idxs, parent) => {
 | 
			
		||||
			parent.toLocations = idxs.map(idx => res.locations[idx])
 | 
			
		||||
			parent.toLocations = idxs.map(idx => common.locations[idx])
 | 
			
		||||
		})
 | 
			
		||||
		findInTree(res, '**.fLocX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.fromLocation = res.locations[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.fromLocation = common.locations[idx]
 | 
			
		||||
		})
 | 
			
		||||
		findInTree(res, '**.tLocX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.toLocation = res.locations[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.toLocation = common.locations[idx]
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.hints = []
 | 
			
		||||
	common.hints = []
 | 
			
		||||
	if (opt.remarks && Array.isArray(c.remL)) {
 | 
			
		||||
		res.hints = c.remL.map(hint => profile.parseHint(profile, hint, {...c, ...res}))
 | 
			
		||||
		common.hints = c.remL.map(hint => profile.parseHint(ctx, hint))
 | 
			
		||||
		findInTree(res, '**.remX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.hint = res.hints[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.hint = common.hints[idx]
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	res.warnings = []
 | 
			
		||||
	common.warnings = []
 | 
			
		||||
	if (opt.remarks && Array.isArray(c.himL)) {
 | 
			
		||||
		res.warnings = c.himL.map(w => profile.parseWarning(profile, w, {...c, ...res}))
 | 
			
		||||
		common.warnings = c.himL.map(w => profile.parseWarning(ctx, w))
 | 
			
		||||
		findInTree(res, '**.himX', (idx, parent) => {
 | 
			
		||||
			if ('number' === typeof idx) parent.warning = res.warnings[idx]
 | 
			
		||||
			if ('number' === typeof idx) parent.warning = common.warnings[idx]
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.polylines = []
 | 
			
		||||
	common.polylines = []
 | 
			
		||||
	if (opt.polylines && Array.isArray(c.polyL)) {
 | 
			
		||||
		const parse = profile.parsePolyline(profile, opt, res)
 | 
			
		||||
		res.polylines = c.polyL.map(parse)
 | 
			
		||||
		common.polylines = c.polyL.map(p => profile.parsePolyline(ctx, p))
 | 
			
		||||
		// todo: **.ani.poly -> parsePolyline()
 | 
			
		||||
 | 
			
		||||
		findInTree(res, '**.polyG.polyXL', (idxs, _, path) => {
 | 
			
		||||
			const idx = idxs.find(idx => !!res.polylines[idx]) // find any given polyline
 | 
			
		||||
			const idx = idxs.find(idx => !!common.polylines[idx]) // find any given polyline
 | 
			
		||||
			const jny = get(res, path.slice(0, -2))
 | 
			
		||||
			jny.polyline = res.polylines[idx]
 | 
			
		||||
			jny.polyline = common.polylines[idx]
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
	return common
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = parseCommonData
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,8 +4,7 @@ const {DateTime, FixedOffsetZone, IANAZone} = require('luxon')
 | 
			
		|||
 | 
			
		||||
const timezones = new WeakMap()
 | 
			
		||||
 | 
			
		||||
// todo: change to `(profile) => (date, time) => {}`
 | 
			
		||||
const parseDateTime = (profile, date, time, tzOffset = null, timestamp = false) => {
 | 
			
		||||
const parseDateTime = ({profile}, date, time, tzOffset = null, timestamp = false) => {
 | 
			
		||||
	const pDate = [date.substr(-8, 4), date.substr(-4, 2), date.substr(-2, 2)]
 | 
			
		||||
	if (!pDate[0] || !pDate[1] || !pDate[2]) {
 | 
			
		||||
		throw new Error('invalid date format: ' + date)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,6 @@
 | 
			
		|||
const createParseArrOrDep = require('./arrival-or-departure')
 | 
			
		||||
 | 
			
		||||
const DEPARTURE = 'd'
 | 
			
		||||
const createParseDeparture = (profile, opt, data) => {
 | 
			
		||||
	return createParseArrOrDep(profile, opt, data, DEPARTURE)
 | 
			
		||||
}
 | 
			
		||||
const parseDeparture = createParseArrOrDep(DEPARTURE)
 | 
			
		||||
 | 
			
		||||
module.exports = createParseDeparture
 | 
			
		||||
module.exports = parseDeparture
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ const codesByIcon = Object.assign(Object.create(null), {
 | 
			
		|||
	cancel: 'cancelled'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// todo: is passing in profile necessary?
 | 
			
		||||
// todo: pass in tag list from hint reference, e.g.:
 | 
			
		||||
// "tagL": [
 | 
			
		||||
// 	"RES_JNY_H3" // H3 = level 3 heading? shown on overview
 | 
			
		||||
| 
						 | 
				
			
			@ -13,7 +12,7 @@ const codesByIcon = Object.assign(Object.create(null), {
 | 
			
		|||
// 	"RES_JNY_DTL" // only shown in journey detail
 | 
			
		||||
// ]
 | 
			
		||||
// todo: https://github.com/public-transport/hafas-client/issues/5
 | 
			
		||||
const parseHint = (profile, h, _) => {
 | 
			
		||||
const parseHint = (ctx, h) => {
 | 
			
		||||
	// todo: C
 | 
			
		||||
 | 
			
		||||
	const text = h.txtN && h.txtN.trim() || ''
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
const parseIcon = (profile, i) => {
 | 
			
		||||
const parseIcon = (ctx, i) => {
 | 
			
		||||
	const res = {
 | 
			
		||||
		type: i.res || null,
 | 
			
		||||
		title: i.text || i.txt || i.txtS || null
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,120 +32,116 @@ const applyRemarks = (leg, refs) => {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const createParseJourneyLeg = (profile, opt, data) => {
 | 
			
		||||
	// todo: pt.status, pt.isPartCncl
 | 
			
		||||
	// todo: pt.isRchbl, pt.chRatingRT, pt.chgDurR, pt.minChg
 | 
			
		||||
	// todo: pt.dep.dProgType, pt.arr.dProgType
 | 
			
		||||
	// todo: what is pt.jny.dirFlg?
 | 
			
		||||
	// todo: what is pt.recState?
 | 
			
		||||
	// todo: what is `sty: 'UNDEF'`?
 | 
			
		||||
	// todo: pt.prodL
 | 
			
		||||
	// todo: pt.parJnyL (list of coupled trains)
 | 
			
		||||
// todo: pt.status, pt.isPartCncl
 | 
			
		||||
// todo: pt.isRchbl, pt.chRatingRT, pt.chgDurR, pt.minChg
 | 
			
		||||
// todo: pt.dep.dProgType, pt.arr.dProgType
 | 
			
		||||
// todo: what is pt.jny.dirFlg?
 | 
			
		||||
// todo: what is pt.recState?
 | 
			
		||||
// todo: what is `sty: 'UNDEF'`?
 | 
			
		||||
// todo: pt.prodL
 | 
			
		||||
// todo: pt.parJnyL (list of coupled trains)
 | 
			
		||||
// todo: pt.planrtTS
 | 
			
		||||
 | 
			
		||||
	// j = journey, pt = part
 | 
			
		||||
	// todo: pt.planrtTS
 | 
			
		||||
	const parseJourneyLeg = (j, pt, parseStopovers = true) => {
 | 
			
		||||
		const res = {
 | 
			
		||||
			origin: clone(pt.dep.location) || null,
 | 
			
		||||
			destination: clone(pt.arr.location)
 | 
			
		||||
		}
 | 
			
		||||
const parseJourneyLeg = (ctx, pt, date) => { // pt = raw leg
 | 
			
		||||
	const {profile, opt} = ctx
 | 
			
		||||
 | 
			
		||||
		const arr = parseWhen(profile, j.date, pt.arr.aTimeS, pt.arr.aTimeR, pt.arr.aTZOffset, pt.arr.aCncl)
 | 
			
		||||
		res.arrival = arr.when
 | 
			
		||||
		res.plannedArrival = arr.plannedWhen
 | 
			
		||||
		res.arrivalDelay = arr.delay
 | 
			
		||||
		if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen
 | 
			
		||||
 | 
			
		||||
		const dep = parseWhen(profile, j.date, pt.dep.dTimeS, pt.dep.dTimeR, pt.dep.dTZOffset, pt.dep.dCncl)
 | 
			
		||||
		res.departure = dep.when
 | 
			
		||||
		res.plannedDeparture = dep.plannedWhen
 | 
			
		||||
		res.departureDelay = dep.delay
 | 
			
		||||
		if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen
 | 
			
		||||
 | 
			
		||||
		if (pt.jny) {
 | 
			
		||||
			res.reachable = !!pt.jny.isRchbl
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (pt.jny && pt.jny.polyline) {
 | 
			
		||||
			res.polyline = pt.jny.polyline || null
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (pt.type === 'WALK' || pt.type === 'TRSF') {
 | 
			
		||||
			res.public = true
 | 
			
		||||
			res.walking = true
 | 
			
		||||
			res.distance = pt.gis && pt.gis.dist || null
 | 
			
		||||
			if (pt.type === 'TRSF') res.transfer = true
 | 
			
		||||
 | 
			
		||||
			// https://gist.github.com/derhuerst/426d4b95aeae701843b1e9c23105b8d4#file-tripsearch-2018-12-05-http-L4207-L4229
 | 
			
		||||
			if (opt.remarks && Array.isArray(pt.gis.msgL)) {
 | 
			
		||||
				applyRemarks(res, pt.gis.msgL)
 | 
			
		||||
			}
 | 
			
		||||
		} else if (pt.type === 'JNY') {
 | 
			
		||||
			// todo: pull `public` value from `profile.products`
 | 
			
		||||
			res.tripId = pt.jny.jid
 | 
			
		||||
			res.line = pt.jny.line || null
 | 
			
		||||
			res.direction = pt.jny.dirTxt && profile.parseStationName(pt.jny.dirTxt) || null
 | 
			
		||||
 | 
			
		||||
			const arrPl = parsePlatform(profile, pt.arr.aPlatfS, pt.arr.aPlatfR, pt.arr.aCncl)
 | 
			
		||||
			res.arrivalPlatform = arrPl.platform
 | 
			
		||||
			res.plannedArrivalPlatform = arrPl.plannedPlatform
 | 
			
		||||
			if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform
 | 
			
		||||
 | 
			
		||||
			const depPl = parsePlatform(profile, pt.dep.dPlatfS, pt.dep.dPlatfR, pt.dep.dCncl)
 | 
			
		||||
			res.departurePlatform = depPl.platform
 | 
			
		||||
			res.plannedDeparturePlatform = depPl.plannedPlatform
 | 
			
		||||
			if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform
 | 
			
		||||
 | 
			
		||||
			if (parseStopovers && pt.jny.stopL) {
 | 
			
		||||
				const parse = profile.parseStopover(profile, opt, data, j.date)
 | 
			
		||||
				const stopL = pt.jny.stopL
 | 
			
		||||
				res.stopovers = stopL.map(parse)
 | 
			
		||||
 | 
			
		||||
				if (opt.remarks && Array.isArray(pt.jny.msgL)) {
 | 
			
		||||
					// todo: apply leg-wide remarks if `parseStopovers` is false
 | 
			
		||||
					applyRemarks(res, pt.jny.msgL)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// filter stations the train passes without stopping, as this doesn't comply with fptf (yet)
 | 
			
		||||
				res.stopovers = res.stopovers.filter((x) => !x.passBy)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const freq = pt.jny.freq || {}
 | 
			
		||||
			// todo: expose `res.cycle` even if only one field exists (breaking)
 | 
			
		||||
			if (freq.minC && freq.maxC) {
 | 
			
		||||
				res.cycle = {
 | 
			
		||||
					min: freq.minC * 60,
 | 
			
		||||
					max: freq.maxC * 60
 | 
			
		||||
				}
 | 
			
		||||
				// nr of connections in this frequency, from now on
 | 
			
		||||
				if (freq.numC) res.cycle.nr = freq.numC
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (freq.jnyL) {
 | 
			
		||||
				const parseAlternative = (a) => {
 | 
			
		||||
					// todo: parse this just like a `leg` (breaking)
 | 
			
		||||
					// todo: parse `a.stopL`, `a.ctxRecon`, `a.msgL`
 | 
			
		||||
					const st0 = a.stopL[0] || {}
 | 
			
		||||
					return {
 | 
			
		||||
						tripId: a.jid,
 | 
			
		||||
						line: a.line || null,
 | 
			
		||||
						direction: a.dirTxt || null,
 | 
			
		||||
						...parseWhen(profile, j.date, st0.dTimeS, st0.dTimeR, st0.dTZOffset, st0.dCncl)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				res.alternatives = freq.jnyL.map(parseAlternative)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (pt.arr.aCncl || pt.dep.dCncl) {
 | 
			
		||||
			res.cancelled = true
 | 
			
		||||
			Object.defineProperty(res, 'canceled', {value: true})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return res
 | 
			
		||||
	const res = {
 | 
			
		||||
		origin: clone(pt.dep.location) || null,
 | 
			
		||||
		destination: clone(pt.arr.location)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return parseJourneyLeg
 | 
			
		||||
	const arr = profile.parseWhen(ctx, date, pt.arr.aTimeS, pt.arr.aTimeR, pt.arr.aTZOffset, pt.arr.aCncl)
 | 
			
		||||
	res.arrival = arr.when
 | 
			
		||||
	res.plannedArrival = arr.plannedWhen
 | 
			
		||||
	res.arrivalDelay = arr.delay
 | 
			
		||||
	if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen
 | 
			
		||||
 | 
			
		||||
	const dep = profile.parseWhen(ctx, date, pt.dep.dTimeS, pt.dep.dTimeR, pt.dep.dTZOffset, pt.dep.dCncl)
 | 
			
		||||
	res.departure = dep.when
 | 
			
		||||
	res.plannedDeparture = dep.plannedWhen
 | 
			
		||||
	res.departureDelay = dep.delay
 | 
			
		||||
	if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen
 | 
			
		||||
 | 
			
		||||
	if (pt.jny) {
 | 
			
		||||
		res.reachable = !!pt.jny.isRchbl
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (pt.jny && pt.jny.polyline) {
 | 
			
		||||
		res.polyline = pt.jny.polyline || null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (pt.type === 'WALK' || pt.type === 'TRSF') {
 | 
			
		||||
		res.public = true
 | 
			
		||||
		res.walking = true
 | 
			
		||||
		res.distance = pt.gis && pt.gis.dist || null
 | 
			
		||||
		if (pt.type === 'TRSF') res.transfer = true
 | 
			
		||||
 | 
			
		||||
		// https://gist.github.com/derhuerst/426d4b95aeae701843b1e9c23105b8d4#file-tripsearch-2018-12-05-http-L4207-L4229
 | 
			
		||||
		if (opt.remarks && Array.isArray(pt.gis.msgL)) {
 | 
			
		||||
			applyRemarks(res, pt.gis.msgL)
 | 
			
		||||
		}
 | 
			
		||||
	} else if (pt.type === 'JNY') {
 | 
			
		||||
		// todo: pull `public` value from `profile.products`
 | 
			
		||||
		res.tripId = pt.jny.jid
 | 
			
		||||
		res.line = pt.jny.line || null
 | 
			
		||||
		res.direction = pt.jny.dirTxt && profile.parseStationName(ctx, pt.jny.dirTxt) || null
 | 
			
		||||
 | 
			
		||||
		const arrPl = profile.parsePlatform(ctx, pt.arr.aPlatfS, pt.arr.aPlatfR, pt.arr.aCncl)
 | 
			
		||||
		res.arrivalPlatform = arrPl.platform
 | 
			
		||||
		res.plannedArrivalPlatform = arrPl.plannedPlatform
 | 
			
		||||
		if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform
 | 
			
		||||
 | 
			
		||||
		const depPl = profile.parsePlatform(ctx, pt.dep.dPlatfS, pt.dep.dPlatfR, pt.dep.dCncl)
 | 
			
		||||
		res.departurePlatform = depPl.platform
 | 
			
		||||
		res.plannedDeparturePlatform = depPl.plannedPlatform
 | 
			
		||||
		if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform
 | 
			
		||||
 | 
			
		||||
		if (opt.stopovers && pt.jny.stopL) {
 | 
			
		||||
			const stopL = pt.jny.stopL
 | 
			
		||||
			res.stopovers = stopL.map(s => profile.parseStopover(ctx, s, date))
 | 
			
		||||
 | 
			
		||||
			if (opt.remarks && Array.isArray(pt.jny.msgL)) {
 | 
			
		||||
				// todo: apply leg-wide remarks if `opt.stopovers` is false
 | 
			
		||||
				applyRemarks(res, pt.jny.msgL)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// filter stations the train passes without stopping, as this doesn't comply with fptf (yet)
 | 
			
		||||
			res.stopovers = res.stopovers.filter((x) => !x.passBy)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const freq = pt.jny.freq || {}
 | 
			
		||||
		// todo: expose `res.cycle` even if only one field exists (breaking)
 | 
			
		||||
		if (freq.minC && freq.maxC) {
 | 
			
		||||
			res.cycle = {
 | 
			
		||||
				min: freq.minC * 60,
 | 
			
		||||
				max: freq.maxC * 60
 | 
			
		||||
			}
 | 
			
		||||
			// nr of connections in this frequency, from now on
 | 
			
		||||
			if (freq.numC) res.cycle.nr = freq.numC
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (freq.jnyL) {
 | 
			
		||||
			const parseAlternative = (a) => {
 | 
			
		||||
				// todo: parse this just like a `leg` (breaking)
 | 
			
		||||
				// todo: parse `a.stopL`, `a.ctxRecon`, `a.msgL`
 | 
			
		||||
				const st0 = a.stopL[0] || {}
 | 
			
		||||
				return {
 | 
			
		||||
					tripId: a.jid,
 | 
			
		||||
					line: a.line || null,
 | 
			
		||||
					direction: a.dirTxt || null,
 | 
			
		||||
					...profile.parseWhen(ctx, date, st0.dTimeS, st0.dTimeR, st0.dTZOffset, st0.dCncl)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			res.alternatives = freq.jnyL.map(parseAlternative)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (pt.arr.aCncl || pt.dep.dCncl) {
 | 
			
		||||
		res.cancelled = true
 | 
			
		||||
		Object.defineProperty(res, 'canceled', {value: true})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = createParseJourneyLeg
 | 
			
		||||
module.exports = parseJourneyLeg
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,47 +22,44 @@ const parseScheduledDays = (sDaysB, year, profile) => {
 | 
			
		|||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const createParseJourney = (profile, opt, data) => {
 | 
			
		||||
	const parseLeg = profile.parseJourneyLeg(profile, opt, data)
 | 
			
		||||
	// todo: c.conSubscr
 | 
			
		||||
	// todo: c.trfRes x vbb-parse-ticket
 | 
			
		||||
	// todo: c.sotRating, c.isSotCon, c.sotCtxt
 | 
			
		||||
	// todo: c.showARSLink
 | 
			
		||||
	// todo: c.useableTime
 | 
			
		||||
	// todo: c.cksum
 | 
			
		||||
	// todo: c.isNotRdbl
 | 
			
		||||
	// todo: c.badSecRefX
 | 
			
		||||
	// todo: c.bfATS, c.bfIOSTS
 | 
			
		||||
	const parseJourney = (j) => {
 | 
			
		||||
		const legs = j.secL.map(leg => parseLeg(j, leg))
 | 
			
		||||
		const res = {
 | 
			
		||||
			type: 'journey',
 | 
			
		||||
			legs,
 | 
			
		||||
			refreshToken: j.ctxRecon || null
 | 
			
		||||
		}
 | 
			
		||||
// todo: c.conSubscr
 | 
			
		||||
// todo: c.trfRes x vbb-parse-ticket
 | 
			
		||||
// todo: c.sotRating, c.isSotCon, c.sotCtxt
 | 
			
		||||
// todo: c.showARSLink
 | 
			
		||||
// todo: c.useableTime
 | 
			
		||||
// todo: c.cksum
 | 
			
		||||
// todo: c.isNotRdbl
 | 
			
		||||
// todo: c.badSecRefX
 | 
			
		||||
// todo: c.bfATS, c.bfIOSTS
 | 
			
		||||
const parseJourney = (ctx, j) => { // j = raw jouney
 | 
			
		||||
	const {profile, opt} = ctx
 | 
			
		||||
 | 
			
		||||
		const freq = j.freq || {}
 | 
			
		||||
		if (freq.minC || freq.maxC) {
 | 
			
		||||
			res.cycle = {}
 | 
			
		||||
			if (freq.minC) res.cycle.min = freq.minC * 60
 | 
			
		||||
			if (freq.maxC) res.cycle.max = freq.maxC * 60
 | 
			
		||||
			// nr of connections in this frequency, from now on
 | 
			
		||||
			if (freq.numC) res.cycle.nr = freq.numC
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (opt.remarks && Array.isArray(j.msgL)) {
 | 
			
		||||
			res.remarks = findRemarks(j.msgL).map(([remark]) => remark)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (opt.scheduledDays) {
 | 
			
		||||
			const year = parseInt(j.date.slice(0, 4))
 | 
			
		||||
			res.scheduledDays = parseScheduledDays(j.sDays.sDaysB, year, profile)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return res
 | 
			
		||||
	const legs = j.secL.map(l => profile.parseJourneyLeg(ctx, l, j.date))
 | 
			
		||||
	const res = {
 | 
			
		||||
		type: 'journey',
 | 
			
		||||
		legs,
 | 
			
		||||
		refreshToken: j.ctxRecon || null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return parseJourney
 | 
			
		||||
	const freq = j.freq || {}
 | 
			
		||||
	if (freq.minC || freq.maxC) {
 | 
			
		||||
		res.cycle = {}
 | 
			
		||||
		if (freq.minC) res.cycle.min = freq.minC * 60
 | 
			
		||||
		if (freq.maxC) res.cycle.max = freq.maxC * 60
 | 
			
		||||
		// nr of connections in this frequency, from now on
 | 
			
		||||
		if (freq.numC) res.cycle.nr = freq.numC
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (opt.remarks && Array.isArray(j.msgL)) {
 | 
			
		||||
		res.remarks = findRemarks(j.msgL).map(([remark]) => remark)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (opt.scheduledDays) {
 | 
			
		||||
		const year = parseInt(j.date.slice(0, 4))
 | 
			
		||||
		res.scheduledDays = parseScheduledDays(j.sDays.sDaysB, year, profile)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = createParseJourney
 | 
			
		||||
module.exports = parseJourney
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,45 +2,42 @@
 | 
			
		|||
 | 
			
		||||
const slugg = require('slugg')
 | 
			
		||||
 | 
			
		||||
const createParseLine = (profile, opt, _) => {
 | 
			
		||||
	const byBitmask = []
 | 
			
		||||
	for (let product of profile.products) {
 | 
			
		||||
		for (let bitmask of product.bitmasks) {
 | 
			
		||||
			byBitmask[bitmask] = product
 | 
			
		||||
const parseLine = ({profile}, p) => {
 | 
			
		||||
	if (!p) return null // todo: handle this upstream
 | 
			
		||||
	const name = p.line || p.addName || p.name || null // wtf
 | 
			
		||||
	const res = {
 | 
			
		||||
		type: 'line',
 | 
			
		||||
		// This is terrible, but FPTF demands an ID. Let's pray for HAFAS.
 | 
			
		||||
		id: (
 | 
			
		||||
			p.prodCtx && p.prodCtx.lineId && slugg(p.prodCtx.lineId.trim())
 | 
			
		||||
			|| name && slugg(name.trim())
 | 
			
		||||
			|| null
 | 
			
		||||
		),
 | 
			
		||||
		// todo: what is p.prodCtx.matchId? use as `id`? expose it.
 | 
			
		||||
		fahrtNr: p.prodCtx && p.prodCtx.num || null,
 | 
			
		||||
		name,
 | 
			
		||||
		public: true
 | 
			
		||||
	}
 | 
			
		||||
	// todo: what is p.number?
 | 
			
		||||
	// todo: what is p.prodCtx.catCode?
 | 
			
		||||
 | 
			
		||||
	if ('cls' in p) {
 | 
			
		||||
		// todo: use profile.products.find() for this
 | 
			
		||||
		const byBitmask = []
 | 
			
		||||
		for (let product of profile.products) {
 | 
			
		||||
			for (let bitmask of product.bitmasks) {
 | 
			
		||||
				byBitmask[bitmask] = product
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// todo: what if `p.cls` is the sum of two bitmasks?
 | 
			
		||||
		const product = byBitmask[parseInt(p.cls)]
 | 
			
		||||
		res.mode = product && product.mode || null
 | 
			
		||||
		res.product = product && product.id || null
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// todo: p.himIdL
 | 
			
		||||
	const parseLine = (p) => {
 | 
			
		||||
		if (!p) return null // todo: handle this upstream
 | 
			
		||||
		const name = p.line || p.addName || p.name || null // wtf
 | 
			
		||||
		const res = {
 | 
			
		||||
			type: 'line',
 | 
			
		||||
			// This is terrible, but FPTF demands an ID. Let's pray for HAFAS.
 | 
			
		||||
			id: (
 | 
			
		||||
				p.prodCtx && p.prodCtx.lineId && slugg(p.prodCtx.lineId.trim())
 | 
			
		||||
				|| name && slugg(name.trim())
 | 
			
		||||
				|| null
 | 
			
		||||
			),
 | 
			
		||||
			// todo: what is p.prodCtx.matchId? use as `id`? expose it.
 | 
			
		||||
			fahrtNr: p.prodCtx && p.prodCtx.num || null,
 | 
			
		||||
			name,
 | 
			
		||||
			public: true
 | 
			
		||||
		}
 | 
			
		||||
		// todo: what is p.number?
 | 
			
		||||
		// todo: what is p.prodCtx.catCode?
 | 
			
		||||
 | 
			
		||||
		if ('cls' in p) {
 | 
			
		||||
			// todo: what if `p.cls` is the sum of two bitmasks?
 | 
			
		||||
			const product = byBitmask[parseInt(p.cls)]
 | 
			
		||||
			res.mode = product && product.mode || null
 | 
			
		||||
			res.product = product && product.id || null
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (p.operator) res.operator = p.operator // todo: move up
 | 
			
		||||
		return res
 | 
			
		||||
	}
 | 
			
		||||
	return parseLine
 | 
			
		||||
	if (p.operator) res.operator = p.operator // todo: move up
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = createParseLine
 | 
			
		||||
module.exports = parseLine
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,9 @@ const ADDRESS = 'A'
 | 
			
		|||
const leadingZeros = /^0+/
 | 
			
		||||
 | 
			
		||||
// todo: what is s.rRefL?
 | 
			
		||||
const parseLocation = (profile, opt, _, l) => {
 | 
			
		||||
const parseLocation = (ctx, l) => {
 | 
			
		||||
	const {profile, opt} = ctx
 | 
			
		||||
 | 
			
		||||
	const lid = parse(l.lid, {delimiter: '@'})
 | 
			
		||||
	const res = {
 | 
			
		||||
		type: 'location',
 | 
			
		||||
| 
						 | 
				
			
			@ -25,11 +27,11 @@ const parseLocation = (profile, opt, _, l) => {
 | 
			
		|||
		const stop = {
 | 
			
		||||
			type: l.isMainMast ? 'station' : 'stop',
 | 
			
		||||
			id: res.id,
 | 
			
		||||
			name: l.name || id.O ? profile.parseStationName(l.name || id.O) : null,
 | 
			
		||||
			name: l.name || id.O ? profile.parseStationName(ctx, l.name || id.O) : null,
 | 
			
		||||
			location: 'number' === typeof res.latitude ? res : null // todo: remove `.id`
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ('pCls' in l) stop.products = profile.parseProducts(l.pCls)
 | 
			
		||||
		if ('pCls' in l) stop.products = profile.parseProductsBitmask(ctx, l.pCls)
 | 
			
		||||
		if ('meta' in l) stop.isMeta = !!l.meta
 | 
			
		||||
 | 
			
		||||
		if (opt.linesOfStops && Array.isArray(l.lines)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,53 +1,49 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
const createParseMovement = (profile, opt, data) => {
 | 
			
		||||
	// todo: what is m.dirGeo? maybe the speed?
 | 
			
		||||
	// todo: what is m.stopL?
 | 
			
		||||
	// todo: what is m.proc? wut?
 | 
			
		||||
	// todo: what is m.pos?
 | 
			
		||||
	// todo: what is m.ani.dirGeo[n]? maybe the speed?
 | 
			
		||||
	// todo: what is m.ani.proc[n]? wut?
 | 
			
		||||
	const parseMovement = (m) => {
 | 
			
		||||
		const pStopover = profile.parseStopover(profile, opt, data, m.date)
 | 
			
		||||
// todo: what is m.dirGeo? maybe the speed?
 | 
			
		||||
// todo: what is m.stopL?
 | 
			
		||||
// todo: what is m.proc? wut?
 | 
			
		||||
// todo: what is m.pos?
 | 
			
		||||
// todo: what is m.ani.dirGeo[n]? maybe the speed?
 | 
			
		||||
// todo: what is m.ani.proc[n]? wut?
 | 
			
		||||
const parseMovement = (ctx, m) => { // m = raw movement
 | 
			
		||||
	const {profile, opt} = ctx
 | 
			
		||||
 | 
			
		||||
		const res = {
 | 
			
		||||
			direction: m.dirTxt ? profile.parseStationName(m.dirTxt) : null,
 | 
			
		||||
			tripId: m.jid || null,
 | 
			
		||||
			line: m.line || null,
 | 
			
		||||
			location: m.pos ? {
 | 
			
		||||
				type: 'location',
 | 
			
		||||
				latitude: m.pos.y / 1000000,
 | 
			
		||||
				longitude: m.pos.x / 1000000
 | 
			
		||||
			} : null,
 | 
			
		||||
			// todo: stopL[0] is the first of the trip! -> filter out
 | 
			
		||||
			nextStopovers: m.stopL.map(pStopover),
 | 
			
		||||
			frames: []
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (m.ani) {
 | 
			
		||||
			if (Array.isArray(m.ani.mSec)) {
 | 
			
		||||
				for (let i = 0; i < m.ani.mSec.length; i++) {
 | 
			
		||||
					res.frames.push({
 | 
			
		||||
						origin: m.ani.fromLocations[i] || null,
 | 
			
		||||
						destination: m.ani.toLocations[i] || null,
 | 
			
		||||
						t: m.ani.mSec[i]
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (opt.polylines) {
 | 
			
		||||
				if (m.ani.poly) {
 | 
			
		||||
					const parse = profile.parsePolyline(profile, opt, data)
 | 
			
		||||
					res.polyline = parse(m.ani.poly)
 | 
			
		||||
				} else if (m.ani.polyline) {
 | 
			
		||||
					res.polyline = m.ani.polyline
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return res
 | 
			
		||||
	const res = {
 | 
			
		||||
		direction: m.dirTxt ? profile.parseStationName(ctx, m.dirTxt) : null,
 | 
			
		||||
		tripId: m.jid || null,
 | 
			
		||||
		line: m.line || null,
 | 
			
		||||
		location: m.pos ? {
 | 
			
		||||
			type: 'location',
 | 
			
		||||
			latitude: m.pos.y / 1000000,
 | 
			
		||||
			longitude: m.pos.x / 1000000
 | 
			
		||||
		} : null,
 | 
			
		||||
		// todo: stopL[0] is the first of the trip! -> filter out
 | 
			
		||||
		nextStopovers: m.stopL.map(s => profile.parseStopover(ctx, s, m.date)),
 | 
			
		||||
		frames: []
 | 
			
		||||
	}
 | 
			
		||||
	return parseMovement
 | 
			
		||||
 | 
			
		||||
	if (m.ani) {
 | 
			
		||||
		if (Array.isArray(m.ani.mSec)) {
 | 
			
		||||
			for (let i = 0; i < m.ani.mSec.length; i++) {
 | 
			
		||||
				res.frames.push({
 | 
			
		||||
					origin: m.ani.fromLocations[i] || null,
 | 
			
		||||
					destination: m.ani.toLocations[i] || null,
 | 
			
		||||
					t: m.ani.mSec[i]
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (opt.polylines) {
 | 
			
		||||
			if (m.ani.poly) {
 | 
			
		||||
				res.polyline = profile.parsePolyline(ctx, m.ani.poly)
 | 
			
		||||
			} else if (m.ani.polyline) {
 | 
			
		||||
				res.polyline = m.ani.polyline
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = createParseMovement
 | 
			
		||||
module.exports = parseMovement
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,9 +6,8 @@
 | 
			
		|||
// todo: what is s.wt?
 | 
			
		||||
// todo: what is s.dur?
 | 
			
		||||
 | 
			
		||||
// todo: [breaking] change to createParseNearby(profile, data) => (n) => nearby
 | 
			
		||||
const parseNearby = (profile, opt, data, n) => {
 | 
			
		||||
	const res = profile.parseLocation(profile, opt, data, n)
 | 
			
		||||
const parseNearby = (ctx, n) => { // n = raw nearby location
 | 
			
		||||
	const res = ctx.profile.parseLocation(ctx, n)
 | 
			
		||||
	res.distance = n.dist
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
 | 
			
		||||
const slugg = require('slugg')
 | 
			
		||||
 | 
			
		||||
const parseOperator = (profile, a) => {
 | 
			
		||||
const parseOperator = (ctx, a) => {
 | 
			
		||||
	const name = a.name && a.name.trim()
 | 
			
		||||
	if (!name) return null
 | 
			
		||||
	return {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
const parsePlatform = (profile, platfS, platfR, cncl = false) => {
 | 
			
		||||
const parsePlatform = (ctx, platfS, platfR, cncl = false) => {
 | 
			
		||||
	let planned = platfS || null
 | 
			
		||||
	let prognosed = platfR || null
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,50 +3,47 @@
 | 
			
		|||
const {toGeoJSON} = require('@mapbox/polyline')
 | 
			
		||||
const distance = require('gps-distance')
 | 
			
		||||
 | 
			
		||||
const createParsePolyline = (profile, opt, _) => {
 | 
			
		||||
	// todo: what is p.delta?
 | 
			
		||||
	// todo: what is p.type?
 | 
			
		||||
	// todo: what is p.crdEncS?
 | 
			
		||||
	// todo: what is p.crdEncF?
 | 
			
		||||
	const parsePolyline = (p) => {
 | 
			
		||||
		const shape = toGeoJSON(p.crdEncYX)
 | 
			
		||||
		if (shape.coordinates.length === 0) return null
 | 
			
		||||
// todo: what is p.delta?
 | 
			
		||||
// todo: what is p.type?
 | 
			
		||||
// todo: what is p.crdEncS?
 | 
			
		||||
// todo: what is p.crdEncF?
 | 
			
		||||
const parsePolyline = (ctx, p) => { // p = raw polyline
 | 
			
		||||
	const shape = toGeoJSON(p.crdEncYX)
 | 
			
		||||
	if (shape.coordinates.length === 0) return null
 | 
			
		||||
 | 
			
		||||
		const res = shape.coordinates.map(crd => ({
 | 
			
		||||
			type: 'Feature',
 | 
			
		||||
			properties: {},
 | 
			
		||||
			geometry: {
 | 
			
		||||
				type: 'Point',
 | 
			
		||||
				coordinates: crd
 | 
			
		||||
			}
 | 
			
		||||
		}))
 | 
			
		||||
	const res = shape.coordinates.map(crd => ({
 | 
			
		||||
		type: 'Feature',
 | 
			
		||||
		properties: {},
 | 
			
		||||
		geometry: {
 | 
			
		||||
			type: 'Point',
 | 
			
		||||
			coordinates: crd
 | 
			
		||||
		}
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
		if (Array.isArray(p.ppLocRefL)) {
 | 
			
		||||
			for (let ref of p.ppLocRefL) {
 | 
			
		||||
				const p = res[ref.ppIdx]
 | 
			
		||||
				if (p && ref.location) p.properties = ref.location
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Often there is one more point right next to each point at a station.
 | 
			
		||||
			// We filter them here if they are < 5m from each other.
 | 
			
		||||
			for (let i = 1; i < res.length; i++) {
 | 
			
		||||
				const p1 = res[i - 1].geometry.coordinates
 | 
			
		||||
				const p2 = res[i].geometry.coordinates
 | 
			
		||||
				const d = distance(p1[1], p1[0], p2[1], p2[0])
 | 
			
		||||
				if (d >= .005) continue
 | 
			
		||||
				const l1 = Object.keys(res[i - 1].properties).length
 | 
			
		||||
				const l2 = Object.keys(res[i].properties).length
 | 
			
		||||
				if (l1 === 0 && l2 > 0) res.splice(i - 1, 1)
 | 
			
		||||
				else if (l2 === 0 && l1 > 0) res.splice(i, 1)
 | 
			
		||||
			}
 | 
			
		||||
	if (Array.isArray(p.ppLocRefL)) {
 | 
			
		||||
		for (let ref of p.ppLocRefL) {
 | 
			
		||||
			const p = res[ref.ppIdx]
 | 
			
		||||
			if (p && ref.location) p.properties = ref.location
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			type: 'FeatureCollection',
 | 
			
		||||
			features: res
 | 
			
		||||
		// Often there is one more point right next to each point at a station.
 | 
			
		||||
		// We filter them here if they are < 5m from each other.
 | 
			
		||||
		for (let i = 1; i < res.length; i++) {
 | 
			
		||||
			const p1 = res[i - 1].geometry.coordinates
 | 
			
		||||
			const p2 = res[i].geometry.coordinates
 | 
			
		||||
			const d = distance(p1[1], p1[0], p2[1], p2[0])
 | 
			
		||||
			if (d >= .005) continue
 | 
			
		||||
			const l1 = Object.keys(res[i - 1].properties).length
 | 
			
		||||
			const l2 = Object.keys(res[i].properties).length
 | 
			
		||||
			if (l1 === 0 && l2 > 0) res.splice(i - 1, 1)
 | 
			
		||||
			else if (l2 === 0 && l1 > 0) res.splice(i, 1)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return parsePolyline
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		type: 'FeatureCollection',
 | 
			
		||||
		features: res
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = createParsePolyline
 | 
			
		||||
module.exports = parsePolyline
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +1,17 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
const createParseBitmask = (profile) => {
 | 
			
		||||
	const defaultProducts = {}
 | 
			
		||||
	for (let product of profile.products) defaultProducts[product.id] = false
 | 
			
		||||
const parseBitmask = ({profile}, bitmask) => {
 | 
			
		||||
	const res = {}
 | 
			
		||||
	for (let product of profile.products) res[product.id] = false
 | 
			
		||||
 | 
			
		||||
	const parseBitmask = (bitmask) => {
 | 
			
		||||
		const res = Object.assign({}, defaultProducts)
 | 
			
		||||
	const bits = bitmask.toString(2).split('').map(i => parseInt(i)).reverse()
 | 
			
		||||
	for (let i = 0; i < bits.length; i++) {
 | 
			
		||||
		if (!bits[i]) continue // ignore `0`
 | 
			
		||||
 | 
			
		||||
		const bits = bitmask.toString(2).split('').map(i => parseInt(i)).reverse()
 | 
			
		||||
		for (let i = 0; i < bits.length; i++) {
 | 
			
		||||
			if (!bits[i]) continue // ignore `0`
 | 
			
		||||
 | 
			
		||||
			const product = profile.products.find(p => p.bitmasks.includes(Math.pow(2, i)))
 | 
			
		||||
			if (product) res[product.id] = true
 | 
			
		||||
		}
 | 
			
		||||
		return res
 | 
			
		||||
		const product = profile.products.find(p => p.bitmasks.includes(Math.pow(2, i)))
 | 
			
		||||
		if (product) res[product.id] = true
 | 
			
		||||
	}
 | 
			
		||||
	return parseBitmask
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = createParseBitmask
 | 
			
		||||
module.exports = parseBitmask
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,48 +4,46 @@ const parseWhen = require('./when')
 | 
			
		|||
const parsePlatform = require('./platform')
 | 
			
		||||
const findRemarks = require('./find-remarks')
 | 
			
		||||
 | 
			
		||||
const createParseStopover = (profile, opt, data, date) => {
 | 
			
		||||
	const parseStopover = (st) => {
 | 
			
		||||
		const arr = parseWhen(profile, date, st.aTimeS, st.aTimeR, st.aTZOffset, st.aCncl)
 | 
			
		||||
		const arrPl = parsePlatform(profile, st.aPlatfS, st.aPlatfR, st.aCncl)
 | 
			
		||||
		const dep = parseWhen(profile, date, st.dTimeS, st.dTimeR, st.dTZOffset, st.dCncl)
 | 
			
		||||
		const depPl = parsePlatform(profile, st.dPlatfS, st.dPlatfR, st.dCncl)
 | 
			
		||||
const parseStopover = (ctx, st, date) => { // st = raw stopover
 | 
			
		||||
	const {profile, opt} = ctx
 | 
			
		||||
 | 
			
		||||
		const res = {
 | 
			
		||||
			stop: st.location || null,
 | 
			
		||||
			arrival: arr.when,
 | 
			
		||||
			plannedArrival: arr.plannedWhen,
 | 
			
		||||
			arrivalDelay: arr.delay,
 | 
			
		||||
			arrivalPlatform: arrPl.platform,
 | 
			
		||||
			plannedArrivalPlatform: arrPl.plannedPlatform,
 | 
			
		||||
			departure: dep.when,
 | 
			
		||||
			plannedDeparture: dep.plannedWhen,
 | 
			
		||||
			departureDelay: dep.delay,
 | 
			
		||||
			departurePlatform: depPl.platform,
 | 
			
		||||
			plannedDeparturePlatform: depPl.plannedPlatform
 | 
			
		||||
		}
 | 
			
		||||
	const arr = profile.parseWhen(ctx, date, st.aTimeS, st.aTimeR, st.aTZOffset, st.aCncl)
 | 
			
		||||
	const arrPl = profile.parsePlatform(ctx, st.aPlatfS, st.aPlatfR, st.aCncl)
 | 
			
		||||
	const dep = profile.parseWhen(ctx, date, st.dTimeS, st.dTimeR, st.dTZOffset, st.dCncl)
 | 
			
		||||
	const depPl = profile.parsePlatform(ctx, st.dPlatfS, st.dPlatfR, st.dCncl)
 | 
			
		||||
 | 
			
		||||
		if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen
 | 
			
		||||
		if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform
 | 
			
		||||
		if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen
 | 
			
		||||
		if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform
 | 
			
		||||
 | 
			
		||||
		// mark stations the train passes without stopping
 | 
			
		||||
		if(st.dInS === false && st.aOutS === false) res.passBy = true
 | 
			
		||||
 | 
			
		||||
		if (st.aCncl || st.dCncl) {
 | 
			
		||||
			res.cancelled = true
 | 
			
		||||
			Object.defineProperty(res, 'canceled', {value: true})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (opt.remarks && Array.isArray(st.msgL)) {
 | 
			
		||||
			res.remarks = findRemarks(st.msgL).map(([remark]) => remark)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return res
 | 
			
		||||
	const res = {
 | 
			
		||||
		stop: st.location || null,
 | 
			
		||||
		arrival: arr.when,
 | 
			
		||||
		plannedArrival: arr.plannedWhen,
 | 
			
		||||
		arrivalDelay: arr.delay,
 | 
			
		||||
		arrivalPlatform: arrPl.platform,
 | 
			
		||||
		plannedArrivalPlatform: arrPl.plannedPlatform,
 | 
			
		||||
		departure: dep.when,
 | 
			
		||||
		plannedDeparture: dep.plannedWhen,
 | 
			
		||||
		departureDelay: dep.delay,
 | 
			
		||||
		departurePlatform: depPl.platform,
 | 
			
		||||
		plannedDeparturePlatform: depPl.plannedPlatform
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return parseStopover
 | 
			
		||||
	if (arr.prognosedWhen) res.prognosedArrival = arr.prognosedWhen
 | 
			
		||||
	if (arrPl.prognosedPlatform) res.prognosedArrivalPlatform = arrPl.prognosedPlatform
 | 
			
		||||
	if (dep.prognosedWhen) res.prognosedDeparture = dep.prognosedWhen
 | 
			
		||||
	if (depPl.prognosedPlatform) res.prognosedDeparturePlatform = depPl.prognosedPlatform
 | 
			
		||||
 | 
			
		||||
	// mark stations the train passes without stopping
 | 
			
		||||
	if(st.dInS === false && st.aOutS === false) res.passBy = true
 | 
			
		||||
 | 
			
		||||
	if (st.aCncl || st.dCncl) {
 | 
			
		||||
		res.cancelled = true
 | 
			
		||||
		Object.defineProperty(res, 'canceled', {value: true})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (opt.remarks && Array.isArray(st.msgL)) {
 | 
			
		||||
		res.remarks = findRemarks(st.msgL).map(([remark]) => remark)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = createParseStopover
 | 
			
		||||
module.exports = parseStopover
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ const typesByIcon = Object.assign(Object.create(null), {
 | 
			
		|||
	HimWarn: 'status'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const parseMsgEdge = (profile) => (e) => {
 | 
			
		||||
const parseMsgEdge = (ctx) => (e) => {
 | 
			
		||||
	const res = omit(e, [
 | 
			
		||||
		'icoX',
 | 
			
		||||
		'fLocX', 'fromLocation',
 | 
			
		||||
| 
						 | 
				
			
			@ -19,18 +19,20 @@ const parseMsgEdge = (profile) => (e) => {
 | 
			
		|||
	res.toLoc = e.toLocation || null
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
const parseMsgEvent = (profile) => (e) => {
 | 
			
		||||
const parseMsgEvent = ({profile}) => (e) => {
 | 
			
		||||
	return {
 | 
			
		||||
		// todo: rename `Loc` -> `Location` [breaking]
 | 
			
		||||
		fromLoc: e.fromLocation || null,
 | 
			
		||||
		toLoc: e.toLocation || null,
 | 
			
		||||
		start: profile.parseDateTime(profile, e.fDate, e.fTime, null),
 | 
			
		||||
		end: profile.parseDateTime(profile, e.tDate, e.tTime, null),
 | 
			
		||||
		start: profile.parseDateTime(ctx, e.fDate, e.fTime, null),
 | 
			
		||||
		end: profile.parseDateTime(ctx, e.tDate, e.tTime, null),
 | 
			
		||||
		sections: e.sectionNums || [] // todo: parse
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const parseWarning = (profile, w, data) => {
 | 
			
		||||
const parseWarning = (ctx, w) => {
 | 
			
		||||
	const {profile, res: resp} = ctx
 | 
			
		||||
 | 
			
		||||
	// todo: act, pub, lead, tckr, prod, comp,
 | 
			
		||||
	// todo: cat (1, 2), pubChL, rRefL, impactL
 | 
			
		||||
	// pubChL:
 | 
			
		||||
| 
						 | 
				
			
			@ -62,24 +64,24 @@ const parseWarning = (profile, w, data) => {
 | 
			
		|||
		priority: w.prio,
 | 
			
		||||
		category: w.cat || null // todo: parse to sth meaningful
 | 
			
		||||
	}
 | 
			
		||||
	if ('prod' in w) res.products = profile.parseProducts(w.prod)
 | 
			
		||||
	if ('prod' in w) res.products = profile.parseProductsBitmask(ctx, w.prod)
 | 
			
		||||
 | 
			
		||||
	if (w.edgeRefL && data.himMsgEdgeL) {
 | 
			
		||||
	if (w.edgeRefL && resp.common && resp.common.himMsgEdgeL) {
 | 
			
		||||
		res.edges = w.edgeRefL
 | 
			
		||||
		.map(i => data.himMsgEdgeL[i])
 | 
			
		||||
		.map(i => resp.common.himMsgEdgeL[i])
 | 
			
		||||
		.filter(e => !!e)
 | 
			
		||||
		.map(parseMsgEdge(profile))
 | 
			
		||||
		.map(parseMsgEdge(ctx))
 | 
			
		||||
	}
 | 
			
		||||
	if (w.eventRefL && data.himMsgEventL) {
 | 
			
		||||
	if (w.eventRefL && resp.common && resp.common.himMsgEventL) {
 | 
			
		||||
		res.events = w.eventRefL
 | 
			
		||||
		.map(i => data.himMsgEventL[i])
 | 
			
		||||
		.map(i => resp.common.himMsgEventL[i])
 | 
			
		||||
		.filter(e => !!e)
 | 
			
		||||
		.map(parseMsgEvent(profile))
 | 
			
		||||
		.map(parseMsgEvent(ctx))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (w.sDate && w.sTime) res.validFrom = profile.parseDateTime(profile, w.sDate, w.sTime, null)
 | 
			
		||||
	if (w.eDate && w.eTime) res.validUntil = profile.parseDateTime(profile, w.eDate, w.eTime, null)
 | 
			
		||||
	if (w.lModDate && w.lModTime) res.modified = profile.parseDateTime(profile, w.lModDate, w.lModTime, null)
 | 
			
		||||
	if (w.sDate && w.sTime) res.validFrom = profile.parseDateTime(ctx, w.sDate, w.sTime, null)
 | 
			
		||||
	if (w.eDate && w.eTime) res.validUntil = profile.parseDateTime(ctx, w.eDate, w.eTime, null)
 | 
			
		||||
	if (w.lModDate && w.lModTime) res.modified = profile.parseDateTime(ctx, w.lModDate, w.lModTime, null)
 | 
			
		||||
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +1,15 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
const parseWhen = (profile, date, timeS, timeR, tzOffset, cncl = false) => {
 | 
			
		||||
	const parse = profile.parseDateTime
 | 
			
		||||
const parseWhen = (ctx, date, timeS, timeR, tzOffset, cncl = false) => {
 | 
			
		||||
	const parse = ctx.profile.parseDateTime
 | 
			
		||||
 | 
			
		||||
	let planned = timeS ? parse(profile, date, timeS, tzOffset, false) : null
 | 
			
		||||
	let prognosed = timeR ? parse(profile, date, timeR, tzOffset, false) : null
 | 
			
		||||
	let planned = timeS ? parse(ctx, date, timeS, tzOffset, false) : null
 | 
			
		||||
	let prognosed = timeR ? parse(ctx, date, timeR, tzOffset, false) : null
 | 
			
		||||
	let delay = null
 | 
			
		||||
 | 
			
		||||
	if (planned && prognosed) {
 | 
			
		||||
		const tPlanned = parse(profile, date, timeS, tzOffset, true)
 | 
			
		||||
		const tPrognosed = parse(profile, date, timeR, tzOffset, true)
 | 
			
		||||
		const tPlanned = parse(ctx, date, timeS, tzOffset, true)
 | 
			
		||||
		const tPrognosed = parse(ctx, date, timeR, tzOffset, true)
 | 
			
		||||
		delay = Math.round((tPrognosed - tPlanned) / 1000)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,48 +3,53 @@
 | 
			
		|||
const test = require('tape')
 | 
			
		||||
const parse = require('../../parse/date-time')
 | 
			
		||||
 | 
			
		||||
const profile = {
 | 
			
		||||
	timezone: 'Europe/Berlin',
 | 
			
		||||
	locale: 'de-DE'
 | 
			
		||||
const ctx = {
 | 
			
		||||
	common: {},
 | 
			
		||||
	opt: {},
 | 
			
		||||
	profile: {
 | 
			
		||||
		timezone: 'Europe/Berlin',
 | 
			
		||||
		locale: 'de-DE'
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('date & time parsing uses profile.timezone', (t) => {
 | 
			
		||||
	const iso = parse({
 | 
			
		||||
		...profile, timezone: 'Europe/Moscow'
 | 
			
		||||
	}, '20190819', '203000', undefined, false)
 | 
			
		||||
	t.equal(iso, '2019-08-19T20:30:00+03:00')
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('date & time parsing returns a timestamp', (t) => {
 | 
			
		||||
	const iso = parse(profile, '20190819', '203000', undefined, false)
 | 
			
		||||
	const ts = parse(profile, '20190819', '203000', undefined, true)
 | 
			
		||||
	const iso = parse(ctx, '20190819', '203000', undefined, false)
 | 
			
		||||
	const ts = parse(ctx, '20190819', '203000', undefined, true)
 | 
			
		||||
	t.equal(ts, +new Date(iso))
 | 
			
		||||
	t.equal(ts, 1566239400 * 1000)
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('date & time parsing uses tzOffset', (t) => {
 | 
			
		||||
	const iso = parse(profile, '20190819', '203000', -120, false)
 | 
			
		||||
	const iso = parse(ctx, '20190819', '203000', -120, false)
 | 
			
		||||
	t.equal(iso, '2019-08-19T20:30:00-02:00')
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('date & time parsing works with day "overflow"', (t) => {
 | 
			
		||||
	const iso = parse(profile, '20190819', '02203000', undefined, false)
 | 
			
		||||
	const iso = parse(ctx, '20190819', '02203000', undefined, false)
 | 
			
		||||
	t.equal(iso, '2019-08-21T20:30:00+02:00')
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// #106
 | 
			
		||||
test('date & time parsing works with day "overflow" & tzOffset', (t) => {
 | 
			
		||||
	const iso = parse(profile, '20190819', '02203000', -120, false)
 | 
			
		||||
	const iso = parse(ctx, '20190819', '02203000', -120, false)
 | 
			
		||||
	t.equal(iso, '2019-08-21T20:30:00-02:00')
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('date & time parsing works with summer & winter time', (t) => {
 | 
			
		||||
	const iso = parse(profile, '20190219', '203000', undefined, false)
 | 
			
		||||
	const iso = parse(ctx, '20190219', '203000', undefined, false)
 | 
			
		||||
	t.equal(iso, '2019-02-19T20:30:00+01:00')
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('date & time parsing uses profile.timezone', (t) => {
 | 
			
		||||
	const iso = parse({
 | 
			
		||||
		...ctx,
 | 
			
		||||
		profile: {...ctx.profile, timezone: 'Europe/Moscow'}
 | 
			
		||||
	}, '20190819', '203000', undefined, false)
 | 
			
		||||
	t.equal(iso, '2019-08-19T20:30:00+03:00')
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,11 @@
 | 
			
		|||
const test = require('tape')
 | 
			
		||||
const parse = require('../../parse/hint')
 | 
			
		||||
 | 
			
		||||
const profile = {}
 | 
			
		||||
const ctx = {
 | 
			
		||||
	data: {},
 | 
			
		||||
	opt: {},
 | 
			
		||||
	profile: {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('parses hints correctly', (t) => {
 | 
			
		||||
	const input = {
 | 
			
		||||
| 
						 | 
				
			
			@ -18,20 +22,20 @@ test('parses hints correctly', (t) => {
 | 
			
		|||
		text: 'some text'
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.deepEqual(parse(profile, input), expected)
 | 
			
		||||
	t.deepEqual(parse(profile, {
 | 
			
		||||
	t.deepEqual(parse(ctx, input), expected)
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, type: 'I'
 | 
			
		||||
	}), expected)
 | 
			
		||||
 | 
			
		||||
	// alternative trip
 | 
			
		||||
	t.deepEqual(parse(profile, {
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, type: 'L', jid: 'trip id'
 | 
			
		||||
	}), {
 | 
			
		||||
		...expected, type: 'status', code: 'alternative-trip', tripId: 'trip id'
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// type: M
 | 
			
		||||
	t.deepEqual(parse(profile, {
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, type: 'M', txtS: 'some summary'
 | 
			
		||||
	}), {
 | 
			
		||||
		...expected, type: 'status', summary: 'some summary'
 | 
			
		||||
| 
						 | 
				
			
			@ -39,18 +43,18 @@ test('parses hints correctly', (t) => {
 | 
			
		|||
 | 
			
		||||
	// type: D
 | 
			
		||||
	for (const type of ['D', 'U', 'R', 'N', 'Y']) {
 | 
			
		||||
		t.deepEqual(parse(profile, {...input, type}), {
 | 
			
		||||
		t.deepEqual(parse(ctx, {...input, type}), {
 | 
			
		||||
			...expected, type: 'status'
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// .code via .icon
 | 
			
		||||
	t.deepEqual(parse(profile, {
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, code: null, icon: {type: 'cancel'}
 | 
			
		||||
	}), {...expected, code: 'cancelled'})
 | 
			
		||||
 | 
			
		||||
	// invalid
 | 
			
		||||
	t.equal(parse(profile, {...input, type: 'X'}), null)
 | 
			
		||||
	t.equal(parse(ctx, {...input, type: 'X'}), null)
 | 
			
		||||
 | 
			
		||||
	t.end()
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,14 +3,18 @@
 | 
			
		|||
const test = require('tape')
 | 
			
		||||
const parse = require('../../parse/icon')
 | 
			
		||||
 | 
			
		||||
test('parses icons correctly', (t) => {
 | 
			
		||||
	const profile = {}
 | 
			
		||||
const ctx = {
 | 
			
		||||
	data: {},
 | 
			
		||||
	opt: {},
 | 
			
		||||
	profile: {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('parses icons correctly', (t) => {
 | 
			
		||||
	const text = {
 | 
			
		||||
		"res": "BVG",
 | 
			
		||||
		"text": "Berliner Verkehrsbetriebe"
 | 
			
		||||
	}
 | 
			
		||||
	t.deepEqual(parse(profile, text), {
 | 
			
		||||
	t.deepEqual(parse(ctx, text), {
 | 
			
		||||
		type: 'BVG',
 | 
			
		||||
		title: 'Berliner Verkehrsbetriebe'
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +23,7 @@ test('parses icons correctly', (t) => {
 | 
			
		|||
		"res": "PROD_BUS",
 | 
			
		||||
		"txtS": "18"
 | 
			
		||||
	}
 | 
			
		||||
	t.deepEqual(parse(profile, txtS), {
 | 
			
		||||
	t.deepEqual(parse(ctx, txtS), {
 | 
			
		||||
		type: 'PROD_BUS',
 | 
			
		||||
		title: '18'
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +32,7 @@ test('parses icons correctly', (t) => {
 | 
			
		|||
		"res": "RBB",
 | 
			
		||||
		"txt": "Regionalbus Braunschweig GmbH"
 | 
			
		||||
	}
 | 
			
		||||
	t.deepEqual(parse(profile, txt), {
 | 
			
		||||
	t.deepEqual(parse(ctx, txt), {
 | 
			
		||||
		type: 'RBB',
 | 
			
		||||
		title: 'Regionalbus Braunschweig GmbH'
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +40,7 @@ test('parses icons correctly', (t) => {
 | 
			
		|||
	const noText = {
 | 
			
		||||
		"res": "attr_bike_r"
 | 
			
		||||
	}
 | 
			
		||||
	t.deepEqual(parse(profile, noText), {
 | 
			
		||||
	t.deepEqual(parse(ctx, noText), {
 | 
			
		||||
		type: 'attr_bike_r',
 | 
			
		||||
		title: null
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +60,7 @@ test('parses icons correctly', (t) => {
 | 
			
		|||
			"a": 255
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	t.deepEqual(parse(profile, withColor), {
 | 
			
		||||
	t.deepEqual(parse(ctx, withColor), {
 | 
			
		||||
		type: 'prod_sub_t',
 | 
			
		||||
		title: null,
 | 
			
		||||
		fgColor: {r: 255, g: 255, b: 255, a: 255},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
const test = require('tape')
 | 
			
		||||
const parser = require('../../parse/line')
 | 
			
		||||
const parse = require('../../parse/line')
 | 
			
		||||
 | 
			
		||||
const profile = {
 | 
			
		||||
	products: [
 | 
			
		||||
| 
						 | 
				
			
			@ -10,8 +10,11 @@ const profile = {
 | 
			
		|||
		{id: 'bus', bitmasks: [4, 8]}
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
const opt = {}
 | 
			
		||||
const parse = parser(profile, opt, {})
 | 
			
		||||
const ctx = {
 | 
			
		||||
	data: {},
 | 
			
		||||
	opt: {},
 | 
			
		||||
	profile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('parses lines correctly', (t) => {
 | 
			
		||||
	const input = {
 | 
			
		||||
| 
						 | 
				
			
			@ -29,23 +32,23 @@ test('parses lines correctly', (t) => {
 | 
			
		|||
		public: true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.deepEqual(parse(input), expected)
 | 
			
		||||
	t.deepEqual(parse(ctx, input), expected)
 | 
			
		||||
 | 
			
		||||
	t.deepEqual(parse({
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, line: null, addName: input.line
 | 
			
		||||
	}), expected)
 | 
			
		||||
	t.deepEqual(parse({
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, line: null, name: input.line
 | 
			
		||||
	}), expected)
 | 
			
		||||
 | 
			
		||||
	// no prodCtx.lineId
 | 
			
		||||
	t.deepEqual(parse({
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, prodCtx: {...input.prodCtx, lineId: null}
 | 
			
		||||
	}), {
 | 
			
		||||
		...expected, id: 'foo-line'
 | 
			
		||||
	})
 | 
			
		||||
	// no prodCtx
 | 
			
		||||
	t.deepEqual(parse({
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input, prodCtx: undefined
 | 
			
		||||
	}), {
 | 
			
		||||
		...expected, id: 'foo-line', fahrtNr: null
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,16 @@ const omit = require('lodash/omit')
 | 
			
		|||
const parse = require('../../parse/location')
 | 
			
		||||
 | 
			
		||||
const profile = {
 | 
			
		||||
	parseStationName: name => name.toLowerCase(),
 | 
			
		||||
	parseProducts: bitmask => [bitmask]
 | 
			
		||||
	parseStationName: (_, name) => name.toLowerCase(),
 | 
			
		||||
	parseProductsBitmask: (_, bitmask) => [bitmask]
 | 
			
		||||
}
 | 
			
		||||
const opt = {
 | 
			
		||||
	linesOfStops: false
 | 
			
		||||
 | 
			
		||||
const ctx = {
 | 
			
		||||
	data: {},
 | 
			
		||||
	opt: {
 | 
			
		||||
		linesOfStops: false
 | 
			
		||||
	},
 | 
			
		||||
	profile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('parses an address correctly', (t) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +25,7 @@ test('parses an address correctly', (t) => {
 | 
			
		|||
		crd: {x: 13418027, y: 52515503}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const address = parse(profile, opt, null, input)
 | 
			
		||||
	const address = parse(ctx, input)
 | 
			
		||||
	t.deepEqual(address, {
 | 
			
		||||
		type: 'location',
 | 
			
		||||
		id: 'some id',
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +45,7 @@ test('parses a POI correctly', (t) => {
 | 
			
		|||
		crd: {x: 13418027, y: 52515503}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const poi = parse(profile, opt, null, input)
 | 
			
		||||
	const poi = parse(ctx, input)
 | 
			
		||||
	t.deepEqual(poi, {
 | 
			
		||||
		type: 'location',
 | 
			
		||||
		poi: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -50,10 +55,10 @@ test('parses a POI correctly', (t) => {
 | 
			
		|||
		longitude: 13.418027
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	const withExtId = parse(profile, opt, null, {...input, extId: 'some ext id'})
 | 
			
		||||
	const withExtId = parse(ctx, {...input, extId: 'some ext id'})
 | 
			
		||||
	t.equal(withExtId.id, 'some ext id')
 | 
			
		||||
 | 
			
		||||
	const withLeadingZero = parse(profile, opt, null, {...input, extId: '00some ext id'})
 | 
			
		||||
	const withLeadingZero = parse(ctx, {...input, extId: '00some ext id'})
 | 
			
		||||
	t.equal(withLeadingZero.id, 'some ext id')
 | 
			
		||||
 | 
			
		||||
	t.end()
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +73,7 @@ test('parses a stop correctly', (t) => {
 | 
			
		|||
		pCls: 123
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const stop = parse(profile, opt, null, input)
 | 
			
		||||
	const stop = parse(ctx, input)
 | 
			
		||||
	t.deepEqual(stop, {
 | 
			
		||||
		type: 'stop',
 | 
			
		||||
		id: 'foo stop',
 | 
			
		||||
| 
						 | 
				
			
			@ -82,17 +87,20 @@ test('parses a stop correctly', (t) => {
 | 
			
		|||
		products: [123]
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	const withoutLoc = parse(profile, opt, null, omit(input, ['crd']))
 | 
			
		||||
	const withoutLoc = parse(ctx, omit(input, ['crd']))
 | 
			
		||||
	t.equal(withoutLoc.location, null)
 | 
			
		||||
 | 
			
		||||
	const mainMast = parse(profile, opt, null, {...input, isMainMast: true})
 | 
			
		||||
	const mainMast = parse(ctx, {...input, isMainMast: true})
 | 
			
		||||
	t.equal(mainMast.type, 'station')
 | 
			
		||||
 | 
			
		||||
	const meta = parse(profile, opt, null, {...input, meta: 1})
 | 
			
		||||
	const meta = parse(ctx, {...input, meta: 1})
 | 
			
		||||
	t.equal(meta.isMeta, true)
 | 
			
		||||
 | 
			
		||||
	const lineA = {id: 'a'}
 | 
			
		||||
	const withLines = parse(profile, {...opt, linesOfStops: true}, null, {
 | 
			
		||||
	const withLines = parse({
 | 
			
		||||
		...ctx,
 | 
			
		||||
		opt: {...ctx.opt, linesOfStops: true}
 | 
			
		||||
	}, {
 | 
			
		||||
		...input, lines: [lineA]
 | 
			
		||||
	})
 | 
			
		||||
	t.deepEqual(withLines.lines, [lineA])
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,16 +3,19 @@
 | 
			
		|||
const test = require('tape')
 | 
			
		||||
const parse = require('../../parse/operator')
 | 
			
		||||
 | 
			
		||||
const ctx = {
 | 
			
		||||
	data: {},
 | 
			
		||||
	opt: {},
 | 
			
		||||
	profile: {}
 | 
			
		||||
}
 | 
			
		||||
test('parses an operator correctly', (t) => {
 | 
			
		||||
	const profile = {}
 | 
			
		||||
 | 
			
		||||
	const op = {
 | 
			
		||||
		"name": "Berliner Verkehrsbetriebe",
 | 
			
		||||
		"icoX": 1,
 | 
			
		||||
		"id": "Berliner Verkehrsbetriebe"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.deepEqual(parse(profile, op), {
 | 
			
		||||
	t.deepEqual(parse(ctx, op), {
 | 
			
		||||
		type: 'operator',
 | 
			
		||||
		id: 'berliner-verkehrsbetriebe',
 | 
			
		||||
		name: 'Berliner Verkehrsbetriebe'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,9 +4,14 @@ const test = require('tape')
 | 
			
		|||
const parse = require('../../parse/warning')
 | 
			
		||||
 | 
			
		||||
const profile = {
 | 
			
		||||
	parseProducts: bitmask => [bitmask],
 | 
			
		||||
	parseProductsBitmask: (_, bitmask) => [bitmask],
 | 
			
		||||
	parseDateTime: (_, date, time) => date + ':' + time
 | 
			
		||||
}
 | 
			
		||||
const ctx = {
 | 
			
		||||
	data: {},
 | 
			
		||||
	opt: {},
 | 
			
		||||
	profile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('parses warnings correctly', (t) => {
 | 
			
		||||
	const input = {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,26 +32,26 @@ test('parses warnings correctly', (t) => {
 | 
			
		|||
		category: 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.deepEqual(parse(profile, input), expected)
 | 
			
		||||
	t.deepEqual(parse(ctx, input), expected)
 | 
			
		||||
 | 
			
		||||
	// without basic fields
 | 
			
		||||
	t.deepEqual(parse(profile, {...input, hid: null}), {...expected, id: null})
 | 
			
		||||
	t.deepEqual(parse(profile, {...input, head: null}), {...expected, summary: null})
 | 
			
		||||
	t.deepEqual(parse(profile, {...input, text: null}), {...expected, text: null})
 | 
			
		||||
	t.deepEqual(parse(profile, {...input, cat: null}), {...expected, category: null})
 | 
			
		||||
	t.deepEqual(parse(ctx, {...input, hid: null}), {...expected, id: null})
 | 
			
		||||
	t.deepEqual(parse(ctx, {...input, head: null}), {...expected, summary: null})
 | 
			
		||||
	t.deepEqual(parse(ctx, {...input, text: null}), {...expected, text: null})
 | 
			
		||||
	t.deepEqual(parse(ctx, {...input, cat: null}), {...expected, category: null})
 | 
			
		||||
 | 
			
		||||
	// without icon
 | 
			
		||||
	t.deepEqual(parse(profile, {...input, icon: null}), {
 | 
			
		||||
	t.deepEqual(parse(ctx, {...input, icon: null}), {
 | 
			
		||||
		...expected, type: 'warning', icon: null
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// with products
 | 
			
		||||
	t.deepEqual(parse(profile, {...input, prod: 123}), {
 | 
			
		||||
	t.deepEqual(parse(ctx, {...input, prod: 123}), {
 | 
			
		||||
		...expected, products: [123]
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// validFrom, validUntil, modified
 | 
			
		||||
	t.deepEqual(parse(profile, {
 | 
			
		||||
	t.deepEqual(parse(ctx, {
 | 
			
		||||
		...input,
 | 
			
		||||
		sDate: '20190101', sTime: '094020',
 | 
			
		||||
		eDate: '20190101', eTime: '114020',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,11 +4,16 @@ const test = require('tape')
 | 
			
		|||
const parse = require('../../parse/when')
 | 
			
		||||
 | 
			
		||||
const profile = {
 | 
			
		||||
	parseDateTime: (profile, date, time, tzOffset, timestamp = false) => {
 | 
			
		||||
	parseDateTime: ({profile}, date, time, tzOffset, timestamp = false) => {
 | 
			
		||||
		if (timestamp) return ((date + '' + time) - tzOffset * 60) * 1000
 | 
			
		||||
		return date + ':' + time
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
const ctx = {
 | 
			
		||||
	data: {},
 | 
			
		||||
	opt: {},
 | 
			
		||||
	profile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('parseWhen works correctly', (t) => {
 | 
			
		||||
	const date = '20190606'
 | 
			
		||||
| 
						 | 
				
			
			@ -21,15 +26,15 @@ test('parseWhen works correctly', (t) => {
 | 
			
		|||
		delay: 130 // seconds
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.deepEqual(parse(profile, date, timeS, timeR, tzOffset), expected)
 | 
			
		||||
	t.deepEqual(parse(ctx, date, timeS, timeR, tzOffset), expected)
 | 
			
		||||
 | 
			
		||||
	// no realtime data
 | 
			
		||||
	t.deepEqual(parse(profile, date, timeS, null, tzOffset), {
 | 
			
		||||
	t.deepEqual(parse(ctx, date, timeS, null, tzOffset), {
 | 
			
		||||
		...expected, when: expected.plannedWhen, delay: null
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// cancelled
 | 
			
		||||
	t.deepEqual(parse(profile, date, timeS, timeR, tzOffset, true), {
 | 
			
		||||
	t.deepEqual(parse(ctx, date, timeS, timeR, tzOffset, true), {
 | 
			
		||||
		...expected,
 | 
			
		||||
		when: null,
 | 
			
		||||
		prognosedWhen: expected.when
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue