package main import ( "database/sql" "errors" "flag" "fmt" _ "github.com/mattn/go-sqlite3" "log" "math" "strconv" "time" ) type EventsRow struct { description sql.NullString timestamp sql.NullInt64 interval sql.NullString period sql.NullString yearknown sql.NullBool monthknown sql.NullBool dayknown sql.NullBool hourknown sql.NullBool minuteknown sql.NullBool secondknown sql.NullBool } type TimelineDB struct { *sql.DB } func formula(p float64) int64 { yearsAgo := math.Exp(20.3444*math.Pow(p, 3)+3) - math.Exp(3) return int64(yearsAgo * 31556926) } func (db *TimelineDB) closestEvents(t int64, n int, after bool, before bool) ([]EventsRow, error) { ulimit := int64(math.MaxInt64) llimit := int64(math.MinInt64) if after && before { return nil, errors.New("after and before can't both be true") } else if after { llimit = t } else if before { ulimit = t } query := ` select description, timestamp, yearknown, monthknown, dayknown, hourknown, minuteknown, secondknown from events where timestamp between ? and ? order by abs(? - timestamp) asc limit ? ` stmt, err := db.Prepare(query) if err != nil { return nil, err } defer stmt.Close() rows, err := stmt.Query(llimit, ulimit, t, n) if err != nil { return nil, err } defer rows.Close() events := make([]EventsRow, n) i := 0 for ; rows.Next(); i++ { var event EventsRow err = rows.Scan(&event.description, &event.timestamp, &event.yearknown, &event.monthknown, &event.dayknown, &event.hourknown, &event.minuteknown, &event.secondknown) if err != nil { return events, err } events[i] = event } return events[:i], err } func (event *EventsRow) Output() (int64, string, string, error) { if !event.timestamp.Valid { return 0, "", "", errors.New("null timestamp") } else if !event.description.Valid { return 0, "", "", errors.New("null description") } timestamp := event.timestamp.Int64 desc := event.description.String yearknown := event.yearknown.Valid && event.yearknown.Bool var ago string date := time.Unix(timestamp, 0) if !yearknown { yeardiff := time.Now().Year() - date.Year() ago = strconv.Itoa(yeardiff) + " years ago" } else { ago = date.String() } return timestamp, ago, desc, nil } func main() { percent := flag.Float64("p", -1, "percentage") nevents := flag.Int("n", 1, "number of events") after := flag.Bool("a", false, "only return events occurring after computed timestamp") before := flag.Bool("b", false, "only return events occurring before computed timestamp") flag.Parse() if *after && *before { log.Fatal("cannot pass both -a and -b") } if *percent < 0 || *percent > 1 { log.Fatal("invalid percentage") } else if *nevents < 0 { log.Fatal("number of events must be non-negative") } t := time.Now().Unix() - formula(*percent) var db TimelineDB var err error db.DB, err = sql.Open("sqlite3", "./timeline.db") if err != nil { log.Fatal(err) } defer db.Close() events, err := db.closestEvents(t, *nevents, *after, *before) if err != nil { log.Fatal(err) } for _, event := range events { timestamp, date, desc, err := event.Output() if err != nil { log.Fatal(err) } fmt.Printf("%v\t%v\t%v\n", timestamp, date, desc) } }