107 lines
2.6 KiB
Go
107 lines
2.6 KiB
Go
package models
|
||
|
||
import (
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
var activityLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
|
||
|
||
const (
|
||
// 日与时刻之间留空格,避免「20日11点」粘连难读
|
||
ActivityTimeLayout = "2006年1月2日 15点04分05秒"
|
||
// 历史数据可能无空格,解析时兼容
|
||
activityTimeLayoutLegacy = "2006年1月2日15点04分05秒"
|
||
ActivityDateLayout = "2006-01-02"
|
||
)
|
||
|
||
func CurrentActivityDate() string {
|
||
return time.Now().In(activityLocation).Format(ActivityDateLayout)
|
||
}
|
||
|
||
func CurrentActivityTime() string {
|
||
return FormatActivityTime(time.Now())
|
||
}
|
||
|
||
func FormatActivityTime(t time.Time) string {
|
||
return t.In(activityLocation).Format(ActivityTimeLayout)
|
||
}
|
||
|
||
func ParseActivityTime(value string) (time.Time, bool) {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return time.Time{}, false
|
||
}
|
||
layouts := []string{ActivityTimeLayout, activityTimeLayoutLegacy, time.RFC3339Nano, time.RFC3339}
|
||
for _, layout := range layouts {
|
||
if parsed, err := time.ParseInLocation(layout, value, activityLocation); err == nil {
|
||
return parsed.In(activityLocation), true
|
||
}
|
||
if parsed, err := time.Parse(layout, value); err == nil {
|
||
return parsed.In(activityLocation), true
|
||
}
|
||
}
|
||
return time.Time{}, false
|
||
}
|
||
|
||
func ActivityDate(value string) (string, bool) {
|
||
parsed, ok := ParseActivityTime(value)
|
||
if !ok {
|
||
return "", false
|
||
}
|
||
return parsed.In(activityLocation).Format(ActivityDateLayout), true
|
||
}
|
||
|
||
func HasActivityDate(values []string, date string) bool {
|
||
for _, value := range values {
|
||
if parsedDate, ok := ActivityDate(value); ok && parsedDate == date {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func ActivitySummary(values []string, fallbackDate string) (days int, streak int, lastAt string) {
|
||
dateSet := make(map[string]struct{}, len(values))
|
||
var latest time.Time
|
||
hasLatest := false
|
||
|
||
for _, value := range values {
|
||
parsed, ok := ParseActivityTime(value)
|
||
if !ok {
|
||
continue
|
||
}
|
||
dateKey := parsed.In(activityLocation).Format(ActivityDateLayout)
|
||
dateSet[dateKey] = struct{}{}
|
||
if !hasLatest || parsed.After(latest) {
|
||
latest = parsed
|
||
hasLatest = true
|
||
lastAt = FormatActivityTime(parsed)
|
||
}
|
||
}
|
||
|
||
if len(dateSet) == 0 && strings.TrimSpace(fallbackDate) != "" {
|
||
dateSet[strings.TrimSpace(fallbackDate)] = struct{}{}
|
||
days = 1
|
||
streak = 1
|
||
return
|
||
}
|
||
|
||
days = len(dateSet)
|
||
if !hasLatest {
|
||
return
|
||
}
|
||
|
||
cursor := time.Date(latest.In(activityLocation).Year(), latest.In(activityLocation).Month(), latest.In(activityLocation).Day(), 0, 0, 0, 0, activityLocation)
|
||
for {
|
||
key := cursor.Format(ActivityDateLayout)
|
||
if _, ok := dateSet[key]; !ok {
|
||
break
|
||
}
|
||
streak++
|
||
cursor = cursor.AddDate(0, 0, -1)
|
||
}
|
||
|
||
return
|
||
}
|