gime/helpers.go

306 lines
7.0 KiB
Go

package main
import (
"errors"
"fmt"
"strings"
"time"
timertxt "git.bullercodeworks.com/brian/go-timertxt"
)
// TimerToString takes a TimeEntry and gives a nicely formatted string
func TimerToString(t *timertxt.Timer) string {
var ret string
var end string
if t.StartsToday() {
ret = t.StartDate.Format("15:04 - ")
end = "**:**"
} else {
ret = t.StartDate.Format("2006/01/02 15:04:05 - ")
end = "**:**:**"
}
if !t.FinishDate.IsZero() {
if t.EndsToday() {
end = t.FinishDate.Format("15:04")
} else {
end = t.FinishDate.Format("2006/01/02 15:04:05")
}
}
ret += end
if len(t.Contexts) > 0 {
ret += " " + fmt.Sprint(t.Contexts)
}
if len(t.Projects) > 0 {
ret += " " + fmt.Sprint(t.Projects)
}
if len(t.AdditionalTags) > 0 {
ret += " [ "
for k, v := range t.AdditionalTags {
ret += k + ":" + v + " "
}
ret += "]"
}
return ret
}
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, "+")
})
}
// getAdditionalTagsFromSlice pulls all '*:*' (tags) out of the
// string slice and returns those tags and the remaining
// strings from the slice
func getAdditionalTagsFromSlice(args []string) ([]string, []string) {
return splitSlice(args, func(v string) bool {
return strings.Contains(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, tags 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 + " "
}
for k, v := range t.AdditionalTags {
tags = fmt.Sprintf("%s%s:%s ", tags, k, v)
}
var dur time.Duration
if t.FinishDate.IsZero() {
dur = time.Now().Sub(t.StartDate)
} else {
dur = t.FinishDate.Sub(t.StartDate)
}
dur = dur.Round(GetRoundToDuration())
return fmt.Sprintf("% 2d. %s - %s [ %s] [ %s] [ %s] %s ( %.2f )", t.Id, start, end, contexts, projects, tags, t.Notes, DurationToDecimal(dur))
}
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
}
func BeginningOfDay() time.Time {
now := time.Now()
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
}
func BeginningOfWeek() time.Time {
now := time.Now()
t := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
weekday := int(t.Weekday())
return t.AddDate(0, 0, -weekday)
}
func BeginningOfMonth() time.Time {
now := time.Now()
return time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
}