package report import ( "fmt" "time" "gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil" ) func Resolve(id ID, req ResolveRequest) (Resolved, error) { return DefaultRegistry().Resolve(id, req) } func (r Registry) Resolve(id ID, req ResolveRequest) (Resolved, error) { definition, err := r.Lookup(id) if err != nil { return Resolved{}, err } return r.resolveDefinition(definition, req) } func (r Registry) BatchReports(batch Batch, req ResolveRequest) ([]Resolved, error) { if req.Location == nil { req.Location = time.UTC } if req.Now.IsZero() { req.Now = time.Now() } switch batch { case Morning: ids := []ID{DailyToday, ThreeDay} if req.Now.In(req.Location).Weekday() != time.Sunday { ids = append(ids, Weekend) } return r.resolveIDs(ids, req) case Evening: return r.resolveIDs([]ID{DailyTomorrow}, req) default: return nil, fmt.Errorf("unknown batch %q", batch) } } func (r Registry) resolveIDs(ids []ID, req ResolveRequest) ([]Resolved, error) { resolved := make([]Resolved, 0, len(ids)) for _, id := range ids { item, err := r.Resolve(id, req) if err != nil { return nil, err } resolved = append(resolved, item) } return resolved, nil } func (r Registry) resolveDefinition(definition Definition, req ResolveRequest) (Resolved, error) { if req.Location == nil { req.Location = time.UTC } if req.Now.IsZero() { req.Now = time.Now() } period, err := definition.ResolvePeriod(req) if err != nil { return Resolved{}, err } return Resolved{ Definition: definition, GeneratedAt: req.Now, Timezone: req.Location.String(), ValidPeriod: period, }, nil } func resolveDailyToday(req ResolveRequest) (timeutil.Period, error) { if !req.Date.IsZero() { return timeutil.CivilDay(req.Date, req.Location), nil } return timeutil.CivilDay(req.Now, req.Location), nil } func ParseStormPeriod(start string, end string, location *time.Location) (timeutil.Period, error) { if location == nil { location = time.UTC } startTime, err := timeutil.ParseStormTime(start, location) if err != nil { return timeutil.Period{}, err } endTime, err := timeutil.ParseStormTime(end, location) if err != nil { return timeutil.Period{}, err } period := timeutil.Period{Start: startTime, End: endTime} if !period.IsValid() { return timeutil.Period{}, fmt.Errorf("storm report requires end time after start time") } return period, nil } func resolveDailyTomorrow(req ResolveRequest) (timeutil.Period, error) { return timeutil.CivilDay(req.Now.In(req.Location).AddDate(0, 0, 1), req.Location), nil } func resolveThreeDay(req ResolveRequest) (timeutil.Period, error) { localNow := req.Now.In(req.Location) endDate := localNow.AddDate(0, 0, 3) end := time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 0, 0, 0, 0, req.Location) return timeutil.Period{Start: localNow, End: end}, nil } func resolveWeekend(req ResolveRequest) (timeutil.Period, error) { localNow := req.Now.In(req.Location) weekday := localNow.Weekday() if weekday == time.Sunday { return timeutil.Period{}, fmt.Errorf("weekend outlook is not scheduled on Sunday morning") } daysUntilSaturday := (int(time.Saturday) - int(weekday) + 7) % 7 saturday := localNow.AddDate(0, 0, daysUntilSaturday) start := time.Date(saturday.Year(), saturday.Month(), saturday.Day(), 0, 0, 0, 0, req.Location) if weekday == time.Friday || weekday == time.Saturday { friday := start.AddDate(0, 0, -1) fridayEvening := time.Date(friday.Year(), friday.Month(), friday.Day(), 18, 0, 0, 0, req.Location) start = fridayEvening if localNow.After(start) { start = localNow } } end := time.Date(saturday.Year(), saturday.Month(), saturday.Day(), 0, 0, 0, 0, req.Location).AddDate(0, 0, 2) return timeutil.Period{Start: start, End: end}, nil } func resolveStorm(req ResolveRequest) (timeutil.Period, error) { if req.StormStart.IsZero() { return timeutil.Period{}, fmt.Errorf("storm report requires a start time") } if req.StormEnd.IsZero() { return timeutil.Period{}, fmt.Errorf("storm report requires an end time") } if !req.StormEnd.After(req.StormStart) { return timeutil.Period{}, fmt.Errorf("storm report requires end time after start time") } return timeutil.Period{Start: req.StormStart, End: req.StormEnd}, nil }