You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ntool/ntime/util.go

236 lines
5.2 KiB
Go

package ntime
import (
"fmt"
"git.noahlan.cn/noahlan/ntool/nstr"
"strings"
"time"
)
// ReprOfDuration returns the string representation of given duration in ms.
func ReprOfDuration(duration time.Duration) string {
return fmt.Sprintf("%.1fms", float32(duration)/float32(time.Millisecond))
}
// ElapsedTime calc elapsed time 计算运行时间消耗 单位 ms(毫秒)
func ElapsedTime(startTime time.Time) string {
return fmt.Sprintf("%.3f", time.Since(startTime).Seconds()*1000)
}
// TryToTime parse a date string or duration string to time.Time.
//
// if s is empty, return zero time.
func TryToTime(s string, bt time.Time) (time.Time, error) {
if s == "" {
return ZeroTime, nil
}
if s == "now" {
return time.Now(), nil
}
// if s is a duration string, add it to bt(base time)
if IsDuration(s) {
dur, err := ToDuration(s)
if err != nil {
return ZeroTime, err
}
return bt.Add(dur), nil
}
// as a date string, parse it to time.Time
return ParseTime(s)
}
// InRange check the dst time is in the range of start and end.
//
// if start is zero, only check dst < end,
// if end is zero, only check dst > start.
func InRange(dst, start, end time.Time) bool {
if start.IsZero() && end.IsZero() {
return false
}
if start.IsZero() {
return dst.Before(end)
}
if end.IsZero() {
return dst.After(start)
}
return dst.After(start) && dst.Before(end)
}
// ParseRangeOpt is the option for ParseRange
type ParseRangeOpt struct {
// BaseTime is the base time for relative time string.
// if is zero, use time.Now() as base time.
BaseTime time.Time
// OneAsEnd is the option for one time range.
// - False: "-1h" => "-1h,0"; "1h" => "+1h, feature"
// - True: "-1h" => "zero,-1h"; "1h" => "zero,1h"
OneAsEnd bool
// AutoSort is the option for sort the time range.
AutoSort bool
// SepChar is the separator char for time range string. default is '~'
SepChar byte
// BeforeFn hook for before parse time string.
BeforeFn func(string) string
// KeywordFn is the function for parse keyword time string.
KeywordFn func(string) (time.Time, time.Time, error)
}
func ensureOpt(opt *ParseRangeOpt) *ParseRangeOpt {
if opt == nil {
opt = &ParseRangeOpt{BaseTime: time.Now(), SepChar: '~'}
} else {
if opt.BaseTime.IsZero() {
opt.BaseTime = time.Now()
}
if opt.SepChar == 0 {
opt.SepChar = '~'
}
}
return opt
}
// ParseRange parse time range expression string to time.Time range.
// - "0" is alias of "now"
//
// Expression format:
//
// "-5h~-1h" => 5 hours ago to 1 hour ago
// "1h~5h" => 1 hour after to 5 hours after
// "-1h~1h" => 1 hour ago to 1 hour after
// "-1h" => 1 hour ago to feature. eq "-1h,"
// "-1h~0" => 1 hour ago to now.
// "< -1h" OR "~-1h" => 1 hour ago. eq ",-1h"
// "> 1h" OR "1h" => 1 hour after to feature
// // keyword: now, today, yesterday, tomorrow
// "today" => today start to today end
// "yesterday" => yesterday start to yesterday end
// "tomorrow" => tomorrow start to tomorrow end
//
// Usage:
//
// start, end, err := ParseRange("-1h~1h", nil)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(start, end)
func ParseRange(expr string, opt *ParseRangeOpt) (start, end time.Time, err error) {
opt = ensureOpt(opt)
expr = strings.TrimSpace(expr)
if expr == "" {
err = fmt.Errorf("invalid time range expr %q", expr)
return
}
// parse time range. eg: "5h~1h"
if strings.IndexByte(expr, opt.SepChar) > -1 {
s1, s2 := nstr.TrimCut(expr, string(opt.SepChar))
if s1 == "" && s2 == "" {
err = fmt.Errorf("invalid time range expr: %s", expr)
return
}
if s1 != "" {
start, err = TryToTime(s1, opt.BaseTime)
if err != nil {
return
}
}
if s2 != "" {
end, err = TryToTime(s2, opt.BaseTime)
// auto sort range time
if opt.AutoSort && err == nil {
if !start.IsZero() && start.After(end) {
start, end = end, start
}
}
}
return
}
// single time. eg: "5h", "1h", "-1h"
if IsDuration(expr) {
tt, err1 := TryToTime(expr, opt.BaseTime)
if err1 != nil {
err = err1
return
}
if opt.OneAsEnd {
end = tt
} else {
start = tt
}
return
}
// with compare operator. eg: "<1h", ">1h"
if expr[0] == '<' || expr[0] == '>' {
tt, err1 := TryToTime(strings.Trim(expr[1:], " ="), opt.BaseTime)
if err1 != nil {
err = err1
return
}
if expr[0] == '<' {
end = tt
} else {
start = tt
}
return
}
// parse keyword time string
switch expr {
case "0":
if opt.OneAsEnd {
end = opt.BaseTime
} else {
start = opt.BaseTime
}
case "now":
if opt.OneAsEnd {
end = time.Now()
} else {
start = time.Now()
}
case "today":
start = DayStart(opt.BaseTime)
end = DayEnd(opt.BaseTime)
case "yesterday":
yd := opt.BaseTime.AddDate(0, 0, -1)
start = DayStart(yd)
end = DayEnd(yd)
case "tomorrow":
td := opt.BaseTime.AddDate(0, 0, 1)
start = DayStart(td)
end = DayEnd(td)
default:
// single datetime. eg: "2019-01-01"
tt, err1 := TryToTime(expr, opt.BaseTime)
if err1 != nil {
if opt.KeywordFn == nil {
err = fmt.Errorf("invalid keyword time string: %s", expr)
return
}
start, end, err = opt.KeywordFn(expr)
return
}
if opt.OneAsEnd {
end = tt
} else {
start = tt
}
}
return
}