work/timewarrior.go

320 lines
7.3 KiB
Go

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
}