yao/agent/robot/utils/time.go
Max 90b52eaf22 Update TODO.md to Reflect Completion of Phases 1 and 2
- Marked Phase 1: Types & Interfaces as complete with 88.4% test coverage and all tests passing.
- Updated Phase 2: Skeleton Implementation status to complete, confirming all packages compile successfully without circular dependencies.
- Checked off all tasks under both phases, indicating full implementation of types, interfaces, and skeleton structures.
2026-01-14 18:03:08 +08:00

114 lines
2.8 KiB
Go

package utils
import (
"fmt"
"strconv"
"strings"
"time"
)
// ParseTime parses a time string in HH:MM format
func ParseTime(timeStr string) (hour, minute int, err error) {
parts := strings.Split(timeStr, ":")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("invalid time format: %s (expected HH:MM)", timeStr)
}
hour, err = strconv.Atoi(parts[0])
if err != nil || hour < 0 || hour > 23 {
return 0, 0, fmt.Errorf("invalid hour: %s", parts[0])
}
minute, err = strconv.Atoi(parts[1])
if err != nil || minute < 0 || minute > 59 {
return 0, 0, fmt.Errorf("invalid minute: %s", parts[1])
}
return hour, minute, nil
}
// FormatTime formats hour and minute into HH:MM format
func FormatTime(hour, minute int) string {
return fmt.Sprintf("%02d:%02d", hour, minute)
}
// LoadLocation loads a timezone location, returns Local if empty or invalid
func LoadLocation(tz string) *time.Location {
if tz == "" {
return time.Local
}
loc, err := time.LoadLocation(tz)
if err != nil {
return time.Local
}
return loc
}
// ParseDuration parses a duration string with fallback default
func ParseDuration(durStr string, defaultDur time.Duration) time.Duration {
if durStr == "" {
return defaultDur
}
d, err := time.ParseDuration(durStr)
if err != nil {
return defaultDur
}
return d
}
// IsTimeMatch checks if current time matches the specified time (HH:MM)
func IsTimeMatch(now time.Time, timeStr string, loc *time.Location) bool {
hour, minute, err := ParseTime(timeStr)
if err != nil {
return false
}
nowInLoc := now.In(loc)
return nowInLoc.Hour() == hour && nowInLoc.Minute() == minute
}
// IsDayMatch checks if current day matches the specified day
// days can be: "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", or "*" for any day
func IsDayMatch(now time.Time, days []string) bool {
if len(days) == 0 {
return true
}
dayName := now.Weekday().String()[:3] // "Monday" -> "Mon"
for _, day := range days {
if day == "*" || day == dayName {
return true
}
}
return false
}
// NextScheduledTime calculates the next time a scheduled time will occur
func NextScheduledTime(now time.Time, timeStr string, days []string, loc *time.Location) (time.Time, error) {
hour, minute, err := ParseTime(timeStr)
if err != nil {
return time.Time{}, err
}
nowInLoc := now.In(loc)
// Start from today at the specified time
next := time.Date(nowInLoc.Year(), nowInLoc.Month(), nowInLoc.Day(), hour, minute, 0, 0, loc)
// If the time has passed today, start from tomorrow
if next.Before(nowInLoc) || next.Equal(nowInLoc) {
next = next.Add(24 * time.Hour)
}
// Find the next matching day (within 7 days)
for i := 0; i < 7; i++ {
if IsDayMatch(next, days) {
return next, nil
}
next = next.Add(24 * time.Hour)
}
// If no matching day found (should not happen with valid days), return the calculated time
return next, nil
}