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.
241 lines
5.9 KiB
Go
241 lines
5.9 KiB
Go
1 year ago
|
package ntime
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"git.noahlan.cn/noahlan/ntool/ngo"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// ErrDateLayout error
|
||
|
ErrDateLayout = errors.New("invalid date layout string")
|
||
|
// ErrInvalidParam error
|
||
|
ErrInvalidParam = errors.New("invalid input for parse time")
|
||
|
)
|
||
|
|
||
|
// Format convert time to string use default layout
|
||
|
func Format(t time.Time) string {
|
||
|
return t.Format(DefaultLayout)
|
||
|
}
|
||
|
|
||
|
// FormatBy convert time to string use given layout or template(java),
|
||
|
func FormatBy(t time.Time, layout ...string) string {
|
||
|
return t.Format(ToLayout(ngo.FirstOr(layout, DefaultLayout)))
|
||
|
}
|
||
|
|
||
|
// FormatUnix time seconds use default layout
|
||
|
func FormatUnix(sec int64, layout ...string) string {
|
||
|
return time.Unix(sec, 0).Format(ToLayout(ngo.FirstOr(layout, DefaultLayout)))
|
||
|
}
|
||
|
|
||
|
var timeFormats = [][]int{
|
||
|
{0},
|
||
|
{1},
|
||
|
{2, 1},
|
||
|
{60},
|
||
|
{120, 60},
|
||
|
{3600},
|
||
|
{7200, 3600},
|
||
|
{86400},
|
||
|
{172800, 86400},
|
||
|
}
|
||
|
|
||
|
var timeMessages = []string{
|
||
|
"< 1 sec", "1 sec", "secs", "1 min", "mins", "1 hr", "hrs", "1 day", "days",
|
||
|
}
|
||
|
|
||
|
// HowLongAgo format given timestamp to string.
|
||
|
func HowLongAgo(sec int64) string {
|
||
|
intVal := int(sec)
|
||
|
length := len(timeFormats)
|
||
|
|
||
|
for i, item := range timeFormats {
|
||
|
if intVal >= item[0] {
|
||
|
ni := i + 1
|
||
|
match := false
|
||
|
|
||
|
if ni < length { // next exists
|
||
|
next := timeFormats[ni]
|
||
|
if intVal < next[0] { // current <= intVal < next
|
||
|
match = true
|
||
|
}
|
||
|
} else if ni == length { // current is last
|
||
|
match = true
|
||
|
}
|
||
|
|
||
|
if match { // match success
|
||
|
if len(item) == 1 {
|
||
|
return timeMessages[i]
|
||
|
}
|
||
|
|
||
|
// len is 2
|
||
|
return fmt.Sprintf("%d %s", intVal/item[1], timeMessages[i])
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return "unknown" // He should never happen
|
||
|
}
|
||
|
|
||
|
// auto match use some commonly layouts.
|
||
|
// key is layout length.
|
||
|
var layoutMap = map[int][]string{
|
||
|
6: {"200601", "060102", time.Kitchen},
|
||
|
8: {"20060102"},
|
||
|
10: {time.DateOnly},
|
||
|
13: {"2006-01-02 15"},
|
||
|
15: {time.Stamp},
|
||
|
16: {"2006-01-02 15:04"},
|
||
|
19: {time.DateTime, time.RFC822, time.StampMilli},
|
||
|
20: {"2006-01-02 15:04:05Z"},
|
||
|
21: {time.RFC822Z},
|
||
|
22: {time.StampMicro},
|
||
|
23: {"2006-01-02 15:04:05.000", "2006-01-02 15:04:05.999"},
|
||
|
24: {time.ANSIC},
|
||
|
25: {time.RFC3339, time.StampNano},
|
||
|
// time.Layout}, // must go >= 1.19
|
||
|
26: {"2006-01-02 15:04:05.000000"},
|
||
|
28: {time.UnixDate},
|
||
|
29: {time.RFC1123, "2006-01-02 15:04:05.000000000"},
|
||
|
30: {time.RFC850},
|
||
|
31: {time.RFC1123Z},
|
||
|
35: {time.RFC3339Nano},
|
||
|
}
|
||
|
|
||
|
// MustParseTime must convert date time string to time.Time
|
||
|
// it will return ZeroTime when parsing error occurred. see MustParseTimeD
|
||
|
func MustParseTime(s string, layouts ...string) time.Time {
|
||
|
return MustParseTimeD(s, ZeroTime, layouts...)
|
||
|
}
|
||
|
|
||
|
// MustParseTimeD must convert date time string to time.Time
|
||
|
// it will return defaultTime when parsing error occurred. see ParseTime
|
||
|
func MustParseTimeD(s string, defaultTime time.Time, layouts ...string) time.Time {
|
||
|
t, err := ParseTime(s, layouts...)
|
||
|
if err != nil {
|
||
|
return defaultTime
|
||
|
}
|
||
|
return t
|
||
|
}
|
||
|
|
||
|
// ParseTime convert date time string to time.Time
|
||
|
// it will use some commonly layouts when layouts is empty or nil
|
||
|
func ParseTime(s string, layouts ...string) (t time.Time, err error) {
|
||
|
// custom layout
|
||
|
if len(layouts) > 0 {
|
||
|
if len(layouts[0]) > 0 {
|
||
|
return time.Parse(ToLayout(layouts[0]), s)
|
||
|
}
|
||
|
err = ErrDateLayout
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// auto match use some commonly layouts.
|
||
|
strLn := len(s)
|
||
|
maybeLayouts, ok := layoutMap[strLn]
|
||
|
if !ok {
|
||
|
err = ErrInvalidParam
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var hasAlphaT bool
|
||
|
if pos := strings.IndexByte(s, 'T'); pos > 0 && pos < 12 {
|
||
|
hasAlphaT = true
|
||
|
}
|
||
|
|
||
|
hasSlashR := strings.IndexByte(s, '/') > 0
|
||
|
for _, layout := range maybeLayouts {
|
||
|
// date string has "T". eg: "2006-01-02T15:04:05"
|
||
|
if hasAlphaT {
|
||
|
layout = strings.Replace(layout, " ", "T", 1)
|
||
|
}
|
||
|
|
||
|
// date string has "/". eg: "2006/01/02 15:04:05"
|
||
|
if hasSlashR {
|
||
|
layout = strings.Replace(layout, "-", "/", -1)
|
||
|
}
|
||
|
|
||
|
t, err = time.Parse(layout, s)
|
||
|
if err == nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// t, err = time.ParseInLocation(layout, s, time.Local)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
// TIP: extend unit d,w
|
||
|
// time.ParseDuration() is not supported. eg: "1d", "2w"
|
||
|
durStrReg = regexp.MustCompile(`^(-?\d+)(ns|us|µs|ms|s|m|h|d|w)$`)
|
||
|
// match long duration string, such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks"
|
||
|
// time.ParseDuration() is not supported.
|
||
|
durStrRegL = regexp.MustCompile(`^(-?\d+)([a-zA-Z]{3,})$`)
|
||
|
)
|
||
|
|
||
|
// IsDuration check the string is a duration string.
|
||
|
func IsDuration(s string) bool {
|
||
|
if s == "0" || durStrReg.MatchString(s) {
|
||
|
return true
|
||
|
}
|
||
|
return durStrRegL.MatchString(s)
|
||
|
}
|
||
|
|
||
|
// ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m".
|
||
|
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||
|
//
|
||
|
// Diff of time.ParseDuration:
|
||
|
// - support extend unit d, w at the end of string. such as "1d", "2w".
|
||
|
// - support long string unit at end. such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks".
|
||
|
//
|
||
|
// If the string is not a valid duration string, it will return an error.
|
||
|
func ToDuration(s string) (time.Duration, error) {
|
||
|
ln := len(s)
|
||
|
if ln == 0 {
|
||
|
return 0, fmt.Errorf("empty duration string")
|
||
|
}
|
||
|
|
||
|
s = strings.ToLower(s)
|
||
|
if s == "0" {
|
||
|
return 0, nil
|
||
|
}
|
||
|
|
||
|
// extend unit d,w, time.ParseDuration() is not supported. eg: "1d", "2w"
|
||
|
if lastUnit := s[ln-1]; lastUnit == 'd' {
|
||
|
s = s + "ay"
|
||
|
} else if lastUnit == 'w' {
|
||
|
s = s + "eek"
|
||
|
}
|
||
|
|
||
|
// long unit, time.ParseDuration() is not supported. eg: "-3sec" => [3sec -3 sec]
|
||
|
ss := durStrRegL.FindStringSubmatch(s)
|
||
|
if len(ss) == 3 {
|
||
|
num, unit := ss[1], ss[2]
|
||
|
|
||
|
// convert to short unit
|
||
|
switch unit {
|
||
|
case "week", "weeks":
|
||
|
// max unit is hour, so need convert by 24 * 7 * n
|
||
|
n, _ := strconv.Atoi(num)
|
||
|
s = strconv.Itoa(n*24*7) + "h"
|
||
|
case "day", "days":
|
||
|
// max unit is hour, so need convert by 24 * n
|
||
|
n, _ := strconv.Atoi(num)
|
||
|
s = strconv.Itoa(n*24) + "h"
|
||
|
case "hour", "hours":
|
||
|
s = num + "h"
|
||
|
case "min", "mins", "minute", "minutes":
|
||
|
s = num + "m"
|
||
|
case "sec", "secs", "second", "seconds":
|
||
|
s = num + "s"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return time.ParseDuration(s)
|
||
|
}
|