Compare commits

..

13 Commits

Author SHA1 Message Date
filifa 35cd759205 fix runtime error when -n is larger than number of rows in query 2024-02-23 23:54:54 -06:00
filifa 1260930b76 add flag for outputting multiple events 2024-02-23 23:04:35 -06:00
filifa bafbc3578c return errors 2024-02-22 23:36:57 -06:00
filifa 2925fb174c print "years ago" instead of date when not yearknown 2024-02-22 22:54:45 -06:00
filifa 76a56879b0 split output code into method 2024-02-22 22:33:54 -06:00
filifa 1ecf16bd0a add infra for unknown dates 2024-02-22 22:25:51 -06:00
filifa 990c9a66bd add basic go program 2024-02-21 22:17:02 -06:00
filifa 4d42e6cae8 add timestamps 2024-02-18 19:04:41 -06:00
filifa 3d381b1a4e don't track db files 2024-02-12 23:51:15 -06:00
filifa f0daafc5a8 add index on timestamp column 2024-02-12 23:41:35 -06:00
filifa ab910e266f restructure table 2024-02-12 23:30:11 -06:00
filifa d997666bc6 delete some bad rows 2024-02-12 23:10:09 -06:00
filifa ad76ffcff4 initial commit 2024-02-12 22:47:58 -06:00
3 changed files with 30 additions and 77 deletions
-39
View File
@@ -1,39 +0,0 @@
# xbit
`xbit` (xkcd backward in time) is a program for outputting historical events
while completing a task, inspired by [xkcd](https://xkcd.com/1017). The program
reads a SQLite database that stores event descriptions and timestamps, allowing
you to easily add your own events of interest.
The provided SQL file will pre-populate the database with thousands of events
scraped from
[Wikipedia](https://en.wikipedia.org/wiki/Detailed_logarithmic_timeline). Of
these events, over 300 were manually given timestamps.
## How to use
First create the database:
```
sqlite3 timeline.db < timeline.sql
```
Then set the `XBIT_DB` environment variable to the database file.
```
export XBIT_DB=./timeline.db
```
Call the program with `xbit -p <percentage>`, like `xbit -p 0.25`. This will
calculate a timestamp using the formula from the xkcd comic, then output
information about the event in the database closest to that timestamp. See
`xbit -h` for more args.
## Database details
* Due to the time-consuming nature of manually assigning timestamps, there are
many events that were scraped from Wikipedia but not given timestamps. You
may wish to skim through these and add timestamps to events you personally find
interesting.
* For an event to be output by `xbit`, it must have an entry in the database
with a unix timestamp of when the event occured. You can optionally set bools
in the "known" columns to indicate how precise your timestamp is. These may be
used to modify how the event is output.
* There are a few other columns that mainly exist as artifacts from the
scraping process. These are not used by `xbit` and do not need to be
populated.
+1 -1
View File
@@ -1,4 +1,4 @@
module scm.dairydemon.net/filifa/xbit module dairydemon.net/xbit
go 1.19 go 1.19
+29 -37
View File
@@ -2,13 +2,11 @@ package main
import ( import (
"database/sql" "database/sql"
"errors"
"flag" "flag"
"fmt" "fmt"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"log" "log"
"math" "math"
"os"
"strconv" "strconv"
"time" "time"
) )
@@ -35,22 +33,28 @@ func formula(p float64) int64 {
return int64(yearsAgo * 31556926) return int64(yearsAgo * 31556926)
} }
func (db *TimelineDB) closestEvents(t int64, n uint, llimit int64, ulimit int64) ([]EventsRow, error) { func (db *TimelineDB) closestEvents(t int64, n int) ([]EventsRow, error) {
query := ` query := `
select description, timestamp, yearknown, monthknown, dayknown, hourknown, minuteknown, secondknown select description, timestamp, yearknown, monthknown, dayknown, hourknown, minuteknown, secondknown
from events from events
where timestamp between ? and ? where timestamp is not null
order by abs(? - timestamp) asc order by abs(? - timestamp) asc
limit ? limit ?
` `
rows, err := db.Query(query, llimit, ulimit, t, n) events := make([]EventsRow, n)
stmt, err := db.Prepare(query)
if err != nil { if err != nil {
return nil, err return events, err
}
defer stmt.Close()
rows, err := stmt.Query(t, n)
if err != nil {
return events, err
} }
defer rows.Close() defer rows.Close()
events := make([]EventsRow, n)
i := 0 i := 0
for ; rows.Next(); i++ { for ; rows.Next(); i++ {
var event EventsRow var event EventsRow
@@ -66,33 +70,36 @@ func (db *TimelineDB) closestEvents(t int64, n uint, llimit int64, ulimit int64)
} }
func (event *EventsRow) Output() (int64, string, string, error) { func (event *EventsRow) Output() (int64, string, string, error) {
if !event.timestamp.Valid { timestamp, err := event.timestamp.Value()
return 0, "", "", errors.New("null timestamp") if err != nil {
} else if !event.description.Valid { return 0, "", "", err
return 0, "", "", errors.New("null description")
} }
timestamp := event.timestamp.Int64 desc, err := event.description.Value()
desc := event.description.String if err != nil {
yearknown := event.yearknown.Valid && event.yearknown.Bool return 0, "", "", err
}
yearknown, err := event.yearknown.Value()
if err != nil {
return 0, "", "", err
}
var ago string var ago string
date := time.Unix(timestamp, 0) date := time.Unix(timestamp.(int64), 0)
if !yearknown { if yearknown == nil || !yearknown.(bool) {
yeardiff := time.Now().Year() - date.Year() yeardiff := time.Now().Year() - date.Year()
ago = strconv.Itoa(yeardiff) + " years ago" ago = strconv.Itoa(yeardiff) + " years ago"
} else { } else {
ago = date.String() ago = date.String()
} }
return timestamp, ago, desc, nil return timestamp.(int64), ago, desc.(string), nil
} }
func main() { func main() {
percent := flag.Float64("p", -1, "percentage") percent := flag.Float64("p", -1, "percentage")
nevents := flag.Uint("n", 1, "number of events") 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() flag.Parse()
if *percent < 0 || *percent > 1 { if *percent < 0 || *percent > 1 {
@@ -101,30 +108,15 @@ func main() {
t := time.Now().Unix() - formula(*percent) t := time.Now().Unix() - formula(*percent)
llimit := int64(math.MinInt64)
ulimit := int64(math.MaxInt64)
if *after && *before {
log.Fatal("cannot pass both -a and -b")
} else if *after {
llimit = t
} else if *before {
ulimit = t
}
dbpath := os.Getenv("XBIT_DB")
if dbpath == "" {
log.Fatal("XBIT_DB not set")
}
var db TimelineDB var db TimelineDB
var err error var err error
db.DB, err = sql.Open("sqlite3", dbpath) db.DB, err = sql.Open("sqlite3", "./timeline.db")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer db.Close() defer db.Close()
events, err := db.closestEvents(t, *nevents, llimit, ulimit) events, err := db.closestEvents(t, *nevents)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }