/* Returns the expected arrival times given vehicleId and naptanId pairs */ func SelectNextBusesByVehicleStopPairs(vehiclesStopsMap map[string]string) []NextBus { var nextBuses []NextBus innerQueries := make([]string, 0) for vehicleId, naptanId := range vehiclesStopsMap { innerQueries = append(innerQueries, fmt.Sprintf("SELECT line, bound, naptan_id, vehicle_id, expected_arrival_time, already_departed "+ "FROM next_buses "+ "WHERE vehicle_id='%v' AND naptan_id='%v'", vehicleId, naptanId)) } dbconn.AcquireLock() err := db.Select(&nextBuses, strings.Join(innerQueries, " UNION ALL ")) dbconn.ReleaseLock() if err != nil { logger.GetLogger().Panic(err) } /* Set time zone to UTC */ for i, v := range nextBuses { nextBuses[i].ExpectedArrivalTime = v.ExpectedArrivalTime.In(timezone.Get()) } return nextBuses }
/* Returns the next buses that are approaching a stop given a line and bound after a certain time */ func SelectNextBuses(lineBoundCombination map[string]string, naptanId string, arrivingAfter time.Time) []NextBus { var nextBuses []NextBus innerQueries := make([]string, 0) for line, bound := range lineBoundCombination { innerQueries = append(innerQueries, fmt.Sprintf("SELECT line, bound, naptan_id, vehicle_id, expected_arrival_time, already_departed "+ "FROM next_buses "+ "WHERE naptan_id='%v' AND line='%v' AND bound='%v' AND expected_arrival_time>=TIMESTAMP '%v'", naptanId, line, bound, toPsqlTimeStamp(arrivingAfter))) } sql := fmt.Sprintf("SELECT line, bound, naptan_id, vehicle_id, expected_arrival_time, already_departed "+ "FROM (%v) AS all_next_buses ORDER BY expected_arrival_time", strings.Join(innerQueries, " UNION ALL ")) logger.GetLogger().Info(sql) dbconn.AcquireLock() err := db.Select(&nextBuses, sql) dbconn.ReleaseLock() if err != nil { logger.GetLogger().Panic(err) } /* Set time zone to UTC */ for i, v := range nextBuses { nextBuses[i].ExpectedArrivalTime = v.ExpectedArrivalTime.In(timezone.Get()) } return nextBuses }
func parseTime(timeStamp string) time.Time { t, err := time.Parse(timeFormatString, timeStamp) if err != nil { logger.GetLogger().Panic(err) } return t.In(timezone.Get()) }
func parseISOTime(isoFormattedTimeStamp string) time.Time { t, err := time.Parse(time.RFC3339Nano, isoFormattedTimeStamp) if err != nil { logger.GetLogger().Panic(err) } return t.In(timezone.Get()) }
func TestFetchActualJourneyTimes(t *testing.T) { hammersmith := "490007705G" kingsCross := "490001171G" startTime := time.Date(2016, time.March, 15, 0, 0, 0, 0, time.UTC) endTime := time.Now().In(timezone.Get()) FetchActualJourneyTimes("10", "outbound", hammersmith, kingsCross, startTime, endTime) }
/* Checks whether a timestamp is in the past */ func timeStampHasPassedBy(timeStamp string, duration time.Duration) bool { tflFormatString := "2006-01-02T15:04:05.999Z" ts, err := time.Parse(tflFormatString, timeStamp) if err != nil { logger.GetLogger().Panic(err) } return ts.Before(time.Now().In(timezone.Get()).Add(-1 * duration)) }
func main() { /* List of neighbouring stops to plot their variation of journey times */ exhibitionRoad := "490006691W" royalAlbertHall := "490011750W" savoyStreet := "490011938U" bedfordStreet := "490003112J" regentStreet := "490011515Z" picadillyCircus := "490000179C" waterloo := "490014270P" lancasterPlace := "490008932T" marbleArch := "490000144R" dorchesterHotel := "490006111S1" heathrowParkThistleHotel := "490008019E" pinglestoneClose := "490007897E" rochesterWay := "490014366S" elthamCemetery := "490006536S" eastCroydon := "490001089E6" lunarHouse := "490017825WN" nutfieldClose := "490010463S" whiteHartLaneRailwayStation := "490001335D" templatePath := "../analysis/template/journeytime.html" journeys := map[string]string{ exhibitionRoad: royalAlbertHall, savoyStreet: bedfordStreet, regentStreet: picadillyCircus, waterloo: lancasterPlace, marbleArch: dorchesterHotel, heathrowParkThistleHotel: pinglestoneClose, rochesterWay: elthamCemetery, eastCroydon: lunarHouse, nutfieldClose: whiteHartLaneRailwayStation, } start := time.Date(2016, time.February, 13, 0, 0, 0, 0, time.UTC) end := time.Now().In(timezone.Get()) gap := 24 * time.Hour interval := 1 * time.Hour dates := make([]time.Time, 0) for d := start; d.Before(end); d = d.Add(gap) { dates = append(dates, d) } wr, err := os.Create("../../../../../charts/londonJourneyTimePlot.html") if err != nil { logger.GetLogger().Panic(err) } analysis.JourneyTimePlot(journeys, dates, interval, wr, templatePath) }
/* Returns map (vehicleId => ( "direction" => bound, "naptanId" => naptanId, "timeStamp" => timeStamp, "expectedArrival" => expectedArrival)) for each bus tracked */ func parseStreamArrivalsFromResponse(response *http.Response) <-chan map[string]map[string]string { out := make(chan map[string]map[string]string) go func() { var predictionArray []json.Number dec := json.NewDecoder(response.Body) for dec.More() { err := dec.Decode(&predictionArray) if err != nil { logger.GetLogger().Panic(err) } if len(predictionArray) == 0 { logger.GetLogger().Panic("Unexpected empty prediction array") } responseType, _ := predictionArray[0].Int64() switch responseType { /* Response type 1 corresponds to prediction array. Parse prediction array */ case 1: if len(predictionArray) != 6 { logger.GetLogger().Info("Expected prediction array to have length 5, but got %v. Prediction array: %v", len(predictionArray), predictionArray) } naptanId := predictionArray[1].String() visitNumber := predictionArray[2].String() directionId, _ := predictionArray[3].Int64() vehicleId := predictionArray[4].String() expectedArrivalUnixEpoch, _ := predictionArray[5].Int64() result := make(map[string]map[string]string) bundle := make(map[string]string) bundle["naptanId"] = naptanId bundle["visitNumber"] = visitNumber bundle["direction"] = directionIdToBound(directionId, predictionArray) bundle["timeStamp"] = toTflTimeStamp(time.Now().In(timezone.Get())) bundle["expectedArrival"] = toTflTimeStamp(time.Unix(0, expectedArrivalUnixEpoch)) result[vehicleId] = bundle out <- result default: logger.GetLogger().Error("Encountered non-prediction array: %v", predictionArray) continue } } }() return out }
func TestDeleteOldLogs(t *testing.T) { days := 1 DeleteOldLogs(days) names := listFiles(logDir) for _, name := range names { logFileTime := parseLogFileForTime(name) if logFileTime.Before(time.Now().In(timezone.Get()).Add( time.Duration(days*-24) * time.Hour)) { t.Fatalf("Failed to delete log file %v which is older than %v days", name, days) } } }
func main() { /* List of neighbouring stops to plot their variation of journey times */ oxfordCircusStation := "490010689OJ" tottenhamCourtRoadStation := "490000235V" edithGrove := "490005076W" worldsEndHealthCentre := "490009370W" museumStreet := "490010131W" cambridgeCircus := "490004695S" cephasStreet := "490004945S" darlingRow := "490005965S" monumentStation := "490000148K" greatTowerStreet := "490007418E" grosvenorGardens := "490014050R" victoria := "490014051V" beechfieldRoad := "490003812E" catfordBridgeStation := "490001053M" gerraldPlace := "490016425WA" denmarkStreet := "490004695A" blackbirdCross := "490004051W" kingsDrive := "490004331S" parkHouse := "490010777W" hamptonCourtGardens := "490007727B" templatePath := "../analysis/template/journeytime_v2.html" journeys := map[string]string{ oxfordCircusStation: tottenhamCourtRoadStation, edithGrove: worldsEndHealthCentre, museumStreet: cambridgeCircus, cephasStreet: darlingRow, monumentStation: greatTowerStreet, grosvenorGardens: victoria, beechfieldRoad: catfordBridgeStation, gerraldPlace: denmarkStreet, blackbirdCross: kingsDrive, parkHouse: hamptonCourtGardens, } start := time.Date(2016, time.May, 1, 0, 0, 0, 0, time.UTC) end := time.Now().In(timezone.Get()) wr, err := os.Create("../../../../../charts/londonJourneyTimePlot_v2.html") if err != nil { logger.GetLogger().Panic(err) } analysis.JourneyTimePlot2(journeys, start, end, wr, templatePath) }
func nextBusHandler(w http.ResponseWriter, r *http.Request) { /* Extract fields in GET request */ stopsStrCommaDelimited := r.URL.Query().Get("stops") numRouteOptionsPerStopCommaDelimited := r.URL.Query().Get("numRouteOptionsPerStop") lineBoundCombinationsCommaDelimited := r.URL.Query().Get("lineBoundCombinations") /* Map of target buses => target stop map (vehicleId => naptanId) */ counter := 0 numRouteOptionsPerStopArr := strings.Split(numRouteOptionsPerStopCommaDelimited, ",") lineBoundCombinationsArr := strings.Split(lineBoundCombinationsCommaDelimited, ",") stops := strings.Split(stopsStrCommaDelimited, ",") stopNextBusesMap := make(map[string][]TflNextBus) for i, stop := range stops { lineBoundCombinations := make(map[string]string) numRouteOptions, err := strconv.Atoi(numRouteOptionsPerStopArr[i]) if err != nil { writeError(w, err) } for i := 0; i < numRouteOptions; i++ { /* Extract line bound combination for stop */ line := lineBoundCombinationsArr[counter] counter++ bound := lineBoundCombinationsArr[counter] counter++ lineBoundCombinations[line] = bound } /* Find next buses */ nextBuses := selectdb.SelectNextBuses(lineBoundCombinations, stop, time.Now().In(timezone.Get())) tflNextBuses := make([]TflNextBus, len(nextBuses)) for i, nextBus := range nextBuses { tflNextBuses[i] = TflNextBus{ Line: nextBus.Line, Bound: nextBus.Bound, VehicleId: nextBus.VehicleId, ExpectedArrivalTime: formatISOTime(nextBus.ExpectedArrivalTime), AlreadyDeparted: nextBus.AlreadyDeparted, } } addToNextBusesCurrentLocations(&tflNextBuses) stopNextBusesMap[stop] = tflNextBuses } /* Write HTML output */ writeNextBusesDetails(w, stopNextBusesMap) }
func removeIfOld(dir, name string, days int) { logFileTime, err := parseLogFileForTime(name) if err != nil { logger.GetLogger().Error(err.Error()) return } if logFileTime.Before(time.Now().In(timezone.Get()).Add( time.Duration(days*-24) * time.Hour)) { err := os.Remove(filepath.Join(dir, name)) if err != nil { logger.GetLogger().Panic(err) } logger.GetLogger().Info("Removed log file %v", name) } }
/* Returns all expected terminus departures after now */ func SelectAllTerminusDepartures() []TerminusDeparture { var terminusDepartures []TerminusDeparture dbconn.AcquireLock() err := db.Select(&terminusDepartures, "SELECT line, bound, naptan_id, vehicle_id, expected_departure_time "+ "FROM terminus_departures "+ "WHERE expected_departure_time >= now() AT TIME ZONE 'utc'") dbconn.ReleaseLock() if err != nil { logger.GetLogger().Panic(err) } /* Adjust time zone to UTC */ for i, v := range terminusDepartures { terminusDepartures[i].ExpectedDepartureTime = v.ExpectedDepartureTime.In(timezone.Get()) } return terminusDepartures }
func TestAutoCorrelationPlot(t *testing.T) { exhibitionRoad := "490006691W" royalAlbertHall := "490011750W" templatePath := "template/autocorrelation.html" journeys := map[string]string{ exhibitionRoad: royalAlbertHall, } startTime := time.Date(2016, time.February, 13, 11, 0, 0, 0, time.UTC) endTime := time.Now().In(timezone.Get()).Add(-1 * time.Hour) interval := 1 * time.Hour wr, err := os.Create("plots/sample_autocorrelation.html") if err != nil { t.Fatal(err) } AutoCorrelationPlot(journeys, startTime, endTime, interval, wr, templatePath) }
/* Returns map[string]map[string]map[string]int ( map : fromStop => (map : toStop => (map : "estimate" => estimate in seconds "stddev" => standard deviation in seconds "delayBusCount" => bus count as evidence of delay "delayExpectedToLast" => how long delay is expected to last in seconds ) ) ) the estimated journey times, standard deviation, between map of neighbouring stops for mixed, historic and real time. */ func getEstimates(adjacentStops map[string]map[string]bool) (map[string]map[string]map[string]int, map[string]map[string]map[string]int, map[string]map[string]map[string]int) { departureTime := time.Now().In(timezone.Get()) timeRanges := map[time.Time]time.Time{ departureTime.AddDate(0, 0, -7).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -7).Add(30 * time.Minute), departureTime.AddDate(0, 0, -14).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -14).Add(30 * time.Minute), departureTime.AddDate(0, 0, -21).Add(-30 * time.Minute): departureTime.AddDate(0, 0, -21).Add(30 * time.Minute), } realTimeEstimates := make(map[string]map[string]map[string]int) mixedEstimates := make(map[string]map[string]map[string]int) /* Select historic and real time estimates from database */ historicEstimates := selectdb.SelectHistoricEstimates(timeRanges) realTimeData := selectdb.SelectRealTimeEstimates(realTimeWindow) /* Combine historic and real time data */ for fromStop, fromStopDetails := range historicEstimates { for toStop, historicDetails := range fromStopDetails { historicEstimate := time.Duration(historicDetails["estimate"]) * time.Second /* Extract real time data and lookup delay characteristics, skip for cases lacking real time estimate */ delayBusCount := 0 realTimeFromStopDetails, exists := realTimeData[fromStop] if !exists { continue } realTimeDetails, exists := realTimeFromStopDetails[toStop] if !exists { continue } realTimeCount, _ := realTimeDetails["count"] if realTimeCount < 3 { continue } realTimeRecords := make([]time.Duration, realTimeCount) for i := 1; i <= realTimeCount; i++ { // Since real time records are already reversed, reverse it again for ascending order in time, // so that handleDelayReverseLookup will work fine realTimeRecord := time.Duration(realTimeDetails[strconv.Itoa(realTimeCount+1-i)]) * time.Second realTimeRecords[i-1] = realTimeRecord } /* Calculate real time estimate using last three buses averaged */ realTimeLastThreeInSeconds := make([]float64, 3) for j := 0; j < 3; j++ { realTimeLastThreeInSeconds[j] = realTimeRecords[len(realTimeRecords)-1-j].Seconds() } realTimeEstimateInSeconds, _ := statistics.Mean(realTimeLastThreeInSeconds) _, exists = realTimeEstimates[fromStop] if !exists { realTimeEstimates[fromStop] = make(map[string]map[string]int) mixedEstimates[fromStop] = make(map[string]map[string]int) } realTimeEstimates[fromStop][toStop] = map[string]int{ "estimate": int(realTimeEstimateInSeconds), } /* Get mixed estimates below */ shouldUseHistoric, delayBusCount, delayExpectedToLast := performanceeval.HandleDelayReverseLookup( fromStop, toStop, departureTime, delayLastingThreshold, historicEstimate, realTimeRecords) if shouldUseHistoric { mixedEstimates[fromStop][toStop] = historicEstimates[fromStop][toStop] mixedEstimates[fromStop][toStop]["delayBusCount"] = delayBusCount } else { mixedEstimates[fromStop][toStop] = map[string]int{ "estimate": int(realTimeEstimateInSeconds), "delayBusCount": delayBusCount, "delayExpectedToLast": int(delayExpectedToLast.Seconds()), "delay": 1, } } } } return mixedEstimates, historicEstimates, realTimeEstimates }
/* Returns timestamp in psql format of current time */ func getCurrentPsqlTimeStamp() string { psqlFormatString := "2006-01-02 15:04:05" return time.Now().In(timezone.Get()).Format(psqlFormatString) }
/* Delay characteristics represented by map fromId => (toId => (day of week => (time of day => (delayThreshold => (numBuses => expected lasting time))))) */ func computeDelayCharacteristics(adjacentStops map[string]map[string]bool) map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration { delayCharacteristics := make(map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration) /* Channel for delay characteristics */ ch := make(chan map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration) lineBounds := selectdb.SelectLineBoundsServingMultipleAdjacentStops(adjacentStops) for fromStop, details := range adjacentStops { fromStopDelayCharacteristics := make(map[string]map[int]map[int]map[time.Duration]map[int]time.Duration) for toStop, _ := range details { go func(fromStop, toStop string) { /* Form time ranges - Compute for the weekday/weekend of yesterday */ timeRanges := make(map[time.Time]time.Time) endOfYesterday := time.Now().In(timezone.Get()).Truncate(24 * time.Hour) startOfYesterday := endOfYesterday.AddDate(0, 0, -1) weekday := startOfYesterday.Weekday() for _, day := range daysToLookBack { timeRanges[startOfYesterday.AddDate(0, 0, -day)] = endOfYesterday.AddDate(0, 0, -day) } /* Select journey history */ journeyHistory := selectdb.SelectJourneyHistory(fromStop, toStop, timeRanges, nil) times := make([]time.Time, len(journeyHistory)) actualDurations := make([]time.Duration, len(journeyHistory)) for i, record := range journeyHistory { times[i] = record.ToTimestamp actualDurations[i] = time.Duration(record.TimeTaken) * time.Second } /* Select historic journey times */ var line, bound string for k, v := range lineBounds[fromStop][toStop] { line = k bound = v } /* Generate times at intervals for prediction efficiency */ timesAtIntervals := make([]time.Time, 0) for startTime, endTime := range timeRanges { for t := startTime.Round(predictionInterval); t.Before(endTime.Add(predictionInterval)); t = t.Add(predictionInterval) { timesAtIntervals = append(timesAtIntervals, t) } } historicTimes := PredictJourneyTimes(line, bound, fromStop, toStop, timesAtIntervals, DefaultHistoric(), nil) historicDurations := make([]time.Duration, len(journeyHistory)) for i, t := range times { historicDurations[i] = historicTimes[t.Round(predictionInterval)] } delayCharacteristics := map[string]map[string]map[int]map[int]map[time.Duration]map[int]time.Duration{ fromStop: map[string]map[int]map[int]map[time.Duration]map[int]time.Duration{ toStop: computeDelayCharacteristicsForStopPair(fromStop, toStop, weekday, times, actualDurations, historicDurations), }, } updateDelayCharacteristics(delayCharacteristics) ch <- delayCharacteristics }(fromStop, toStop) fromStopDelayCharacteristics[toStop] = nil // fromStopDelayCharacteristics[toStop] = computeDelayCharacteristicsForStopPair(fromStop, toStop, times, actualDurations, historicDurations) } // delayCharacteristics[fromStop] = fromStopDelayCharacteristics delayCharacteristics[fromStop] = fromStopDelayCharacteristics } for _, v := range adjacentStops { for range v { <-ch /*entry := <-ch delayCharacteristics[entry.fromStop][entry.toStop] = entry.entry updateDelayCharacteristics(delayCharacteristics)*/ } } return delayCharacteristics }
/* Given a timestamp, decides whether its estimate is trustworthy based on how recent the timestamp is */ func isRecentEstimate(t time.Time) bool { threshold := 5 * time.Minute return !t.Before(time.Now().In(timezone.Get()).Add(-1 * threshold)) }
/* Change the predictions inside directions to use our own predictions */ func (directions *TflDirections) UpdatePredictions(isDeparting bool, departureOrArrivalTime time.Time) error { journeys := &directions.Journeys for i := 0; i < len(*journeys); i++ { /* Counter for keeping track of times for prediction */ cumulativeTime := departureOrArrivalTime if !isDeparting { /* Approximate departure time for arriving case by subtracting tfl estimate */ cumulativeTime = departureOrArrivalTime.Add(-time.Duration((*journeys)[i].TflDuration) * time.Minute) } initialTime := cumulativeTime legs := &(*journeys)[i].Legs for j := 0; j < len(*legs); j++ { leg := &(*legs)[j] leg.DepartureTime = formatISOTime(cumulativeTime) if leg.Mode.Id == "bus" { var line string var bound string var stopPoints []string lineBoundCombination := make(map[string]string) /* Update naptan ids and coordinates of intermediate stops */ for i, routeOption := range leg.RouteOptions { var err error line = routeOption.LineIdentifier.Id stopPoints, bound, err = searchIntermediateStops(line, leg.DeparturePoint.IcsCode, leg.ArrivalPoint.IcsCode) if err != nil { /* Bus may not be Tfl operated (e.g. bus 555 to Heathrow). In such case skip. */ continue } /* Add bound to struct */ leg.RouteOptions[i].LineIdentifier.Bound = bound lineBoundCombination[line] = bound } /* If no stop points found, invalidate journey for removal afterwards*/ if len(stopPoints) == 0 { (*journeys)[i].IsInvalid = true continue } leg.IntermediateStops = make([]TflStopPoint, len(stopPoints)) details := selectdb.SelectMultipleStops(stopPoints) for i, naptanId := range stopPoints { latitude, _ := strconv.ParseFloat(details[naptanId]["latitude"], 64) longitude, _ := strconv.ParseFloat(details[naptanId]["longitude"], 64) leg.IntermediateStops[i] = TflStopPoint{ Name: details[naptanId]["name"], NaptanId: naptanId, Latitude: latitude, Longitude: longitude, } } /* Update bus prediction */ newPrediction, historicBasedPrediction, realTimeBasedPrediction, newPredictionAdjacentStops, err := updateBusPrediction(line, bound, stopPoints, cumulativeTime) if err == nil { leg.DurationInMinutes = int(newPrediction.Minutes()) leg.HistoricDurationInMinutes = int(historicBasedPrediction.Minutes()) leg.RealTimeDurationInMinutes = int(realTimeBasedPrediction.Minutes()) } /* Update next bus arrivals */ fromStop := leg.IntermediateStops[0].NaptanId nextBuses := selectdb.SelectNextBuses(lineBoundCombination, fromStop, time.Now().In(timezone.Get())) leg.NextBuses = make([]TflNextBus, len(nextBuses)) for i, nextBus := range nextBuses { leg.NextBuses[i] = TflNextBus{ Line: nextBus.Line, Bound: nextBus.Bound, VehicleId: nextBus.VehicleId, ExpectedArrivalTime: formatISOTime(nextBus.ExpectedArrivalTime), AlreadyDeparted: nextBus.AlreadyDeparted, } } addToNextBusesCurrentLocations(&leg.NextBuses) /* Find out next bus frequency */ averageWaitTime, minWaitTime, maxWaitTime, err := selectdb.SelectAverageWaitTimes(lineBoundCombination, initialTime) if err != nil { logger.GetLogger().Error(err.Error()) } leg.FrequencyLow = int(minWaitTime.Minutes()) leg.FrequencyHigh = int(maxWaitTime.Minutes()) nextBusFlag := false if len(nextBuses) > 0 { /* Find first bus that is going to arrive and the user is able to catch */ for _, nextBus := range nextBuses { nextBusTime := nextBus.ExpectedArrivalTime if nextBusTime.After(cumulativeTime) { leg.TimeToNextBus = int(nextBusTime.Sub(cumulativeTime).Minutes()) cumulativeTime = nextBusTime nextBusFlag = true break } } } if len(nextBuses) == 0 || (len(nextBuses) > 0 && !nextBusFlag) { /* Handle special case if lack of next buses, use average wait times instead */ leg.TimeToNextBus = int(averageWaitTime.Minutes()) cumulativeTime = cumulativeTime.Add(averageWaitTime) } /* Update cumulative time */ leg.IntermediateStops[0].ExpectedArrival = int(cumulativeTime.Sub(initialTime).Minutes()) for i := 0; i < len(stopPoints)-1; i++ { fromStop := stopPoints[i] toStop := stopPoints[i+1] cumulativeTime = cumulativeTime.Add(newPredictionAdjacentStops[fromStop][toStop]) leg.IntermediateStops[i+1].ExpectedArrival = int(cumulativeTime.Sub(initialTime).Minutes()) } } else { cumulativeTime = cumulativeTime.Add(time.Duration(leg.DurationInMinutes) * time.Minute) } } } /* Remove invalid journeys */ for i := len(*journeys) - 1; i >= 0; i-- { if (*journeys)[i].IsInvalid { *journeys = append((*journeys)[:i], (*journeys)[i+1:]...) } } return nil }
/* Checks whether timestamp has passed */ func timeStampHasPassed(tflFormat string) bool { return parseTflTimeStamp(tflFormat).Before(time.Now().In(timezone.Get())) }