package main import ( "errors" "fmt" "strings" "time" timertxt "git.bullercodeworks.com/brian/go-timertxt" ) func GetRoundToDuration() time.Duration { var dur time.Duration var err error if dur, err = time.ParseDuration(app.config.Get("roundto")); err != nil { app.config.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) } // getContextsFromSlice pulls all '@' (contexts) out of the // string slice and return those contexts and the remaining // strings from the slice func getContextsFromSlice(args []string) ([]string, []string) { return splitSlice(args, func(v string) bool { return strings.HasPrefix(v, "@") }) } // getProjectsFromSlice pulls all '+' (projects) out of the // string slice and return those projects and the remaining // strings from the slice func getProjectsFromSlice(args []string) ([]string, []string) { return splitSlice(args, func(v string) bool { return strings.HasPrefix(v, "+") }) } func splitSlice(args []string, predicate func(string) bool) ([]string, []string) { var rem1, rem2 []string for _, v := range args { if predicate(v) { rem1 = append(rem1, v) } else { rem2 = append(rem2, v) } } return rem1, rem2 } 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) } var fuzzyFormats = []string{ "1504", "15:04", // Kitchen, 24hr time.Kitchen, time.RFC3339, "2006-01-02T15:04:05", // RFC3339 without timezone "2006-01-02T15:04", // RFC3339 without seconds or timezone time.Stamp, "02 Jan 06 15:04:05", // RFC822 with second time.RFC822, "01/02/2006 15:04", // U.S. Format "01/02/2006 15:04:05", // U.S. Format with seconds "01/02/06 15:04", // U.S. Format, short year "01/02/06 15:04:05", // U.S. Format, short year, with seconds "2006-01-02", "2006-01-02 15:04", "2006-01-02 15:04:05", "20060102", "20060102 15:04", "20060102 15:04:05", "20060102 1504", "20060102 150405", "20060102T15:04", "20060102T15:04:05", "20060102T1504", "20060102T150405", } func timerToFriendlyString(t *timertxt.Timer) string { var start, end, contexts, projects string start = t.StartDate.Format(friendlyFormatForTime(t.StartDate)) if t.FinishDate.IsZero() { end = "**:**" } else { end = t.FinishDate.Format(friendlyFormatForTime(t.FinishDate)) } for _, v := range t.Contexts { contexts += "@" + v + " " } for _, v := range t.Projects { projects += "+" + v + " " } return fmt.Sprintf("% 2d. %s - %s [ %s] [ %s] %s", t.Id, start, end, contexts, projects, t.Notes) } 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 }