From a74f6e024ff55b6348dce9689856948c6359385d Mon Sep 17 00:00:00 2001 From: Kaya-Sem Date: Sun, 18 Aug 2024 20:59:15 +0200 Subject: [PATCH] fix: correct relative time calculation with delay --- cmd/api/connection.go | 20 ++++++++++- cmd/api/timetableDepartureStruct.go | 22 ++++++++++++ cmd/tables/connectionTable.go | 5 +-- cmd/tables/tableUtil.go | 50 ++++++++++++++------------- cmd/tables/tableUtil_test.go | 53 +++++++++++++++++++++++++++++ cmd/tables/timetableTable.go | 5 +-- 6 files changed, 127 insertions(+), 28 deletions(-) create mode 100644 cmd/tables/tableUtil_test.go diff --git a/cmd/api/connection.go b/cmd/api/connection.go index 3e0b863..47a9189 100644 --- a/cmd/api/connection.go +++ b/cmd/api/connection.go @@ -5,6 +5,8 @@ import ( "fmt" "io" "net/http" + "os" + "strconv" ) // GetConnections fetches the connection data from the API and returns the response body as a byte slice. @@ -41,7 +43,23 @@ func ParseConnections(body []byte) ([]Connection, error) { return result.Connection, nil } -// Struct definitions remain the same. +func (c Connection) GetDelayInSeconds() int { + delay, err := strconv.Atoi(c.Departure.Delay) + + if err != nil { + fmt.Printf("Error converting delay: %s", c.Departure.Time) + os.Exit(1) + } + return delay +} + +func (c Connection) GetUnixDepartureTime() int { + depTime, err := strconv.Atoi(c.Departure.Time) + if err != nil { + fmt.Println("Error converting departuretime: %s", c.Departure.Time) + } + return depTime +} type ConnectionResult struct { Connection []Connection `json:"connection"` diff --git a/cmd/api/timetableDepartureStruct.go b/cmd/api/timetableDepartureStruct.go index 596bef0..5b9eb80 100644 --- a/cmd/api/timetableDepartureStruct.go +++ b/cmd/api/timetableDepartureStruct.go @@ -1,5 +1,27 @@ package api +import ( + "fmt" + "strconv" +) + +func (d TimetableDeparture) GetUnixDepartureTime() int { + time, err := strconv.Atoi(d.Time) + if err != nil { + fmt.Println("Error converting departure time: %s", d.Time) + } + + return time +} + +func (d TimetableDeparture) GetDelayInSeconds() int { + delay, err := strconv.Atoi(d.Delay) + if err != nil { + fmt.Println("Error converting delay: %s", d.Delay) + } + return delay +} + type StationTimetableResponse struct { Version string `json:"version"` Timestamp string `json:"timestamp"` diff --git a/cmd/tables/connectionTable.go b/cmd/tables/connectionTable.go index 3f11090..ed18901 100644 --- a/cmd/tables/connectionTable.go +++ b/cmd/tables/connectionTable.go @@ -23,12 +23,13 @@ func (m connectionTableModel) Init() tea.Cmd { return nil } func getDetailedConnectionInfo(c api.Connection) string { return fmt.Sprintf(` -Detailed info: +Departure in %s Destination: %s Track: %s Departure Time: %s Vehicle: %s `, + CalculateHumanRelativeTime(c), c.Departure.Station, c.Departure.Platform, cmd.UnixToHHMM(c.Departure.Time), @@ -44,7 +45,7 @@ func (m *connectionTableModel) updateSelectedDetails() { m.selectedDetails = getDetailedConnectionInfo(selectedConnection) } else { - m.selectedDetails = "No row selected" // Should never really happen + m.selectedDetails = "Nothing found!" } } diff --git a/cmd/tables/tableUtil.go b/cmd/tables/tableUtil.go index 5411876..924afbf 100644 --- a/cmd/tables/tableUtil.go +++ b/cmd/tables/tableUtil.go @@ -2,22 +2,30 @@ package table import ( "fmt" - "github.com/charmbracelet/lipgloss" "time" + + "github.com/charmbracelet/lipgloss" ) +const () + const ( - BorderColor = "240" // gray - SelectedForeground = "15" // white - SelectedBackground = "97" // mauve + Gray = "240" + White = "15" + Mauve = "97" + BorderColor = Gray + SelectedForeground = White + SelectedBackground = Mauve tableHeight = 10 ) var ( - lowOccupancyStyle = lipgloss.NewStyle().Italic(true).Foreground(lipgloss.Color("2")) // green - mediumOccupancyStyle = lipgloss.NewStyle().Italic(true).Foreground(lipgloss.Color("214")) // orange - highOccupancyStyle = lipgloss.NewStyle().Italic(true).Foreground(lipgloss.Color("9")) // red - unknownOccupancyStyle = lipgloss.NewStyle().Italic(true).Faint(true).Italic(true) + baseOccupancyStyle = lipgloss.NewStyle().Italic(true) + + lowOccupancyStyle = baseOccupancyStyle.Copy().Foreground(lipgloss.Color("2")) // green + mediumOccupancyStyle = baseOccupancyStyle.Copy().Foreground(lipgloss.Color("214")) // orange + highOccupancyStyle = baseOccupancyStyle.Copy().Foreground(lipgloss.Color("9")) // red + unknownOccupancyStyle = baseOccupancyStyle.Copy().Faint(true) ) func styleOccupancy(s string) string { @@ -33,25 +41,21 @@ func styleOccupancy(s string) string { } } -// CalculateHumanRelativeTime used for calucating human-readable "from now" time. E.g 'in 20 minutes' -func CalculateHumanRelativeTime(departureTime string) string { - now := time.Now() +type timeable interface { + GetUnixDepartureTime() int + GetDelayInSeconds() int +} - depTime, err := time.Parse("15:04", departureTime) - if err != nil { - return "" - } +// TODO: add delay onto the time - // Combine the parsed time with today's date - depDateTime := time.Date(now.Year(), now.Month(), now.Day(), depTime.Hour(), depTime.Minute(), 0, 0, now.Location()) +func CalculateHumanRelativeTime(t timeable) string { + now := time.Now() - // If the departure time is earlier than now, assume it's for the next day - if depDateTime.Before(now) { - depDateTime = depDateTime.Add(24 * time.Hour) - } + depTime := time.Unix(int64(t.GetUnixDepartureTime()), 0) + depTime = depTime.Add(time.Duration(t.GetDelayInSeconds()) * time.Second) - // Calculate the duration between now and the departure time - duration := depDateTime.Sub(now) + // Calculate the duration between now and the adjusted departure time + duration := depTime.Sub(now) // Handle special cases if duration < 1*time.Minute { diff --git a/cmd/tables/tableUtil_test.go b/cmd/tables/tableUtil_test.go new file mode 100644 index 0000000..5e88aa9 --- /dev/null +++ b/cmd/tables/tableUtil_test.go @@ -0,0 +1,53 @@ +package table + +import ( + "testing" + "time" +) + +type mockTimeable struct { + unixTime int + delay int +} + +func (m mockTimeable) GetUnixDepartureTime() int { + return m.unixTime +} + +func (m mockTimeable) GetDelayInSeconds() int { + return m.delay +} + +// Test for CalculateHumanRelativeTime function +func TestCalculateHumanRelativeTime(t *testing.T) { + now := time.Now() + + tests := []struct { + unixTime int + delay int + expected string + }{ + // Event is happening now (no delay) + {int(now.Unix()), 0, "now"}, + // Event in 5 minutes (no delay) + {int(now.Add(5 * time.Minute).Unix()), 0, "4 min"}, + // Event in 1 hour (no delay) + {int(now.Add(1 * time.Hour).Unix()), 0, "59 min"}, + // Event in 1 hour and 30 minutes (no delay) + {int(now.Add(1*time.Hour + 30*time.Minute).Unix()), 0, "1 hour 29 min"}, + // Event in 2 hours (no delay) + {int(now.Add(2 * time.Hour).Unix()), 0, "1 hour 59 min"}, + // Event is 30 minutes ago but with 1-hour delay + {int(now.Add(-30 * time.Minute).Unix()), 3600, "29 min"}, + // Event in 45 minutes (30 minutes delay) + {int(now.Add(15 * time.Minute).Unix()), 1800, "44 min"}, + } + + for _, test := range tests { + mock := mockTimeable{unixTime: test.unixTime, delay: test.delay} + result := CalculateHumanRelativeTime(mock) + if result != test.expected { + t.Errorf("CalculateHumanRelativeTime(%v) = %s; want %s", mock, result, test.expected) + } + } +} diff --git a/cmd/tables/timetableTable.go b/cmd/tables/timetableTable.go index f4c3989..03b4b4f 100644 --- a/cmd/tables/timetableTable.go +++ b/cmd/tables/timetableTable.go @@ -22,7 +22,7 @@ type timetableTableModel struct { func (m timetableTableModel) Init() tea.Cmd { return nil } func getDetailedDepartureInfo(d api.TimetableDeparture) string { - relativeTime := CalculateHumanRelativeTime(d.Time) + relativeTime := CalculateHumanRelativeTime(d) return fmt.Sprintf(` Departure in: %s Track: %s @@ -47,7 +47,7 @@ func (m *timetableTableModel) updateSelectedDetails() { // Update the selected details including the relative time m.selectedDetails = getDetailedDepartureInfo(selectedDeparture) } else { - m.selectedDetails = "No row selected" // Should never really happen + m.selectedDetails = "Nothing found!" } } @@ -77,6 +77,7 @@ func (m timetableTableModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, teaCmd } +// TODO: export to consts var detailsBoxStyle = lipgloss.NewStyle().Padding(1) func (m timetableTableModel) View() string {