92 lines
3.0 KiB
Go
92 lines
3.0 KiB
Go
package tables
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// IsCalendarAlignableDuration reports whether the given duration string supports calendar-aligned resets.
|
|
// Only day ("d"), week ("w"), month ("M"), and year ("Y") suffixes have natural calendar boundaries.
|
|
// Sub-day durations like "1h", "30m" are not alignable.
|
|
func IsCalendarAlignableDuration(duration string) bool {
|
|
if duration == "" {
|
|
return false
|
|
}
|
|
switch duration[len(duration)-1] {
|
|
case 'd', 'w', 'M', 'Y':
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// GetCalendarPeriodStart returns the start of the current calendar period for the given duration and time.
|
|
// For calendar-scale durations (daily, weekly, monthly, yearly) it snaps to clean boundaries in UTC:
|
|
// - "Nd" → midnight UTC on the current day
|
|
// - "Nw" → midnight UTC on the most recent Monday
|
|
// - "NM" → midnight UTC on the 1st of the current month
|
|
// - "NY" → midnight UTC on Jan 1 of the current year
|
|
//
|
|
// For all other durations (e.g. "1h", "30m") the original time t is returned unchanged,
|
|
// since sub-day periods don't have a natural calendar boundary.
|
|
func GetCalendarPeriodStart(duration string, t time.Time) time.Time {
|
|
if duration == "" {
|
|
return t
|
|
}
|
|
t = t.UTC()
|
|
suffix := duration[len(duration)-1:]
|
|
switch suffix {
|
|
case "d":
|
|
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
|
|
case "w":
|
|
weekday := int(t.Weekday())
|
|
// Sunday = 0, so shift to Monday = 0
|
|
daysFromMonday := (weekday + 6) % 7
|
|
monday := t.AddDate(0, 0, -daysFromMonday)
|
|
return time.Date(monday.Year(), monday.Month(), monday.Day(), 0, 0, 0, 0, time.UTC)
|
|
case "M":
|
|
return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
case "Y":
|
|
return time.Date(t.Year(), time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
default:
|
|
return t
|
|
}
|
|
}
|
|
|
|
// ParseDuration function to parse duration strings
|
|
func ParseDuration(duration string) (time.Duration, error) {
|
|
if duration == "" {
|
|
return 0, fmt.Errorf("duration is empty")
|
|
}
|
|
|
|
// Handle special cases for days, weeks, months, years
|
|
switch {
|
|
case duration[len(duration)-1:] == "d":
|
|
days := duration[:len(duration)-1]
|
|
if d, err := time.ParseDuration(days + "h"); err == nil {
|
|
return d * 24, nil
|
|
}
|
|
return 0, fmt.Errorf("invalid day duration: %s", duration)
|
|
case duration[len(duration)-1:] == "w":
|
|
weeks := duration[:len(duration)-1]
|
|
if w, err := time.ParseDuration(weeks + "h"); err == nil {
|
|
return w * 24 * 7, nil
|
|
}
|
|
return 0, fmt.Errorf("invalid week duration: %s", duration)
|
|
case duration[len(duration)-1:] == "M":
|
|
months := duration[:len(duration)-1]
|
|
if m, err := time.ParseDuration(months + "h"); err == nil {
|
|
return m * 24 * 30, nil // Approximate month as 30 days
|
|
}
|
|
return 0, fmt.Errorf("invalid month duration: %s", duration)
|
|
case duration[len(duration)-1:] == "Y":
|
|
years := duration[:len(duration)-1]
|
|
if y, err := time.ParseDuration(years + "h"); err == nil {
|
|
return y * 24 * 365, nil // Approximate year as 365 days
|
|
}
|
|
return 0, fmt.Errorf("invalid year duration: %s", duration)
|
|
default:
|
|
return time.ParseDuration(duration)
|
|
}
|
|
}
|