320 lines
7.3 KiB
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
|
|
}
|