1package main
2
3import (
4	cryptorand "crypto/rand"
5	"fmt"
6	mathrand "math/rand"
7	"os"
8	"strings"
9	"time"
10
11	"github.com/oklog/ulid"
12	getopt "github.com/pborman/getopt/v2"
13)
14
15const (
16	defaultms = "Mon Jan 02 15:04:05.999 MST 2006"
17	rfc3339ms = "2006-01-02T15:04:05.999MST"
18)
19
20func main() {
21	// Completely obnoxious.
22	getopt.HelpColumn = 50
23	getopt.DisplayWidth = 140
24
25	fs := getopt.New()
26	var (
27		format = fs.StringLong("format", 'f', "default", "when parsing, show times in this format: default, rfc3339, unix, ms", "<format>")
28		local  = fs.BoolLong("local", 'l', "when parsing, show local time instead of UTC")
29		quick  = fs.BoolLong("quick", 'q', "when generating, use non-crypto-grade entropy")
30		zero   = fs.BoolLong("zero", 'z', "when generating, fix entropy to all-zeroes")
31		help   = fs.BoolLong("help", 'h', "print this help text")
32	)
33	if err := fs.Getopt(os.Args, nil); err != nil {
34		fmt.Fprintf(os.Stderr, "%v\n", err)
35		os.Exit(1)
36	}
37	if *help {
38		fs.PrintUsage(os.Stderr)
39		os.Exit(0)
40	}
41
42	var formatFunc func(time.Time) string
43	switch strings.ToLower(*format) {
44	case "default":
45		formatFunc = func(t time.Time) string { return t.Format(defaultms) }
46	case "rfc3339":
47		formatFunc = func(t time.Time) string { return t.Format(rfc3339ms) }
48	case "unix":
49		formatFunc = func(t time.Time) string { return fmt.Sprint(t.Unix()) }
50	case "ms":
51		formatFunc = func(t time.Time) string { return fmt.Sprint(t.UnixNano() / 1e6) }
52	default:
53		fmt.Fprintf(os.Stderr, "invalid --format %s\n", *format)
54		os.Exit(1)
55	}
56
57	switch args := fs.Args(); len(args) {
58	case 0:
59		generate(*quick, *zero)
60	default:
61		parse(args[0], *local, formatFunc)
62	}
63}
64
65func generate(quick, zero bool) {
66	entropy := cryptorand.Reader
67	if quick {
68		seed := time.Now().UnixNano()
69		source := mathrand.NewSource(seed)
70		entropy = mathrand.New(source)
71	}
72	if zero {
73		entropy = zeroReader{}
74	}
75
76	id, err := ulid.New(ulid.Timestamp(time.Now()), entropy)
77	if err != nil {
78		fmt.Fprintf(os.Stderr, "%v\n", err)
79		os.Exit(1)
80	}
81
82	fmt.Fprintf(os.Stdout, "%s\n", id)
83}
84
85func parse(s string, local bool, f func(time.Time) string) {
86	id, err := ulid.Parse(s)
87	if err != nil {
88		fmt.Fprintf(os.Stderr, "%v\n", err)
89		os.Exit(1)
90	}
91
92	var (
93		msec = id.Time()
94		sec  = msec / 1e3
95		rem  = msec % 1e3
96		nsec = rem * 1e6
97		t    = time.Unix(int64(sec), int64(nsec))
98	)
99	if !local {
100		t = t.UTC()
101	}
102	fmt.Fprintf(os.Stderr, "%s\n", f(t))
103}
104
105type zeroReader struct{}
106
107func (zeroReader) Read(p []byte) (int, error) {
108	for i := range p {
109		p[i] = 0
110	}
111	return len(p), nil
112}
113