tracktime

Small utility for tracking working hours in a plain text file

git clone https://git.8pit.net/tracktime.git

  1package main
  2
  3import (
  4	"github.com/nmeum/tracktime/parser"
  5
  6	"flag"
  7	"fmt"
  8	"log"
  9	"os"
 10	"time"
 11)
 12
 13const (
 14	DAY   = 'd'
 15	WEEK  = 'w'
 16	MONTH = 'm'
 17)
 18
 19var (
 20	goal     = flag.Int("h", 8, "hours per interval")
 21	interval = flag.String("i", "d", "interval for working hours")
 22	seconds  = flag.Bool("s", false, "output duration in seconds")
 23)
 24
 25var dateLayout string
 26
 27func max(a, b int) int {
 28	if a > b {
 29		return a
 30	} else {
 31		return b
 32	}
 33}
 34
 35func durationString(duration time.Duration) string {
 36	if *seconds {
 37		return fmt.Sprintf("%v", duration.Seconds())
 38	} else {
 39		return duration.String()
 40	}
 41}
 42
 43func intervalString(date time.Time) string {
 44	if *interval == "" {
 45		fmt.Fprintf(os.Stderr, "invalid interval\n")
 46		os.Exit(1)
 47	}
 48
 49	switch (*interval)[0] {
 50	case DAY:
 51		return date.Format(dateLayout)
 52	case WEEK:
 53		year, week := date.ISOWeek()
 54		return fmt.Sprintf("W%v %v", week, year)
 55	case MONTH:
 56		year := date.Year()
 57		return fmt.Sprintf("%s %v", date.Month(), year)
 58	default:
 59		fmt.Fprintf(os.Stderr, "unsupported interval: %q\n", *interval)
 60		os.Exit(2)
 61	}
 62
 63	panic("unreachable")
 64}
 65
 66func handleEntries(entries []*parser.Entry) {
 67	var keys []string
 68	var maxdurlen int
 69
 70	workmap := make(map[string]time.Duration)
 71	for _, entry := range entries {
 72		key := intervalString(entry.Date)
 73		if _, ok := workmap[key]; !ok {
 74			keys = append(keys, key)
 75		}
 76		workmap[key] += entry.Duration
 77
 78		// Date should always have the same width. Only duration
 79		// requires padding based on the maximum duration length.
 80		maxdurlen = max(maxdurlen, len(fmt.Sprintf("%v", workmap[key])))
 81	}
 82
 83	var delta, goalHours time.Duration
 84	goalHours = time.Duration(*goal) * time.Hour
 85
 86	// Output in same order as specified in input file
 87	for _, key := range keys {
 88		hours := workmap[key]
 89		delta += (hours - goalHours)
 90
 91		// Output should always be aligned at the pipe character.
 92		fmt.Printf("%v %*v | %v\n", key, maxdurlen, hours, durationString(delta))
 93	}
 94}
 95
 96func main() {
 97	log.SetFlags(log.Lshortfile)
 98	flag.Parse()
 99
100	if flag.NArg() != 1 {
101		fmt.Fprintf(os.Stderr, "specify a file to parse\n")
102		os.Exit(1)
103	}
104
105	fp := flag.Arg(0)
106	file, err := os.Open(fp)
107	if err != nil {
108		log.Fatal(err)
109	}
110	defer file.Close()
111
112	dateLayout = parser.DefaultTimeFormat()
113	p := parser.NewParser(dateLayout)
114
115	entries, err := p.ParseEntries(fp, file)
116	if err != nil {
117		log.Fatal(err)
118	}
119
120	handleEntries(entries)
121}