package main import ( "fmt" "os" "os/exec" "sort" "strconv" "strings" "time" "github.com/xuri/excelize/v2" ) type TimeWarrior struct{} func NewTimeWarrior() *TimeWarrior { return &TimeWarrior{} } func (t *TimeWarrior) runCommand(args ...string) error { cmd := exec.Command("timew", args...) cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr return cmd.Run() } func (t *TimeWarrior) StartWork() error { return t.runCommand("start", "work") } func (t *TimeWarrior) StopWork() error { return t.runCommand("stop", "work") } func (t *TimeWarrior) StartBreak() error { return t.runCommand("track", "break") } func (t *TimeWarrior) StopBreak() error { return t.runCommand("track", "work") } func (t *TimeWarrior) ShowSummary(period string) error { return t.runCommand("summary", period, "work") } type TimeEntry struct { Week string Date string Day string Start string End string Duration string Tag string } func (t *TimeWarrior) ExportSummary() error { fmt.Println("Export Timetable") exportCommand := exec.Command("timew", "summary", ":year") output, err := exportCommand.Output() if err != nil { return err } lines := strings.Split(string(output), "\n") if len(lines) > 2 { var entries []TimeEntry for _, line := range lines[3 : len(lines)-4] { words := strings.Fields(line) newLine := strings.Join(words, " ") parts := strings.Split(strings.TrimSpace(newLine), " ") entry := TimeEntry{} switch len(parts) { case 4, 5: entry.Tag = parts[0] entry.Start = parts[1] entry.End = parts[2] entry.Duration = parts[3] case 7, 8: entry.Week = parts[0] entry.Date = parts[1] entry.Day = parts[2] entry.Tag = parts[3] entry.Start = parts[4] entry.End = parts[5] entry.Duration = parts[6] default: fmt.Println("Unknown length") } entries = append(entries, entry) } dailySummary := make(map[string]*DailySummary) var currentDate string for _, entry := range entries { if entry.Date != "" { currentDate = entry.Date } if currentDate == "" { continue } if _, exists := dailySummary[currentDate]; !exists { dailySummary[currentDate] = &DailySummary{ Date: currentDate, Day: entry.Day, } } summary := dailySummary[currentDate] switch strings.ToLower(entry.Tag) { case "work": if summary.WorkStart == "" { summary.WorkStart = entry.Start } summary.WorkEnd = entry.End case "break": duration, _ := parseDuration(entry.Duration) summary.BreakDuration += duration case "uni", "free", "krank", "urlaub": summary.Tag = entry.Tag } } var excelEntries []ExcelEntry for _, summary := range dailySummary { entry := ExcelEntry{ Date: summary.Date, Day: summary.Day, WorkStart: summary.WorkStart, WorkEnd: summary.WorkEnd, BreakDuration: formatDuration(summary.BreakDuration), Tag: summary.Tag, } excelEntries = append(excelEntries, entry) } for _, entry := range excelEntries { fmt.Printf("%+v\n", entry) } err = writeExcelSheet(excelEntries) if err != nil { return err } } else { fmt.Println("No Data") } return nil } type DailySummary struct { Date string Day string WorkStart string WorkEnd string BreakDuration time.Duration Tag string } type ExcelEntry struct { Date string Day string WorkStart string WorkEnd string BreakDuration string Tag string } func parseDuration(s string) (time.Duration, error) { parts := strings.Split(s, ":") if len(parts) != 3 { return 0, fmt.Errorf("invalid duration format: %s", s) } hours, err := strconv.Atoi(parts[0]) if err != nil { return 0, err } minutes, err := strconv.Atoi(parts[1]) if err != nil { return 0, err } seconds, err := strconv.Atoi(parts[2]) if err != nil { return 0, err } duration, err := time.ParseDuration(fmt.Sprintf("%vh%vm%vs", hours, minutes, seconds)) if err != nil { return 0, err } return duration, nil } func formatDuration(d time.Duration) string { h := d / time.Hour d -= h * time.Hour m := d / time.Minute d -= m * time.Minute s := d / time.Second return fmt.Sprintf("%d:%02d:%02d", h, m, s) } func writeExcelSheet(entries []ExcelEntry) error { sort.Slice(entries, func(i, j int) bool { dateI, _ := time.Parse("2006-01-02", entries[i].Date) dateJ, _ := time.Parse("2006-01-02", entries[j].Date) return dateI.Before(dateJ) }) sheetName := fmt.Sprint(time.Now().Year()) f := excelize.NewFile() defer func() { if err := f.Close(); err != nil { fmt.Println(err) } }() index, err := f.NewSheet(sheetName) if err != nil { return err } // Überschriften setzen f.SetCellValue(sheetName, "B1", "Arbeitszeiten "+sheetName) f.SetCellValue(sheetName, "B3", "Datum") f.SetCellValue(sheetName, "D3", "Arbeitszeit") f.SetCellValue(sheetName, "G3", "Summe") f.SetCellValue(sheetName, "I3", "Pause") f.SetCellValue(sheetName, "J3", "Summe") f.SetCellValue(sheetName, "K3", "Soll") f.SetCellValue(sheetName, "L3", "Saldo") f.SetCellValue(sheetName, "N3", "Saldo") f.SetCellValue(sheetName, "D4", "von") f.SetCellValue(sheetName, "E4", "bis") f.SetCellValue(sheetName, "G4", "brutto") f.SetCellValue(sheetName, "J4", "netto") f.SetCellValue(sheetName, "L4", "Tag") f.SetCellValue(sheetName, "N4", "total") strStyle := "hh:mm" timeStyle, err := f.NewStyle(&excelize.Style{ CustomNumFmt: &strStyle, }) if err != nil { return err } for num, entry := range entries { var soll string switch entry.Day { case "Mon", "Tue": soll = "08:00" case "Wed": soll = "04:00" default: soll = "" } row := fmt.Sprint(num + 6) dateValue, _ := time.Parse("2006-01-02", entry.Date) f.SetCellValue(sheetName, "B"+row, dateValue) if entry.Tag == "" { startTime, _ := time.Parse("15:04:05", entry.WorkStart) endTime, _ := time.Parse("15:04:05", entry.WorkEnd) startExcel := float64(startTime.Hour())/24.0 + float64(startTime.Minute())/(24.0*60.0) endExcel := float64(endTime.Hour())/24.0 + float64(endTime.Minute())/(24.0*60.0) f.SetCellValue(sheetName, "D"+row, startExcel) f.SetCellStyle(sheetName, "D"+row, "D"+row, timeStyle) f.SetCellValue(sheetName, "E"+row, endExcel) f.SetCellStyle(sheetName, "E"+row, "E"+row, timeStyle) // Formeln setzen f.SetCellFormula(sheetName, "G"+row, fmt.Sprintf("E%d-D%d", num+6, num+6)) f.SetCellStyle(sheetName, "G"+row, "G"+row, timeStyle) f.SetCellValue(sheetName, "I"+row, entry.BreakDuration) f.SetCellFormula(sheetName, "J"+row, fmt.Sprintf("G%d-I%d", num+6, num+6)) f.SetCellStyle(sheetName, "J"+row, "J"+row, timeStyle) f.SetCellValue(sheetName, "K"+row, soll) f.SetCellStyle(sheetName, "K"+row, "K"+row, timeStyle) f.SetCellFormula(sheetName, "L"+row, fmt.Sprintf("J%d-K%d", num+6, num+6)) f.SetCellStyle(sheetName, "L"+row, "L"+row, timeStyle) } else { text := "" switch entry.Tag { case "uni": text = "Hochschule" case "urlaub": text = "Urlaub" default: text = "" } f.SetCellValue(sheetName, "D"+row, text) } f.SetCellFormula(sheetName, "N"+row, fmt.Sprintf("N%d+L%d", num+5, num+6)) f.SetCellStyle(sheetName, "N"+row, "N"+row, timeStyle) } f.SetActiveSheet(index) if err := f.SaveAs("Test.xlsx"); err != nil { fmt.Println(err) } return nil }