package main import ( "errors" "fmt" "strconv" "time" "git.bullercodeworks.com/brian/gime-lib" ) func GetRoundToDuration() time.Duration { var dur time.Duration var err error if dur, err = time.ParseDuration(cfg.Get("roundto")); err != nil { cfg.Set("roundto", DefRoundTo) dur, _ = time.ParseDuration(DefRoundTo) } return dur } func DurationToDecimal(dur time.Duration) float64 { mins := dur.Minutes() - (dur.Hours() * 60) return dur.Hours() + (mins / 60) } // filterTimerCollection takes a collection and a function that it runs every entry through // If the function returns true for the entry, it adds it to a new collection to be returned func filterTimerCollection(c *gime.TimeEntryCollection, fn func(t *gime.TimeEntry) bool) *gime.TimeEntryCollection { ret := new(gime.TimeEntryCollection) for i := 0; i < c.Length(); i++ { if fn(c.Get(i)) { ret.Push(c.Get(i)) } } return ret } func TimerCollectionToString(c *gime.TimeEntryCollection) string { var ret string for i := 0; i < c.Length(); i++ { ret += TimerToString(c.Get(i)) if i < c.Length()-1 { ret += "\n" } } return ret } // TimerToString takes a TimeEntry and gives a nicely formatted string func TimerToString(t *gime.TimeEntry) string { var ret string var end string if t.StartsToday() { ret = t.GetStart().Format("15:04 - ") end = "**:**" } else { ret = t.GetStart().Format("2006/01/02 15:04:05 - ") end = "**:**:**" } if !t.GetEnd().IsZero() { if t.EndsToday() { end = t.GetEnd().Format("15:04") } else { end = t.GetEnd().Format("2006/01/02 15:04:05") } } ret += end tags := t.GetTags() if tags.Length() > 0 { ret += " [ " for i := 0; i < tags.Length(); i++ { ret += tags.Get(i) + " " } ret += "]" } return ret } func InferTimerDetailString(t *gime.TimeEntry) string { diffEnd := time.Now() if !t.GetEnd().IsZero() { diffEnd = t.GetEnd() } if int(diffEnd.Sub(t.GetStart())) >= (int(time.Hour) * diffEnd.Hour()) { return TimerDetailToLongString(t) } return TimerDetailToString(t) } func TimerDetailToString(t *gime.TimeEntry) string { ret := t.GetStart().Format("15:04") + " - " if t.GetEnd().IsZero() { ret += "**:** (" + padLeft(sinceToString(t.GetStart()), len("00h 00m 00s")) + ") " } else { ret += t.GetEnd().Format("15:04") + " (" + padLeft(diffToString(t.GetStart(), t.GetEnd()), len("00h 00m 00s")) + ") " } if t.GetTags().Length() > 0 { ret += " [ " for j := 0; j < t.GetTags().Length(); j++ { ret += t.GetTags().Get(j) if j < t.GetTags().Length()-1 { ret += ", " } } ret += " ] " } return ret } // ...ToLongString includes year/month/day func TimerDetailToLongString(t *gime.TimeEntry) string { ret := t.GetStart().Format(time.Stamp) if t.GetEnd().IsZero() { ret += " (" + padLeft(sinceToString(t.GetStart()), len("0000y 00m 00d 00h 00m 00s")) + ") " } else { ret += " (" + padLeft(diffToString(t.GetStart(), t.GetEnd()), len("0000y 00m 00d 00h 00m 00s")) + ") " } if t.GetTags().Length() > 0 { ret += " [ " for j := 0; j < t.GetTags().Length(); j++ { ret += t.GetTags().Get(j) if j < t.GetTags().Length()-1 { ret += ", " } } ret += " ] " } return ret } // findTimerById takes a timer id and returns the TimeEntry and the type string // of the entry corresponding to that id // It searches TypeCurrent -> TypeRecent -> TypeArchive func findTimerById(tmrId int) (*gime.TimeEntry, int, error) { // Find the timer for this tmrId var prevNum, numLoaded int for i := range gdb.AllTypes { timeCollection := gdb.LoadTimeEntryCollection(gdb.AllTypes[i]) numLoaded += timeCollection.Length() if numLoaded > tmrId { // This is the set that it's in, the index we need is tmrId - prevNum return timeCollection.Get(tmrId - prevNum), gdb.AllTypes[i], nil } prevNum = numLoaded } return nil, gime.TypeAll, errors.New("Unable to find timer with id: " + strconv.Itoa(tmrId)) } func findIdOfTimer(tmr *gime.TimeEntry) (int, error) { var prevNum int for i := range gdb.AllTypes { timeCollection := gdb.LoadTimeEntryCollection(gdb.AllTypes[i]) if idx := timeCollection.Index(tmr); idx > -1 { // It's in this collection return (prevNum + idx), nil } prevNum += timeCollection.Length() } return -1, errors.New("Unable to find timer") } // pullRemoveTagsFromArgs takes a list of arguments, removes all 'remove tags' from them // then returns the tags and the remaining args func pullRemoveTagsFromArgs(args []string) ([]string, []string) { var tags, rem []string for _, opt := range args { if opt[0] == '-' { tags = append(tags, opt[1:]) } else { rem = append(rem, opt) } } return tags, rem } // pullTagsFromArgs takes a list of arguments, removes all tags from them // then returns the tags and the remaining args func pullTagsFromArgs(args []string) ([]string, []string) { var tags, rem []string for _, opt := range args { if opt[0] == '+' { tags = append(tags, opt[1:]) } else { rem = append(rem, opt) } } return tags, rem } func parseFuzzyTime(t string) (time.Time, error) { var ret time.Time var err error for i := range fuzzyFormats { ret, err = time.Parse(fuzzyFormats[i], t) if err == nil { // Make sure it's in the local timezone tz := time.Now().Format("Z07:00") t = ret.Format("2006-01-02T15:04:05") + tz if ret, err = time.Parse(time.RFC3339, t); err != nil { return ret, err } // Check for zero on year/mo/day if ret.Year() == 0 && ret.Month() == time.January && ret.Day() == 1 { ret = ret.AddDate(time.Now().Year(), int(time.Now().Month())-1, time.Now().Day()-1) } return ret, nil } } return time.Time{}, errors.New("Unable to parse time: " + t) } func friendlyFormatForTime(t time.Time) string { nowTime := time.Now() if t.Year() != nowTime.Year() || t.Month() != nowTime.Month() { return "2006-01-02 15:04" } else if t.Day() != nowTime.Day() { return "01/02 15:04" } return "15:04" } // timeToFriendlyString returns an easier to read version of the time // giving enough details that the user should be fine inferring the rest func timeToFriendlyString(t time.Time) string { return t.Format(friendlyFormatForTime(t)) } func sinceToString(tm time.Time) string { return diffToString(tm, time.Now()) } func diffToString(tm1, tm2 time.Time) string { ret := "" yr, mo, dy, hr, mn, sc := diff(tm1, tm2) higher := false if yr > 0 { ret += fmt.Sprintf("%4dy ", yr) higher = true } if mo > 0 || higher { ret += fmt.Sprintf("%2dm ", mo) higher = true } if dy > 0 || higher { ret += fmt.Sprintf("%2dd ", dy) higher = true } if hr > 0 || higher { ret += fmt.Sprintf("%2dh ", hr) higher = true } if mn > 0 || higher { ret += fmt.Sprintf("%2dm ", mn) higher = true } if sc > 0 || higher { ret += fmt.Sprintf("%2ds", sc) } return ret } func padRight(st string, l int) string { for len(st) < l { st = st + " " } return st } func padLeft(st string, l int) string { for len(st) < l { st = " " + st } return st } func diff(a, b time.Time) (year, month, day, hour, min, sec int) { if a.Location() != b.Location() { b = b.In(a.Location()) } if a.After(b) { a, b = b, a } y1, M1, d1 := a.Date() y2, M2, d2 := b.Date() h1, m1, s1 := a.Clock() h2, m2, s2 := b.Clock() year = int(y2 - y1) month = int(M2 - M1) day = int(d2 - d1) hour = int(h2 - h1) min = int(m2 - m1) sec = int(s2 - s1) // Normalize negative values if sec < 0 { sec += 60 min-- } if min < 0 { min += 60 hour-- } if hour < 0 { hour += 24 day-- } if day < 0 { // days in month: t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC) day += 32 - t.Day() month-- } if month < 0 { month += 12 year-- } return }