diff --git a/backend/sport/main.go b/backend/sport/main.go index 99657ce..64ffe81 100644 --- a/backend/sport/main.go +++ b/backend/sport/main.go @@ -2,7 +2,11 @@ package main import ( "errors" + "github.com/google/uuid" + "github.com/pocketbase/pocketbase/tools/types" + "htwkalender/model" "net/http" + "regexp" "strconv" "strings" "sync" @@ -18,10 +22,160 @@ import ( func main() { var sportCourseLinks = fetchAllAvailableSportCourses() - events := fetchHTWKSportCourses(sportCourseLinks) + sportEntries := fetchHTWKSportCourses(sportCourseLinks) + + for _, event := range sportEntries { + println(event.Title) + } + + events := formatEntriesToEvents(sportEntries) for _, event := range events { - println(event.Title) + println(event.Name) + } +} + +func formatEntriesToEvents(entries []SportEntry) []model.Event { + + var events []model.Event + + for i, entry := range entries { + + eventStarts, eventEnds := calculateEventStarts(entry.Details.DateRange.Start, entry.Details.DateRange.End, entry.Details.Cycle) + + for j := range eventStarts { + + start, _ := types.ParseDateTime(eventStarts[j].In(time.UTC)) + end, _ := types.ParseDateTime(eventEnds[j].In(time.UTC)) + + var event = model.Event{ + UUID: uuid.NewSHA1(uuid.NameSpaceDNS, []byte(entry.Title+strconv.FormatInt(int64(i), 10))).String(), + Day: entry.Details.DateRange.Start.Weekday().String(), + Week: strconv.Itoa(23), + Start: start, + End: end, + Name: entry.Title, + EventType: entry.Details.Type, + Prof: entry.Details.CourseLead.Name, + Rooms: entry.Details.Location.Name, + Notes: entry.AdditionalNote, + BookedAt: "", + Course: "Sport", + Semester: checkSemester(entry.Details.DateRange.Start), + } + events = append(events, event) + } + + } + return events +} + +func calculateEventStarts(start time.Time, end time.Time, cycle string) ([]time.Time, []time.Time) { + + // start is the begin of the cycle e.g. 01.04.2020 + // end is the end of the cycle e.g. 30.09.2020 + // cycle is the day and timespan (e.g. "Mo 18:00-20:00") + + // check if start is before end + if start.After(end) { + return nil, nil + } + + // check if cycle is valid + if !checkCycle(cycle) { + return nil, nil + } + + var weekDay = cycle[0:2] + // match weekday to time.Weekday (e.g. "Mo" -> time.Monday) + var weekDayInt int + + switch weekDay { + case "Mo": + weekDayInt = 1 + case "Di": + weekDayInt = 2 + case "Mi": + weekDayInt = 3 + case "Do": + weekDayInt = 4 + case "Fr": + weekDayInt = 5 + case "Sa": + weekDayInt = 6 + case "So": + weekDayInt = 0 + } + + // get every date matching the weekday in the cycle between start and end + var eventDates []time.Time + for d := start; d.Before(end); d = d.AddDate(0, 0, 1) { + if d.Weekday() == time.Weekday(weekDayInt) { + eventDates = append(eventDates, d) + } + } + + // add hours and minutes to the dates in eventDates + // array of tuple of start and end times + var eventStartsWithTime []time.Time + var eventEndWithTime []time.Time + + for _, eventStart := range eventDates { + timeRegExp, _ := regexp.Compile("[0-9]{2}:[0-9]{2}") + times := timeRegExp.FindAllString(cycle, 2) + startHour, _ := strconv.Atoi(times[0][0:2]) + startMinute, _ := strconv.Atoi(times[0][3:5]) + + endHour, _ := strconv.Atoi(times[1][0:2]) + endMinute, _ := strconv.Atoi(times[1][3:5]) + eventStartsWithTime = append(eventStartsWithTime, time.Date(eventStart.Year(), eventStart.Month(), eventStart.Day(), startHour, startMinute, 0, 0, eventStart.Location())) + eventEndWithTime = append(eventEndWithTime, time.Date(eventStart.Year(), eventStart.Month(), eventStart.Day(), endHour, endMinute, 0, 0, eventStart.Location())) + } + + return eventStartsWithTime, eventEndWithTime +} + +func checkCycle(cycle string) bool { + + // check if cycle is valid + if len(cycle) < 12 { + return false + } + + // check if cycle has a weekday + weekDay := cycle[0:2] + if weekDay != "Mo" && weekDay != "Di" && weekDay != "Mi" && weekDay != "Do" && weekDay != "Fr" && weekDay != "Sa" && weekDay != "So" { + return false + } + + // check if cycle has a timespan + timeSpan := cycle[3:12] + if len(timeSpan) != 9 { + return false + } + + // check if timespan has a start and end time + startTime := timeSpan[0:5] + endTime := timeSpan[6:9] + if len(startTime) != 5 || len(endTime) != 3 { + return false + } + + // check if start time is before end time + if startTime > endTime { + return false + } + + return true + +} + +// check if ws or ss +func checkSemester(date time.Time) string { + if date.Month() >= 4 && date.Month() <= 9 { + return "ss" + } else { + return "ws" } } @@ -52,7 +206,7 @@ func fetchAllAvailableSportCourses() []string { // fetchAllHTWKSportCourses fetches all sport courses from the given links. // to speed up the process, it uses multithreading. -func fetchHTWKSportCourses(links []string) []Event { +func fetchHTWKSportCourses(links []string) []SportEntry { //multithreaded webpage requests to speed up the process @@ -75,7 +229,7 @@ func fetchHTWKSportCourses(links []string) []Event { } wg.Wait() - var events []Event + var events []SportEntry for _, doc := range htmlPageArray { if doc != nil { @@ -111,15 +265,15 @@ func htmlRequest(url string) (*goquery.Document, error) { // If the sport course exists, it will return the sport course. // goquery is used to parse the html. The html structure is not very consistent, so it is hard to parse. // May be improved in the future. -func fetchHtwkSportCourse(doc *goquery.Document) ([]Event, error) { - var events []Event +func fetchHtwkSportCourse(doc *goquery.Document) ([]SportEntry, error) { + var events []SportEntry if doc.Find("h1").Text() == "Aktuelle Sportangebote" { return nil, errors.New("not a sport course page") } doc.Find(".eventHead").Each(func(i int, s *goquery.Selection) { - var event Event + var event SportEntry var details EventDetails fullTitle := strings.TrimSpace(s.Find("h3").Text()) diff --git a/backend/sport/sportFetcherModel.go b/backend/sport/sportFetcherModel.go index 785e582..d7b82ce 100644 --- a/backend/sport/sportFetcherModel.go +++ b/backend/sport/sportFetcherModel.go @@ -4,8 +4,8 @@ import "time" // MODELS -// Event represents the overall event details. -type Event struct { +// SportEntry represents the overall event details. +type SportEntry struct { Title string Details EventDetails AdditionalNote string