szczanieckiej.git

commit fb6de5436d2837ab31bfccafbba1c19aa0b621cc

Author: Adam Evyčędo <git@apiote.xyz>

fully support exceptions in calendars

 api/api.go | 1 
 api/lineResponse.go | 4 
 server/route_line.go | 6 
 traffic/access.go | 9 +
 traffic/convert.go | 201 +++++++++++++++++++++++++++++++------------
 traffic/structs_gen.go | 20 +++


diff --git a/api/api.go b/api/api.go
index b99e1b920ca2496474c0cf0af7b2b977decab01d..78919e92d0d01fb84d85c841733172fe3a732445 100644
--- a/api/api.go
+++ b/api/api.go
@@ -83,6 +83,7 @@ 			lineGraph := LineGraphV1{
 				Stops:     make([]StopStubV1, len(graph.StopCodes)),
 				NextNodes: graph.NextNodes,
 			}
+			delete(lineGraph.NextNodes, -1)
 			for i, code := range graph.StopCodes {
 				stopStub, err := traffic.GetStopStub(code, line.Name, context, t)
 				if err != nil {




diff --git a/api/lineResponse.go b/api/lineResponse.go
index 1fda5e76233deed002c02e15e432010dfac279c9..76db96c1e13c112f22038f75dbd8f17f3b1e0888 100644
--- a/api/lineResponse.go
+++ b/api/lineResponse.go
@@ -1,6 +1,8 @@
 package api
 
-import "apiote.xyz/p/szczanieckiej/traffic"
+import (
+	"apiote.xyz/p/szczanieckiej/traffic"
+)
 
 func MakeLineResponse(line traffic.Line, context traffic.Context, t *traffic.Traffic, accept uint) (LineResponse, error) {
 	switch accept {




diff --git a/server/route_line.go b/server/route_line.go
index 0a9ad73e5d7078fd3dd552363e4610e6d8e606b5..2e58663b43be11cc0dc47a85b0549d6eae7f82be 100644
--- a/server/route_line.go
+++ b/server/route_line.go
@@ -82,12 +82,13 @@ 			r: r,
 			t: t,
 			c: cfg,
 			a: accept,
+			f: feedName,
 		},
 	}
 	result := gott.R[AbstractHandlerVars]{
 		S: handlerVars,
 	}
-	result.
+	result = result.
 		Map(splitPath).
 		Tee(checkLinePath).
 		Bind(getVersionCode).
@@ -95,7 +96,8 @@ 		Map(createContext).
 		Bind(getLine).
 		Tee(checkEmptyLine).
 		Bind(makeLineResponse).
-		Bind(marshalLineResponse)
+		Bind(marshalLineResponse).
+		Tee(writeResponse)
 
 	return result.E
 }




diff --git a/traffic/access.go b/traffic/access.go
index 375544ffead8855c21aaafb24ad23f84b1160c30..51113da428bb4cf1873c2bf6727a84c8129873fd 100644
--- a/traffic/access.go
+++ b/traffic/access.go
@@ -85,9 +85,12 @@ 	schedules := map[string]struct{}{}
 	weekday := uint8(1 << time.Weekday())
 	date := time.Format(DateFormat)
 	for _, schedule := range calendar {
-		if schedule.StartDate <= date && date <= schedule.EndDate &&
-			(schedule.Weekdays&weekday != 0) {
-			schedules[schedule.Id] = struct{}{}
+		for _, dateRange := range schedule.DateRanges {
+			if dateRange.Start <= date && date <= dateRange.End &&
+				(dateRange.Weekdays&weekday != 0) {
+				schedules[schedule.Id] = struct{}{}
+				break
+			}
 		}
 	}
 	var err error




diff --git a/traffic/convert.go b/traffic/convert.go
index a49e3b03356915983b357d580834a3bc90b1e9b0..5c58ca183b0b3f002d67bae405fc2360f46ee203 100644
--- a/traffic/convert.go
+++ b/traffic/convert.go
@@ -83,6 +83,7 @@ 	ValidTillError      []error
 	tripHeadsigns       map[string]string
 	stopNames           map[string]string
 	feedInfo            FeedInfo
+	schedules           map[string]Schedule
 }
 
 // helper functions
@@ -285,8 +286,8 @@ 	return c, e
 }
 
 func convertCalendar(c feedConverter) (feedConverter, error) { // ( feedInfo -- >> calendar)
+	c.schedules = map[string]Schedule{}
 	path := c.TmpFeedPath
-	resultFile := c.TrafficCalendarFile
 	calendarFile, err := os.Open(filepath.Join(path, "calendar.txt"))
 	if err != nil {
 		return c, fmt.Errorf("while opening file: %w", err)
@@ -315,40 +316,37 @@ 		}
 		schedule.Id = record[fields["service_id"]]
 		startDate := record[fields["start_date"]]
 		endDate := record[fields["end_date"]]
-		schedule.StartDate = startDate[:4] + "-" + startDate[4:6] + "-" + startDate[6:]
-		schedule.EndDate = endDate[:4] + "-" + endDate[4:6] + "-" + endDate[6:]
+		schedule.DateRanges = []DateRange{
+			DateRange{
+				Start: startDate[:4] + "-" + startDate[4:6] + "-" + startDate[6:],
+				End:   endDate[:4] + "-" + endDate[4:6] + "-" + endDate[6:],
+			},
+		}
 		if record[fields["monday"]] == "1" {
-			schedule.Weekdays |= (1 << 1)
+			schedule.DateRanges[0].Weekdays |= (1 << 1)
 		}
 		if record[fields["tuesday"]] == "1" {
-			schedule.Weekdays |= (1 << 2)
+			schedule.DateRanges[0].Weekdays |= (1 << 2)
 		}
 		if record[fields["wednesday"]] == "1" {
-			schedule.Weekdays |= (1 << 3)
+			schedule.DateRanges[0].Weekdays |= (1 << 3)
 		}
 		if record[fields["thursday"]] == "1" {
-			schedule.Weekdays |= (1 << 4)
+			schedule.DateRanges[0].Weekdays |= (1 << 4)
 		}
 		if record[fields["friday"]] == "1" {
-			schedule.Weekdays |= (1 << 5)
+			schedule.DateRanges[0].Weekdays |= (1 << 5)
 		}
 		if record[fields["saturday"]] == "1" {
-			schedule.Weekdays |= (1 << 6)
+			schedule.DateRanges[0].Weekdays |= (1 << 6)
 		}
 		if record[fields["sunday"]] == "1" {
-			schedule.Weekdays |= (1 << 0)
-			schedule.Weekdays |= (1 << 7)
-		}
-		bytes, err := bare.Marshal(&schedule)
-		if err != nil {
-			return c, fmt.Errorf("while marshalling: %w", err)
+			schedule.DateRanges[0].Weekdays |= (1 << 0)
+			schedule.DateRanges[0].Weekdays |= (1 << 7)
 		}
-		_, err = resultFile.Write(bytes)
-		if err != nil {
-			return c, fmt.Errorf("while writing: %w", err)
-		}
+		c.schedules[schedule.Id] = schedule
 
-		scheduleStart, err := time.ParseInLocation("2006-01-02", schedule.StartDate, c.Feed.GetLocation())
+		scheduleStart, err := time.ParseInLocation("2006-01-02", schedule.DateRanges[0].Start, c.Feed.GetLocation())
 		if err != nil {
 			c.ValidFromError = append(c.ValidFromError, err)
 		}
@@ -357,7 +355,7 @@ 			c.ValidFrom = scheduleStart
 			c.feedInfo.ValidSince = scheduleStart.Format("20060102")
 		}
 
