// Next returns the next summary or an error. When there are no more summaries, // the error io.EOF is returned. func (s *SummaryIterator) Next() (*Summary, error) { // Fetch the first entry on first call to Next to create periods iterator. // Doing it here rather than in the constructor to avoid callers having to // implement additional logic when the db is empty. var err error if s.entry == nil { if s.entry, err = s.entries.Next(); err != nil { return nil, err } s.periods = datetime.NewIterator(s.entry.Start, s.period, false, s.firstDay) } summary := &Summary{Categories: make(map[string]time.Duration)} summary.From, summary.To = s.periods.Next() for { duration := s.entry.PartialDuration(s.now, summary.From, summary.To) if duration > 0 { summary.Categories[s.entry.CategoryID] += duration } if s.entry.Start.Before(summary.From) { break } if s.entry, err = s.entries.Next(); err != nil { return nil, err } } return summary, nil }
func cmdReport(d db.DB, categoryS, periodS, firstDayS string) { period, err := datetime.ParsePeriod(periodS) if err != nil { fatal(err) } else if period == datetime.Day { fatal(errors.New("bad period: day")) } firstDay, err := datetime.ParseWeekday(firstDayS) if err != nil { fatal(err) } path, err := d.CategoryPath(ParseCategory(categoryS), false) if err != nil { fatal(err) } entryItr, err := d.Query(db.Query{CategoryID: path.CategoryID()}) if err != nil { fatal(err) } defer entryItr.Close() entry, err := entryItr.Next() if err == io.EOF { return } else if err != nil { fatal(err) } // @TODO move logic into separate function now := time.Now() reportItr := datetime.NewIterator(entry.Start, period, false, firstDay) report := &Report{Duration: period} report.From, report.To = reportItr.Next() dayItr := datetime.NewIterator(report.To, datetime.Day, false, firstDay) day := &ReportDay{} day.From, day.To = dayItr.Next() report.Days = append([]*ReportDay{day}, report.Days...) noteAssigned := false outer: for { var overlap time.Duration for { overlap = entry.PartialDuration(now, day.From, day.To) if overlap > 0 { day.Tracked += overlap if !noteAssigned { note := strings.Trim(entry.Note, "\n") if note != "" { day.Notes = append([]string{note}, day.Notes...) } noteAssigned = true } } if !entry.Start.Before(day.From) { entry, err = entryItr.Next() if err == io.EOF { fmt.Fprint(os.Stdout, FormatReport(report)) break outer } else if err != nil { fatal(err) } noteAssigned = false continue } day = &ReportDay{} day.From, day.To = dayItr.Next() if day.To.Before(report.From) { fmt.Fprint(os.Stdout, FormatReport(report)) report = &Report{Duration: period} report.From, report.To = reportItr.Next() } report.Days = append([]*ReportDay{day}, report.Days...) } } }