From e424bb970a7371a81e7b7e6888089d4858e62a03 Mon Sep 17 00:00:00 2001 From: Martin Czygan Date: Tue, 30 Nov 2021 15:03:02 +0100 Subject: [PATCH] add date interval utils --- dateutil/intervals.go | 79 ++++++++++++++++++++++++++++++++++++++ dateutil/intervals_test.go | 76 ++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ go.sum | 9 +++++ xflag/date.go | 28 ++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 dateutil/intervals.go create mode 100644 dateutil/intervals_test.go create mode 100644 xflag/date.go diff --git a/dateutil/intervals.go b/dateutil/intervals.go new file mode 100644 index 00000000..7248f1d1 --- /dev/null +++ b/dateutil/intervals.go @@ -0,0 +1,79 @@ +// Package dateutil provides interval handling and a custom flag. +package dateutil + +import ( + "time" + + "github.com/araddon/dateparse" + "github.com/jinzhu/now" +) + +var ( + // EveryMinute will chop up a timespan into 60s intervals; + // https://english.stackexchange.com/questions/3091/weekly-daily-hourly-minutely. + EveryMinute = makeIntervalFunc(padLMinute, padRMinute) + Hourly = makeIntervalFunc(padLHour, padRHour) + Daily = makeIntervalFunc(padLDay, padRDay) + Weekly = makeIntervalFunc(padLWeek, padRWeek) + Monthly = makeIntervalFunc(padLMonth, padRMonth) + + padLMinute = func(t time.Time) time.Time { return now.With(t).BeginningOfMinute() } + padRMinute = func(t time.Time) time.Time { return now.With(t).EndOfMinute() } + padLHour = func(t time.Time) time.Time { return now.With(t).BeginningOfHour() } + padRHour = func(t time.Time) time.Time { return now.With(t).EndOfHour() } + padLDay = func(t time.Time) time.Time { return now.With(t).BeginningOfDay() } + padRDay = func(t time.Time) time.Time { return now.With(t).EndOfDay() } + padLWeek = func(t time.Time) time.Time { return now.With(t).BeginningOfWeek() } + padRWeek = func(t time.Time) time.Time { return now.With(t).EndOfWeek() } + padLMonth = func(t time.Time) time.Time { return now.With(t).BeginningOfMonth() } + padRMonth = func(t time.Time) time.Time { return now.With(t).EndOfMonth() } +) + +// Interval groups start and end. +type Interval struct { + Start time.Time + End time.Time +} + +type ( + // padFunc allows to move a given time back and forth. + padFunc func(t time.Time) time.Time + // intervalFunc takes a start and endtime and returns a number of + // intervals. How intervals are generated is flexible. + intervalFunc func(s, e time.Time) []Interval +) + +// makeIntervalFunc is a helper to create daily, weekly and other intervals. +// Given two shiftFuncs (to mark the beginning of an interval and the end), we +// return a function, that will allow us to generate intervals. +// TODO: We only need right pad, no? +func makeIntervalFunc(padLeft, padRight padFunc) intervalFunc { + return func(s, e time.Time) (result []Interval) { + if e.Before(s) || e.Equal(s) { + return + } + e = e.Add(-1 * time.Second) + var ( + l time.Time = s + r time.Time + ) + for { + r = padRight(l) + result = append(result, Interval{l, r}) + l = padLeft(r.Add(1 * time.Second)) + if l.After(e) { + break + } + } + return result + } +} + +// MustParse will panic on an unparsable date string. +func MustParse(value string) time.Time { + t, err := dateparse.ParseStrict(value) + if err != nil { + panic(err) + } + return t +} diff --git a/dateutil/intervals_test.go b/dateutil/intervals_test.go new file mode 100644 index 00000000..527414d5 --- /dev/null +++ b/dateutil/intervals_test.go @@ -0,0 +1,76 @@ +package dateutil + +import ( + "testing" + "time" +) + +func TestMakeIntervalFunc(t *testing.T) { + var cases = []struct { + padLeft padFunc + padRight padFunc + start time.Time + end time.Time + numIntervals int + }{ + { + padLeft: padLDay, + padRight: padRDay, + start: MustParse("2000-01-01"), + end: MustParse("2000-01-01"), + numIntervals: 0, + }, + { + padLeft: padLDay, + padRight: padRDay, + start: MustParse("2000-01-01"), + end: MustParse("1999-01-01"), + numIntervals: 0, + }, + { + padLeft: padLDay, + padRight: padRDay, + start: MustParse("2000-01-01"), + end: MustParse("2001-01-01"), + numIntervals: 366, + }, + { + padLeft: padLHour, + padRight: padRHour, + start: MustParse("2000-01-01 10:00"), + end: MustParse("2000-01-01 12:00"), + numIntervals: 2, + }, + { + padLeft: padLHour, + padRight: padRHour, + start: MustParse("2000-01-01 10:30"), + end: MustParse("2000-01-01 12:00"), + numIntervals: 2, + }, + { + padLeft: padLMonth, + padRight: padRMonth, + start: MustParse("2000-01-01 10:30"), + end: MustParse("2000-01-01 12:00"), + numIntervals: 1, + }, + } + for i, c := range cases { + f := makeIntervalFunc(c.padLeft, c.padRight) + ivs := f(c.start, c.end) + t.Logf("[%d] start: %v, end: %v", i, c.start, c.end) + switch len(ivs) { + case 0: + case 1: + t.Logf("[%d] [%v]", i, ivs[0]) + case 2: + t.Logf("[%d] [%v, %v]", i, ivs[0], ivs[1]) + default: + t.Logf("[%d] [%v, ..., %v]", i, ivs[0], ivs[len(ivs)-1]) + } + if len(ivs) != c.numIntervals { + t.Fatalf("[%d] got %d, want %d", i, len(ivs), c.numIntervals) + } + } +} diff --git a/go.mod b/go.mod index 90f8c929..bfe6a28a 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/abadojack/whatlanggo v1.0.1 github.com/adrg/xdg v0.3.4 github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 + github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/beevik/etree v1.1.0 github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 github.com/dustin/go-humanize v1.0.0 @@ -16,6 +17,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hoisie/mustache v0.0.0-20160804235033-6375acf62c69 // indirect github.com/ilyakaznacheev/cleanenv v1.2.5 + github.com/jinzhu/now v1.1.3 github.com/jmoiron/sqlx v1.3.4 github.com/joho/godotenv v1.4.0 // indirect github.com/kennygrant/sanitize v1.2.4 @@ -23,6 +25,7 @@ require ( github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/klauspost/pgzip v1.2.5 github.com/kr/pretty v0.3.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/lytics/logrus v0.0.0-20170528191427-4389a17ed024 github.com/mattn/go-colorable v0.1.10 // indirect github.com/mattn/go-sqlite3 v1.14.8 diff --git a/go.sum b/go.sum index d3564746..c192cd66 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/adrg/xdg v0.3.4 h1:0BivHfQ0LSGQrFTaEZ0hyQLm/HAidci7m+1cT6wKKdA= github.com/adrg/xdg v0.3.4/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= @@ -35,6 +37,8 @@ github.com/hoisie/mustache v0.0.0-20160804235033-6375acf62c69 h1:umaj0TCQ9lWUUKy github.com/hoisie/mustache v0.0.0-20160804235033-6375acf62c69/go.mod h1:zdLK9ilQRSMjSeLKoZ4BqUfBT7jswTGF8zRlKEsiRXA= github.com/ilyakaznacheev/cleanenv v1.2.5 h1:/SlcF9GaIvefWqFJzsccGG/NJdoaAwb7Mm7ImzhO3DM= github.com/ilyakaznacheev/cleanenv v1.2.5/go.mod h1:/i3yhzwZ3s7hacNERGFwvlhwXMDcaqwIzmayEhbRplk= +github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI= +github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -56,6 +60,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lytics/logrus v0.0.0-20170528191427-4389a17ed024 h1:QaKVrqyQRNPbdBNCpiU0Ei3iDQko3qoiUUXMiTWhzZM= @@ -66,6 +72,7 @@ github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMop github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -83,8 +90,10 @@ github.com/olebedev/config v0.0.0-20190528211619-364964f3a8e4 h1:JnVsYEQzhEcOspy github.com/olebedev/config v0.0.0-20190528211619-364964f3a8e4/go.mod h1:RL5+WRxWTAXqqCi9i+eZlHrUtO7AQujUqWi+xMohmc4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/segmentio/asm v1.0.1 h1:g9VK62hXylgXI4yJV+dLTu/1j7kTxG9bkUSYBxL9dpg= github.com/segmentio/asm v1.0.1/go.mod h1:4EUJGaKsB8ImLUwOGORVsNd9vTRDeh44JGsY4aKp5I4= github.com/segmentio/encoding v0.2.21 h1:hlRQz3Pv+/mBj+jqr46TVqqv6AuTwvP5aAxQ0Usd4gY= diff --git a/xflag/date.go b/xflag/date.go new file mode 100644 index 00000000..cf4665f4 --- /dev/null +++ b/xflag/date.go @@ -0,0 +1,28 @@ +package xflag + +import ( + "time" + + "github.com/araddon/dateparse" +) + +// Date can be used to parse command line args into dates. +type Date struct { + time.Time +} + +// String returns a formatted date. +func (d *Date) String() string { + return d.Format("2006-01-02") +} + +// Set parses a value into a date, relatively flexible due to +// araddon/dateparse, 2014-04-26 will work, but oct. 7, 1970, too. +func (d *Date) Set(value string) error { + t, err := dateparse.ParseStrict(value) + if err != nil { + return err + } + *d = Date{t} + return nil +}