-		scheduleEnd, err := time.ParseInLocation("2006-01-02", schedule.EndDate, c.Feed.GetLocation())
+		scheduleEnd, err := time.ParseInLocation("2006-01-02", schedule.DateRanges[0].End, c.Feed.GetLocation())
 		if err != nil {
 			c.ValidTillError = append(c.ValidTillError, err)
 		}
@@ -371,7 +369,6 @@ }
 
 func convertCalendarDates(c feedConverter) (feedConverter, error) {
 	path := c.TmpFeedPath
-	resultFile := c.TrafficCalendarFile
 	datesFile, err := os.Open(filepath.Join(path, "calendar_dates.txt"))
 	if err != nil {
 		return c, fmt.Errorf("while opening file: %w", err)
@@ -389,7 +386,6 @@ 		fields[headerField] = i
 	}
 
 	for {
-		schedule := Schedule{}
 		record, err := r.Read()
 		if err == io.EOF {
 			break
@@ -398,42 +394,132 @@ 		if err != nil {
 			return c, fmt.Errorf("while reading a record: %w", err)
 		}
 		if record[fields["exception_type"]] == "1" {
+			id := record[fields["service_id"]]
+			schedule := c.schedules[id]
+
 			date := record[fields["date"]]
-			schedule.Id = record[fields["service_id"]]
-			schedule.StartDate = date[:4] + "-" + date[4:6] + "-" + date[6:]
-			schedule.EndDate = date[:4] + "-" + date[4:6] + "-" + date[6:]
-			schedule.Weekdays = 0xff
+			dateRange := DateRange{
+				Start:    date[:4] + "-" + date[4:6] + "-" + date[6:],
+				End:      date[:4] + "-" + date[4:6] + "-" + date[6:],
+				Weekdays: 0xff,
+			}
+			if len(schedule.DateRanges) == 0 {
+				schedule.Id = id
+				schedule.DateRanges = []DateRange{dateRange}
+			} else {
+				inserted := false
+				for i, dr := range schedule.DateRanges {
+					if dateRange.Start < dr.Start {
+						newDateRanges := append(schedule.DateRanges[:i], dateRange)
+						newDateRanges = append(newDateRanges, schedule.DateRanges[i:]...)
+						schedule.DateRanges = newDateRanges
+						inserted = true
+						break
+					}
+				}
+				if !inserted {
+					schedule.DateRanges = append(schedule.DateRanges, dateRange)
+				}
+			}
+
+			c.schedules[schedule.Id] = schedule
+
+			scheduleStart, err := time.ParseInLocation("2006-01-02", schedule.DateRanges[0].Start, c.Feed.GetLocation())
+			if err != nil {
+				c.ValidFromError = append(c.ValidFromError, err)
+			}
+			if err == nil && (c.ValidFrom.IsZero() || scheduleStart.Before(c.ValidFrom)) {
+				c.ValidFrom = scheduleStart
+				c.feedInfo.ValidSince = scheduleStart.Format("20060102")
+			}
+
+			scheduleEnd, err := time.ParseInLocation("2006-01-02", schedule.DateRanges[0].End, c.Feed.GetLocation())
+			if err != nil {
+				c.ValidTillError = append(c.ValidTillError, err)
+			}
+			if err == nil && (c.ValidTill.IsZero() || scheduleEnd.After(c.ValidTill)) {
+				c.ValidTill = scheduleEnd
+				c.feedInfo.ValidTill = scheduleEnd.Format("20060102")
+			}
 		} else {
-			continue
+			date := record[fields["date"]]
+			formatedDate := date[:4] + "-" + date[4:6] + "-" + date[6:]
+			scheduleToEdit := c.schedules[record[fields["service_id"]]]
+
+			newDateRanges := []DateRange{}
+			for i := 0; i < len(scheduleToEdit.DateRanges); i++ {
+				dateRange := scheduleToEdit.DateRanges[i]
+
+				if dateRange.Start == formatedDate {
+					d, _ := time.ParseInLocation("2006-01-02", dateRange.Start, c.Feed.GetLocation())
+					dateRange.Start = d.AddDate(0, 0, 1).Format("2006-01-02")
+					if dateRange.Start <= dateRange.End {
+						newDateRanges = append(newDateRanges, dateRange)
+					}
+					continue
+				}
+
+				if dateRange.Start < formatedDate && formatedDate < dateRange.End {
+					d, _ := time.ParseInLocation("2006-01-02", formatedDate, c.Feed.GetLocation())
+					range1 := DateRange{dateRange.Start, d.AddDate(0, 0, -1).Format("2006-01-02"), dateRange.Weekdays}
+					range2 := DateRange{d.AddDate(0, 0, 1).Format("2006-01-02"), dateRange.End, dateRange.Weekdays}
+					newDateRanges = append(newDateRanges, range1)
+					newDateRanges = append(newDateRanges, range2)
+					continue
+				}
+
+				if formatedDate == dateRange.End {
+					d, _ := time.ParseInLocation("2006-01-02", dateRange.End, c.Feed.GetLocation())
+					dateRange.End = d.AddDate(0, 0, -1).Format("2006-01-02")
+					newDateRanges = append(newDateRanges, dateRange)
+					continue
+				}
+
+				newDateRanges = append(newDateRanges, dateRange)
+			}
+
+			scheduleToEdit.DateRanges = newDateRanges
+			c.schedules[record[fields["service_id"]]] = scheduleToEdit
 		}
+
+	}
+	return c, nil
+}
+
+func checkAnyCalendarConverted(c feedConverter) error {
+	if len(c.schedules) == 0 {
+		return fmt.Errorf("no calendar converted")
+	}
+	return nil
+}
+
+func saveSchedules(c feedConverter) error {
+	resultFile := c.TrafficCalendarFile
+
+	schedulesArray := make([]Schedule, len(c.schedules))
+	i := 0
+	for _, schedule := range c.schedules {
+		schedulesArray[i] = schedule
+		i++
+	}
+	sort.Slice(schedulesArray, func(i, j int) bool {
+		return schedulesArray[i].DateRanges[0].Start < schedulesArray[j].DateRanges[0].Start
+	})
+
+	for _, schedule := range schedulesArray {
 		bytes, err := bare.Marshal(&schedule)
 		if err != nil {
-			return c, fmt.Errorf("while marshalling: %w", err)
+			return fmt.Errorf("while marshalling: %w", err)
 		}
 		_, err = resultFile.Write(bytes)
 		if err != nil {
-			return c, fmt.Errorf("while writing: %w", err)
+			return fmt.Errorf("while writing: %w", err)
 		}
+	}
 
-		scheduleStart, err := time.ParseInLocation("2006-01-02", schedule.StartDate, c.Feed.GetLocation())
-		if err != nil {
-			c.ValidFromError = append(c.ValidFromError, err)
-		}
-		if err == nil && (c.ValidFrom.IsZero() || scheduleStart.Before(c.ValidFrom)) {
-			c.ValidFrom = scheduleStart
-			c.feedInfo.ValidSince = scheduleStart.Format("20060102")
-		}
+	c.schedules = map[string]Schedule{}
 
-		scheduleEnd, err := time.ParseInLocation("2006-01-02", schedule.EndDate, c.Feed.GetLocation())
-		if err != nil {
-			c.ValidTillError = append(c.ValidTillError, err)
-		}
-		if err == nil && (c.ValidTill.IsZero() || scheduleEnd.After(c.ValidTill)) {
-			c.ValidTill = scheduleEnd
-			c.feedInfo.ValidTill = scheduleEnd.Format("20060102")
-		}
-	}
-	return c, nil
+	return nil
 }
 
 func saveFeedInfo(c feedConverter) error {
@@ -454,14 +540,6 @@ 		return fmt.Errorf("while writing: %w", err)
 	}
 
 	return nil
