mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-07-24 06:23:36 +00:00
119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
package wc
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const ictOffset = 7 * time.Hour
|
|
|
|
const formatHint = "Use dd-mm-yyyy, dd/mm/yyyy, or ddmmyyyy."
|
|
|
|
// IctLocation is the fixed-offset UTC+7 timezone for schedule display.
|
|
var IctLocation = time.FixedZone("ICT", int(ictOffset/time.Second))
|
|
|
|
type parseDateResult struct {
|
|
OK bool
|
|
Date time.Time
|
|
Error string
|
|
}
|
|
|
|
var digitsOnly = regexp.MustCompile(`^\d+$`)
|
|
|
|
func ictDayStartOf(now time.Time) time.Time {
|
|
ict := now.In(IctLocation)
|
|
dayStart := time.Date(ict.Year(), ict.Month(), ict.Day(), 0, 0, 0, 0, IctLocation)
|
|
return dayStart.UTC()
|
|
}
|
|
|
|
func ictWeekStartOf(now time.Time) time.Time {
|
|
day := ictDayStartOf(now).In(IctLocation)
|
|
daysFromMonday := (int(day.Weekday()) + 6) % 7
|
|
return day.AddDate(0, 0, -daysFromMonday).UTC()
|
|
}
|
|
|
|
func addDays(date time.Time, days int) time.Time {
|
|
return date.Add(time.Duration(days) * 24 * time.Hour)
|
|
}
|
|
|
|
func splitParts(trimmed string) ([]string, string) {
|
|
if strings.ContainsAny(trimmed, "-/") {
|
|
normalized := strings.ReplaceAll(trimmed, "/", "-")
|
|
parts := strings.Split(normalized, "-")
|
|
if len(parts) < 1 || len(parts) > 3 {
|
|
return nil, fmt.Sprintf(`Invalid date %q. %s`, trimmed, formatHint)
|
|
}
|
|
for _, p := range parts {
|
|
if p == "" || !digitsOnly.MatchString(p) {
|
|
return nil, fmt.Sprintf(`Invalid date %q. %s`, trimmed, formatHint)
|
|
}
|
|
}
|
|
return parts, ""
|
|
}
|
|
|
|
if !digitsOnly.MatchString(trimmed) {
|
|
return nil, fmt.Sprintf(`Invalid date %q. %s`, trimmed, formatHint)
|
|
}
|
|
switch len(trimmed) {
|
|
case 1, 2:
|
|
return []string{trimmed}, ""
|
|
case 4:
|
|
return []string{trimmed[:2], trimmed[2:]}, ""
|
|
case 8:
|
|
return []string{trimmed[:2], trimmed[2:4], trimmed[4:]}, ""
|
|
default:
|
|
return nil, fmt.Sprintf(`Invalid date %q. %s`, trimmed, formatHint)
|
|
}
|
|
}
|
|
|
|
// ParseScheduleDate parses a /wc date argument. Empty input means today in ICT.
|
|
func ParseScheduleDate(input string, now time.Time) parseDateResult {
|
|
trimmed := strings.TrimSpace(input)
|
|
if trimmed == "" {
|
|
return parseDateResult{OK: true, Date: ictDayStartOf(now)}
|
|
}
|
|
|
|
parts, errMsg := splitParts(trimmed)
|
|
if errMsg != "" {
|
|
return parseDateResult{Error: errMsg}
|
|
}
|
|
|
|
ictNow := now.In(IctLocation)
|
|
day, _ := strconv.Atoi(parts[0])
|
|
month := int(ictNow.Month())
|
|
year := ictNow.Year()
|
|
if len(parts) >= 2 {
|
|
month, _ = strconv.Atoi(parts[1])
|
|
}
|
|
if len(parts) >= 3 {
|
|
year, _ = strconv.Atoi(parts[2])
|
|
}
|
|
|
|
if day < 1 || day > 31 {
|
|
return parseDateResult{Error: fmt.Sprintf(`Invalid day %q - must be 1-31.`, parts[0])}
|
|
}
|
|
if month < 1 || month > 12 {
|
|
monthStr := ""
|
|
if len(parts) >= 2 {
|
|
monthStr = parts[1]
|
|
}
|
|
return parseDateResult{Error: fmt.Sprintf(`Invalid month %q - must be 1-12.`, monthStr)}
|
|
}
|
|
if year < 1970 || year > 2100 {
|
|
yearStr := ""
|
|
if len(parts) >= 3 {
|
|
yearStr = parts[2]
|
|
}
|
|
return parseDateResult{Error: fmt.Sprintf(`Invalid year %q.`, yearStr)}
|
|
}
|
|
|
|
candidate := time.Date(year, time.Month(month), day, 0, 0, 0, 0, IctLocation)
|
|
if candidate.Year() != year || int(candidate.Month()) != month || candidate.Day() != day {
|
|
return parseDateResult{Error: fmt.Sprintf(`Invalid date - %d/%d/%d does not exist.`, day, month, year)}
|
|
}
|
|
return parseDateResult{OK: true, Date: candidate.UTC()}
|
|
}
|