Files
SproutGate/sproutgate-backend/internal/models/activity.go
2026-03-20 20:42:33 +08:00

107 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}