-}
-
-func checkAnyCalendarConverted(c feedConverter) error {
-	info, err := c.TrafficCalendarFile.Stat()
-	if err == nil && info.Size() == 0 {
-		return fmt.Errorf("no calendar converted")
-	}
-	return err
 }
 
 func closeTrafficCalendarFile(c feedConverter, e error) (feedConverter, error) {
@@ -946,7 +1024,15 @@ 			graph.StopCodes = append(graph.StopCodes, stop.Code)
 		}
 		if previousTripID != tripID {
 			// first of current trip
-			graph.NextNodes[-1] = append(graph.NextNodes[-1], current)
+			connectionDone := false
+			for _, n := range graph.NextNodes[-1] {
+				if n == current {
+					connectionDone = true
+				}
+			}
+			if !connectionDone {
+				graph.NextNodes[-1] = append(graph.NextNodes[-1], current)
+			}
 		} else {
 			// second <- first to last <- penultimate of current trip
 			connectionDone := false
@@ -1337,6 +1423,7 @@ 			Recover(recoverCalendar).
 			Bind(convertCalendarDates).
 			Recover(recoverCalendar).
 			Tee(checkAnyCalendarConverted).
+			Tee(saveSchedules).
 			Tee(saveFeedInfo).
 			Recover(closeTrafficCalendarFile).
 			Bind(convertDepartures).




diff --git a/traffic/structs_gen.go b/traffic/structs_gen.go
index 9bd9ff49d7174901352a521fdee6cb3beefa02d5..bfd9eefced8b3b4b7452e7e0991ea2a21657ca9c 100644
--- a/traffic/structs_gen.go
+++ b/traffic/structs_gen.go
@@ -223,10 +223,8 @@ 	return bare.Marshal(t)
 }
 
 type Schedule struct {
-	Id        string `bare:"id"`
-	Weekdays  uint8  `bare:"weekdays"`
-	StartDate string `bare:"startDate"`
-	EndDate   string `bare:"endDate"`
+	Id         string      `bare:"id"`
+	DateRanges []DateRange `bare:"dateRanges"`
 }
 
 func (t *Schedule) Decode(data []byte) error {
@@ -234,6 +232,20 @@ 	return bare.Unmarshal(data, t)
 }
 
 func (t *Schedule) Encode() ([]byte, error) {
+	return bare.Marshal(t)
+}
+
+type DateRange struct {
+	Start    string `bare:"start"`
+	End      string `bare:"end"`
+	Weekdays uint8  `bare:"weekdays"`
+}
+
+func (t *DateRange) Decode(data []byte) error {
+	return bare.Unmarshal(data, t)
+}
+
+func (t *DateRange) Encode() ([]byte, error) {
 	return bare.Marshal(t)
 }