diff --git a/cli/status.go b/cli/status.go index 0bba9f7..5a03420 100644 --- a/cli/status.go +++ b/cli/status.go @@ -37,17 +37,18 @@ func statusCommand(t *core.Timetrace) *cobra.Command { trackedTimeCurrent := defaultString if report.TrackedTimeCurrent != nil { - trackedTimeCurrent = report.FormatCurrentTime() + trackedTimeCurrent = t.Formatter().FormatCurrentTime(report) } rows := [][]string{ { project, trackedTimeCurrent, - report.FormatTodayTime(), + t.Formatter().FormatTodayTime(report), + t.Formatter().FormatBreakTime(report), }, } - out.Table([]string{"Current project", "Worked since start", "Worked today"}, rows, nil) + out.Table([]string{"Current project", "Worked since start", "Worked today", "Breaks"}, rows, nil) }, } diff --git a/core/formatter.go b/core/formatter.go index 6357601..d5b273e 100644 --- a/core/formatter.go +++ b/core/formatter.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "time" ) @@ -67,3 +68,38 @@ func (f *Formatter) ParseRecordKey(key string) (time.Time, error) { func (f *Formatter) RecordKey(record *Record) string { return record.Start.Format(f.RecordKeyLayout()) } + +// FormatTodayTime returns the formated string of the total +// time of today follwoing the format convention +func (f *Formatter) FormatTodayTime(report *Report) string { + return f.formatDuration(report.TrackedTimeToday) +} + +// FormatCurrentTime returns the formated string of the current +// report time follwoing the format convention +func (f *Formatter) FormatCurrentTime(report *Report) string { + return f.formatDuration(*report.TrackedTimeCurrent) +} + +// FormatBreakTime returns the formated string of the total time +// taking breaks today following the format convention +func (f *Formatter) FormatBreakTime(report *Report) string { + return f.formatDuration(report.BreakTimeToday) +} + +// formatDuration formats the passed duration into a string. +// The format will be "8h 24min". If the duration is less then 60 secods +// the format will be "0h 0min 12sec". +func (f *Formatter) formatDuration(duration time.Duration) string { + + hours := int64(duration.Hours()) % 60 + minutes := int64(duration.Minutes()) % 60 + secods := int64(duration.Seconds()) % 60 + + // as by convention if the duarion is < then 60 secods + // return "0h 0min Xsec" + if hours == 0 && minutes == 0 { + return fmt.Sprintf("0h 0min %dsec", secods) + } + return fmt.Sprintf("%dh %dmin", hours, minutes) +} diff --git a/core/record.go b/core/record.go index 3002437..6e2c108 100644 --- a/core/record.go +++ b/core/record.go @@ -213,6 +213,31 @@ func (t *Timetrace) loadAllRecords(date time.Time) ([]*Record, error) { return records, nil } +func (t *Timetrace) loadAllRecordsSortedAscending(date time.Time) ([]*Record, error) { + dir := t.fs.RecordDirFromDate(date) + + recordFilepaths, err := t.fs.RecordFilepaths(dir, func(a, b string) bool { + timeA, _ := time.Parse(recordLayout, a) + timeB, _ := time.Parse(recordLayout, b) + return timeA.Before(timeB) + }) + if err != nil { + return nil, err + } + + var records []*Record + + for _, recordFilepath := range recordFilepaths { + record, err := t.loadRecord(recordFilepath) + if err != nil { + return nil, err + } + records = append(records, record) + } + + return records, nil +} + // LoadLatestRecord loads the youngest record. This may also be a record from // another day. If there is no latest record, nil and no error will be returned. func (t *Timetrace) LoadLatestRecord() (*Record, error) { diff --git a/core/timetrace.go b/core/timetrace.go index d6a3350..01dd9a7 100644 --- a/core/timetrace.go +++ b/core/timetrace.go @@ -2,7 +2,6 @@ package core import ( "errors" - "fmt" "io" "os" "time" @@ -20,6 +19,7 @@ type Report struct { Current *Record TrackedTimeCurrent *time.Duration TrackedTimeToday time.Duration + BreakTimeToday time.Duration } // Filesystem represents a filesystem used for storing and loading resources. @@ -111,8 +111,14 @@ func (t *Timetrace) Status() (*Report, error) { return nil, err } + breakTimeToday, err := t.breakTime(now) + if err != nil { + return nil, err + } + report := &Report{ TrackedTimeToday: trackedTimeToday, + BreakTimeToday: breakTimeToday, } // If the latest record has been stopped, there is no active time tracking. @@ -131,6 +137,21 @@ func (t *Timetrace) Status() (*Report, error) { return report, nil } +func (t *Timetrace) breakTime(date time.Time) (time.Duration, error) { + records, err := t.loadAllRecordsSortedAscending(date) + if err != nil { + return 0, err + } + + // add up the time between records + var breakTime time.Duration + for i := 0; i < len(records)-1; i++ { + breakTime += records[i+1].Start.Sub(*records[i].End) + } + + return breakTime, nil +} + // Stop stops the time tracking and marks the current record as ended. func (t *Timetrace) Stop() error { latestRecord, err := t.LoadLatestRecord() @@ -182,35 +203,6 @@ func (t *Timetrace) trackedTime(date time.Time) (time.Duration, error) { return trackedTime, nil } -// FormatTodayTime returns the formated string of the total -// time of today follwoing the format convention -func (report *Report) FormatTodayTime() string { - return formatDuration(report.TrackedTimeToday) -} - -// FormatCurrentTime returns the formated string of the current -// report time follwoing the format convention -func (report *Report) FormatCurrentTime() string { - return formatDuration(*report.TrackedTimeCurrent) -} - -// formatDuration formats the passed duration into a string. -// The format will be "8h 24min". If the duration is less then 60 secods -// the format will be "0h 0min 12sec". -func formatDuration(duration time.Duration) string { - - hours := int64(duration.Hours()) % 60 - minutes := int64(duration.Minutes()) % 60 - secods := int64(duration.Seconds()) % 60 - - // as by convention if the duarion is < then 60 secods - // return "0h 0min Xsec" - if hours == 0 && minutes == 0 { - return fmt.Sprintf("0h 0min %dsec", secods) - } - return fmt.Sprintf("%dh %dmin", hours, minutes) -} - func (t *Timetrace) isDirEmpty(dir string) (bool, error) { openedDir, err := os.Open(dir) if err != nil {