diff --git a/go.mod b/go.mod index ca74baa..6f8e48e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/cnf/structhash v0.0.0-20201013183111-a92e111048cd github.com/google/uuid v1.1.2 github.com/gookit/color v1.3.1 + github.com/jinzhu/now v1.1.1 github.com/shopspring/decimal v1.2.0 github.com/spf13/cobra v1.0.0 github.com/tidwall/buntdb v1.1.2 diff --git a/go.sum b/go.sum index 3b66c02..c0f6daa 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= diff --git a/z/calendar.go b/z/calendar.go index af5ee22..44d3068 100644 --- a/z/calendar.go +++ b/z/calendar.go @@ -2,10 +2,10 @@ package z import ( "fmt" - "math" "time" - "github.com/gookit/color" "github.com/shopspring/decimal" + "github.com/jinzhu/now" + "github.com/gookit/color" ) type Statistic struct { @@ -14,93 +14,95 @@ type Statistic struct { Color (func(...interface {}) string) } +type WeekStatistics map[string][]Statistic + +type Week struct { + Statistics WeekStatistics +} + +type Month struct { + Name string + Weeks [5]Week +} + type Calendar struct { - + Months [12]Month } -func GetOutputBoxForNumber(number int, clr (func(...interface {}) string) ) (string) { - switch(number) { - case 0: return clr(" ") - case 1: return clr(" ▄") - case 2: return clr("▄▄") - case 3: return clr("▄█") - case 4: return clr("██") - } +func NewCalendar(entries []Entry) (Calendar, error) { + cal := Calendar{} - return clr(" ") -} + for _, entry := range entries { + endOfBeginDay := now.With(entry.Begin).EndOfDay() + sameDayHours := decimal.NewFromInt(0) + nextDayHours := decimal.NewFromInt(0) -func GetOutputBarForHours(hours decimal.Decimal, stats []Statistic) ([]string) { - var bar = []string{ - color.FgGray.Render("····"), - color.FgGray.Render("····"), - color.FgGray.Render("····"), - color.FgGray.Render("····"), - color.FgGray.Render("····"), - color.FgGray.Render("····"), - } + /* + * Apparently the activity end is on a new day. + * This means we have to split the activity across two days. + */ + if endOfBeginDay.Before(entry.Finish) == true { + startOfFinishDay := now.With(entry.Finish).BeginningOfDay() - hoursInt := int((hours.Round(0)).IntPart()) - rest := ((hours.Round(0)).Mod(decimal.NewFromInt(4))).Round(0) - restInt := int(rest.IntPart()) + sameDayDuration := endOfBeginDay.Sub(entry.Begin) + sameDay := sameDayDuration.Hours() + sameDayHours = decimal.NewFromFloat(sameDay) - divisible := hoursInt - restInt - fullparts := divisible / 4 + nextDayDuration := entry.Finish.Sub(startOfFinishDay) + nextDay := nextDayDuration.Hours() + nextDayHours = decimal.NewFromFloat(nextDay) - colorsFull := make(map[int](func(...interface {}) string)) - colorsFullIdx := 0 - - colorFraction := color.FgWhite.Render - colorFractionPrevAmount := 0.0 - - for _, stat := range stats { - statHoursInt, _ := stat.Hours.Float64() - statRest := (stat.Hours.Round(0)).Mod(decimal.NewFromInt(4)) - statRestFloat, _ := statRest.Float64() - - if statRestFloat > colorFractionPrevAmount { - colorFractionPrevAmount = statRestFloat - colorFraction = stat.Color - } - - fmt.Printf("%f\n", statHoursInt) - fullColoredParts := int(math.Round(statHoursInt) / 4) - - if fullColoredParts == 0 && statHoursInt > colorFractionPrevAmount { - colorFractionPrevAmount = statHoursInt - colorFraction = stat.Color - } - - fmt.Printf("Full parts: %d\n", fullColoredParts) - for i := 0; i < fullColoredParts; i++ { - colorsFull[colorsFullIdx] = stat.Color - colorsFullIdx++ - } - } - - iColor := 0 - for i := (len(bar) - 1); i > (len(bar) - 1 - fullparts); i-- { - if iColor < colorsFullIdx { - bar[i] = " " + GetOutputBoxForNumber(4, colorsFull[iColor]) + " " - iColor++ } else { - bar[i] = " " + GetOutputBoxForNumber(4, colorFraction) + " " + sameDayDuration := entry.Finish.Sub(entry.Begin) + sameDay := sameDayDuration.Hours() + sameDayHours = decimal.NewFromFloat(sameDay) + } + + if sameDayHours.GreaterThan(decimal.NewFromInt(0)) { + month, weeknumber := GetISOWeekInMonth(entry.Begin) + month0 := month - 1 + weeknumber0 := weeknumber - 1 + weekday := entry.Begin.Weekday() + weekdayName := weekday.String()[:2] + + stat := Statistic{ + Hours: sameDayHours, + Project: entry.Project, + Color: color.FgCyan.Render, + } + + if cal.Months[month0].Weeks[weeknumber0].Statistics == nil { + cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics) + } + + cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName] = append(cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName], stat) + } + + if nextDayHours.GreaterThan(decimal.NewFromInt(0)) { + month, weeknumber := GetISOWeekInMonth(entry.Finish) + month0 := month - 1 + weeknumber0 := weeknumber - 1 + weekday := entry.Begin.Weekday() + weekdayName := weekday.String()[:2] + + stat := Statistic{ + Hours: nextDayHours, + Project: entry.Project, + Color: color.FgCyan.Render, + } + + if cal.Months[month0].Weeks[weeknumber0].Statistics == nil { + cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics) + } + + cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName] = append(cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName], stat) } } - if(restInt > 0) { - bar[(len(bar) - 1 - fullparts)] = " " + GetOutputBoxForNumber(restInt, colorFraction) + " " - } - - return bar + return cal, nil } -func (calendar *Calendar) GetCalendarWeek(timestamp time.Time) (int) { - var _, cw = timestamp.ISOWeek() - return cw -} - -func (calendar *Calendar) GetOutputForWeekCalendar(cw int, data map[string][]Statistic) (string) { +func (calendar *Calendar) GetOutputForWeekCalendar(date time.Time, month int, week int) (string) { var output string = "" var bars [][]string var totalHours = decimal.NewFromInt(0) @@ -109,16 +111,16 @@ func (calendar *Calendar) GetOutputForWeekCalendar(cw int, data map[string][]Sta for _, day := range days { var dayHours = decimal.NewFromInt(0) - for _, stat := range data[day] { + for _, stat := range calendar.Months[month].Weeks[week].Statistics[day] { dayHours = dayHours.Add(stat.Hours) totalHours = totalHours.Add(stat.Hours) } - bar := GetOutputBarForHours(dayHours, data[day]) + bar := GetOutputBarForHours(dayHours, calendar.Months[month].Weeks[week].Statistics[day]) bars = append(bars, bar) } - output = fmt.Sprintf("CW %02d %s H\n", cw, totalHours.StringFixed(2)) + output = fmt.Sprintf("CW %02d %s H\n", GetISOCalendarWeek(date), totalHours.StringFixed(2)) for row := 0; row < len(bars[0]); row++ { output = fmt.Sprintf("%s%2d │", output, ((6 - row) * 4)) for col := 0; col < len(bars); col++ { diff --git a/z/helpers.go b/z/helpers.go index 7e86313..755aeba 100644 --- a/z/helpers.go +++ b/z/helpers.go @@ -1,11 +1,11 @@ package z import ( - "fmt" "os/user" "regexp" "strconv" "time" + "math" "errors" ) @@ -53,6 +53,7 @@ func GetTimeFormat(timeStr string) (int) { return -1 } +// TODO: Use https://golang.org/pkg/time/#ParseDuration func RelToTime(timeStr string, ftId int) (time.Time, error) { var re = regexp.MustCompile(TimeFormats()[ftId]) gm := re.FindStringSubmatch(timeStr) @@ -100,29 +101,19 @@ func ParseTime(timeStr string) (time.Time, error) { } } -func OutputAppendRight(leftStr string, rightStr string, pad int) (string) { - var output string = "" - var rpos int = 0 +func GetISOCalendarWeek(date time.Time) (int) { + var _, cw = date.ISOWeek() + return cw +} - left := []rune(leftStr) - leftLen := len(left) - right := []rune(rightStr) - rightLen := len(right) - - for lpos := 0; lpos < leftLen; lpos++ { - if left[lpos] == '\n' || lpos == (leftLen - 1) { - output = fmt.Sprintf("%s%*s", output, pad, "") - for rpos = rpos; rpos < rightLen; rpos++ { - output = fmt.Sprintf("%s%c", output, right[rpos]) - if right[rpos] == '\n' { - rpos++ - break - } - } - continue - } - output = fmt.Sprintf("%s%c", output, left[lpos]) +func GetISOWeekInMonth(date time.Time) (month int, weeknumber int) { + if date.IsZero() { + return -1, -1 } - return output + newDay := (date.Day() - int(date.Weekday()) + 1) + addDay := (date.Day() - newDay) * -1 + changedDate := date.AddDate(0, 0, addDay) + + return int(changedDate.Month()), int(math.Ceil(float64(changedDate.Day()) / 7.0)); } diff --git a/z/stats.go b/z/stats.go index 695cda2..e887cb0 100644 --- a/z/stats.go +++ b/z/stats.go @@ -3,9 +3,10 @@ package z import ( "os" "fmt" + "time" "github.com/spf13/cobra" - "github.com/shopspring/decimal" - "github.com/gookit/color" + // "github.com/shopspring/decimal" + // "github.com/gookit/color" ) var statsCmd = &cobra.Command{ @@ -13,55 +14,29 @@ var statsCmd = &cobra.Command{ Short: "Display activity statistics", Long: "Display statistics on all tracked activities.", Run: func(cmd *cobra.Command, args []string) { - // user := GetCurrentUser() + user := GetCurrentUser() - // entries, err := database.ListEntries(user) - // if err != nil { - // fmt.Printf("%s %+v\n", CharError, err) - // os.Exit(1) - // } - - // for _, entry := range entries { - // fmt.Printf("%s\n", entry.GetOutput()) - // } - - var cal Calendar - - var data = make(map[string][]Statistic) - - data["Mo"] = []Statistic { - Statistic{ Hours: decimal.NewFromFloat(12.0), Project: "zeit", Color: color.FgRed.Render }, - Statistic{ Hours: decimal.NewFromFloat(3.5), Project: "blog", Color: color.FgGreen.Render }, - } - data["Tu"] = []Statistic { - Statistic{ Hours: decimal.NewFromFloat(2.25), Project: "zeit", Color: color.FgRed.Render }, - Statistic{ Hours: decimal.NewFromFloat(4.0), Project: "blog", Color: color.FgGreen.Render }, - } - data["We"] = []Statistic { - Statistic{ Hours: decimal.NewFromFloat(10.0), Project: "zeit", Color: color.FgRed.Render }, - Statistic{ Hours: decimal.NewFromFloat(1.5), Project: "blog", Color: color.FgGreen.Render }, - } - data["Th"] = []Statistic { - Statistic{ Hours: decimal.NewFromFloat(4.0), Project: "zeit", Color: color.FgRed.Render }, - Statistic{ Hours: decimal.NewFromFloat(4.5), Project: "blog", Color: color.FgGreen.Render }, - } - data["Fr"] = []Statistic { - Statistic{ Hours: decimal.NewFromFloat(0.5), Project: "zeit", Color: color.FgRed.Render }, - Statistic{ Hours: decimal.NewFromFloat(3.5), Project: "blog", Color: color.FgGreen.Render }, - } - data["Sa"] = []Statistic { - Statistic{ Hours: decimal.NewFromFloat(1.0), Project: "zeit", Color: color.FgRed.Render }, - Statistic{ Hours: decimal.NewFromFloat(1.0), Project: "blog", Color: color.FgGreen.Render }, - } - data["Su"] = []Statistic { - Statistic{ Hours: decimal.NewFromFloat(10.0), Project: "zeit", Color: color.FgRed.Render }, - Statistic{ Hours: decimal.NewFromFloat(0.5), Project: "blog", Color: color.FgGreen.Render }, + entries, err := database.ListEntries(user) + if err != nil { + fmt.Printf("%s %+v\n", CharError, err) + os.Exit(1) } - out := cal.GetOutputForWeekCalendar(1, data) - out2 := cal.GetOutputForWeekCalendar(2, data) + cal, _ := NewCalendar(entries) - fmt.Printf("%s\n", OutputAppendRight(out, out2, 10)) + today := time.Now() + month, weeknumber := GetISOWeekInMonth(today) + month0 := month - 1 + weeknumber0 := weeknumber - 1 + thisWeek := cal.GetOutputForWeekCalendar(today, month0, weeknumber0) + + oneWeekAgo := today.AddDate(0, 0, -7) + month, weeknumber = GetISOWeekInMonth(oneWeekAgo) + month0 = month - 1 + weeknumber0 = weeknumber - 1 + previousWeek := cal.GetOutputForWeekCalendar(oneWeekAgo, month0, weeknumber0) + + fmt.Printf("%s\n", OutputAppendRight(thisWeek, previousWeek, 16)) return },