// TODO: docstring package main import ( "encoding/csv" "fmt" "log" "os" "strconv" "text/template" "time" ) var timeCSVFormat = "2.1.06 15:04" // input format var timeXMLDateFormat = "2006-01-02" // frab xml format for dates var timeXMLTimeFormat = "15:04" // frab xml format for times const csvColTitle = 0 const csvColRoom = 1 const csvColStart = 2 const csvColEnd = 3 const csvColSpeaker = 4 const csvColLang = 5 type frabSchedule struct { Conf conference Days []day } type conference struct { Title string Start string // date (yyyy-mm-dd) End string // date (yyyy-mm-dd) Days int } type day struct { Index int Date string // date (yyyy-mm-dd) Start string // timedate (RFC3339) End string // timedate (RFC3339) Rooms []room } type room struct { Name string Events []event } type event struct { ID int GUID string // autogen UUID Room string Title string Date string // timedate (RFC3339) Start string // time (hh:mm) Duration string // time (hh:mm) Language string Persons []person } type person struct { ID int Name string } func fmtDuration(d time.Duration) string { d = d.Round(time.Minute) h := d / time.Hour d -= h * time.Hour m := d / time.Minute return fmt.Sprintf("%02d:%02d", h, m) } func main() { fmt.Println("Conference Schedule: CSV to Frab (Infobeamer) Converter") /* * Data Input */ // read CSV contents csvFile, err := os.Open("schedule.csv") if err != nil { log.Fatal("Error opening CSV file:\n", err) } csvReader := csv.NewReader(csvFile) csvReader.Comma = ';' // MS excel default export format records, err := csvReader.ReadAll() if err != nil { log.Fatal("Error reading CSV file:\n", err) } // omit title row csvRecords := records[1:] /* * Data Conversion */ roomsList := make(map[time.Time]map[string]int) // map of day's start time to map of room names var conferenceStart time.Time var conferenceEnd time.Time loc, _ := time.LoadLocation("Europe/Berlin") for idx, row := range csvRecords { // parse event times eventStart, err := time.ParseInLocation(timeCSVFormat, row[csvColStart], loc) if err != nil { log.Fatal("Error parsing start date in CSV file, line ", idx+1, ":\n", err) } eventEnd, err := time.ParseInLocation(timeCSVFormat, row[csvColEnd], loc) if err != nil { log.Fatal("Error parsing end date in CSV file, line ", idx+1, ":\n", err) } // create unique list of the rooms for each day dayStart := eventStart.Truncate(24 * time.Hour) if roomsList[dayStart] == nil { roomsList[dayStart] = make(map[string]int) } roomsList[dayStart][row[csvColRoom]] = 1 // get conference start and end date if idx == 0 { conferenceStart = eventStart conferenceEnd = eventEnd } if eventStart.Before(conferenceStart) { conferenceStart = eventStart } if eventEnd.After(conferenceEnd) { conferenceEnd = eventEnd } } conferenceStart = conferenceStart.Truncate(24 * time.Hour) conferenceEnd = conferenceEnd.Truncate(24 * time.Hour).Add(24 * time.Hour) // convert csv entries into event and sort in days events := make(map[int][]event) // map of day index to day's events eventID := 100 speakerID := 1 confDays := make(map[int]day) // map of day index to day structure for idx, row := range csvRecords { eventStart, err := time.ParseInLocation(timeCSVFormat, row[csvColStart], loc) if err != nil { log.Fatal("Error parsing start date in CSV file, line ", idx+1, ":\n", err) } eventEnd, err := time.ParseInLocation(timeCSVFormat, row[csvColEnd], loc) if err != nil { log.Fatal("Error parsing end date in CSV file, line ", idx+1, ":\n", err) } // create event struct and sort into day map var eventSpeaker []person if row[csvColSpeaker] != "" { eventSpeaker = append(eventSpeaker, person{speakerID, row[csvColSpeaker]}) } dayIdx := int(eventStart.Sub(conferenceStart).Hours()/24) + 1 eventGUID := strconv.Itoa(dayIdx) + "-" + strconv.Itoa(eventID) eventDateStr := eventStart.Format(time.RFC3339) eventStartStr := eventStart.Format(timeXMLTimeFormat) eventDurationStr := fmtDuration(eventEnd.Sub(eventStart)) events[dayIdx] = append(events[dayIdx], event{eventID, eventGUID, row[csvColRoom], row[csvColTitle], eventDateStr, eventStartStr, eventDurationStr, row[csvColLang], eventSpeaker}) // create days if _, ok := confDays[dayIdx]; !ok { dayDateStr := eventStart.Format(timeXMLDateFormat) dayStart := eventStart.Truncate(24 * time.Hour) dayEnd := dayStart.Add(24 * time.Hour) dayStartStr := dayStart.Format(time.RFC3339) dayEndStr := dayEnd.Format(time.RFC3339) var dayRooms []room for roomName := range roomsList[dayStart] { dayRooms = append(dayRooms, room{roomName, nil}) } confDays[dayIdx] = day{dayIdx, dayDateStr, dayStartStr, dayEndStr, dayRooms} } eventID++ } // sort events to rooms and day structures for dayIdx, eventDay := range events { for _, event := range eventDay { for roomIdx, roomName := range confDays[dayIdx].Rooms { if roomName.Name == event.Room { confDays[dayIdx].Rooms[roomIdx].Events = append(confDays[dayIdx].Rooms[roomIdx].Events, event) } } } } // new conference confDurationDays := int(conferenceEnd.Sub(conferenceStart).Hours() / 24) conf := conference{"Wälderhaus Veranstaltung", conferenceStart.Format(time.RFC3339), conferenceEnd.Format(time.RFC3339), confDurationDays} var days []day for _, d := range confDays { days = append(days, d) } sched := frabSchedule{conf, days} /* * Data Export */ // open output file xmlOutput, err := os.Create("schedule.xml") defer xmlOutput.Close() if err != nil { log.Fatal("Error opening XML file for output:\n", err) } t := template.New("Frab XML Template") t, err = t.ParseFiles("tmpl/schedule.xml") if err != nil { log.Fatal("Error parsing XML template:\n", err) } err = t.ExecuteTemplate(xmlOutput, "schedule.xml", sched) if err != nil { log.Println("Error executing XML template:\n", err) } /* * Print Summary */ fmt.Println("- Conference Start:", conferenceStart) fmt.Println("- Conference End :", conferenceEnd) fmt.Println("- Days :", confDurationDays) fmt.Println("- Events :", eventID-100) // fmt.Println("Dump days:") // for _, day := range days { // fmt.Println("Day", day.Index) // for _, room := range day.Rooms { // fmt.Println(" Room", room.Name) // for _, event := range room.Events { // fmt.Println(" Event", event) // } // } // } fmt.Println("done") }