diff --git a/interval.go b/interval.go index 3857e64..e08390b 100644 --- a/interval.go +++ b/interval.go @@ -8,7 +8,7 @@ type Interval struct { NotAfter *time.Time } -// Contain return if time t is in the interval +// Contain returns if time t is in the interval func (i Interval) Contain(t time.Time) bool { if i.NotAfter != nil && t.After(*i.NotAfter) { return false @@ -46,7 +46,7 @@ func (i Interval) EndAt(t time.Time) Interval { return i.BeforeOrEqual(t) } -// BeforeOrEqual return a new Interval which not before t +// BeforeOrEqual returns a new Interval which not before t func (i Interval) BeforeOrEqual(t time.Time) Interval { return Interval{ NotBefore: i.NotBefore, @@ -54,7 +54,7 @@ func (i Interval) BeforeOrEqual(t time.Time) Interval { } } -// AfterOrEqual return a new Interval which not after t +// AfterOrEqual returns a new Interval which not after t func (i Interval) AfterOrEqual(t time.Time) Interval { return Interval{ NotBefore: &t, @@ -62,7 +62,7 @@ func (i Interval) AfterOrEqual(t time.Time) Interval { } } -// Before return a new Interval which before t +// Before returns a new Interval which before t func (i Interval) Before(t time.Time) Interval { t = t.Add(-1) return Interval{ @@ -71,7 +71,7 @@ func (i Interval) Before(t time.Time) Interval { } } -// After return a new Interval which after t +// After returns a new Interval which after t func (i Interval) After(t time.Time) Interval { t = t.Add(1) return Interval{ @@ -80,6 +80,35 @@ func (i Interval) After(t time.Time) Interval { } } +// Truncate returns the result of rounding interval down to a multiple of d (since the zero time). +func (i Interval) Truncate(d time.Duration) Interval { + if i.NotBefore != nil { + t := (*i.NotBefore).Truncate(d) + if t.Before(*i.NotBefore) { + t = t.Add(d) + } + i.NotBefore = &t + } + if i.NotAfter != nil { + t := (*i.NotAfter).Truncate(d) + i.NotAfter = &t + } + return i +} + +// Duration returns the duration NotAfter - NotBefore, +// returns 0 if NotAfter is before or equal NotBefore, +// returns -1 if NotAfter or NotBefore if nil. +func (i Interval) Duration() time.Duration { + if i.NotBefore == nil || i.NotAfter == nil { + return -1 + } + if !(*i.NotAfter).After(*i.NotBefore) { + return 0 + } + return (*i.NotAfter).Sub(*i.NotBefore) +} + // BeginAt is alias of AfterOrEqual func BeginAt(t time.Time) Interval { return AfterOrEqual(t) @@ -90,21 +119,21 @@ func EndAt(t time.Time) Interval { return BeforeOrEqual(t) } -// BeforeOrEqual return a new Interval which not before t +// BeforeOrEqual returns a new Interval which not before t func BeforeOrEqual(t time.Time) Interval { return Interval{ NotAfter: &t, } } -// AfterOrEqual return a new Interval which not after t +// AfterOrEqual returns a new Interval which not after t func AfterOrEqual(t time.Time) Interval { return Interval{ NotBefore: &t, } } -// Before return a new Interval which before t +// Before returns a new Interval which before t func Before(t time.Time) Interval { t = t.Add(-1) return Interval{ @@ -112,7 +141,7 @@ func Before(t time.Time) Interval { } } -// After return a new Interval which after t +// After returns a new Interval which after t func After(t time.Time) Interval { t = t.Add(1) return Interval{ diff --git a/interval_test.go b/interval_test.go index b4f9052..608f4c3 100644 --- a/interval_test.go +++ b/interval_test.go @@ -2,10 +2,24 @@ package timeseq import ( "fmt" + "reflect" "testing" "time" ) +func parseTime(s string) time.Time { + ret, err := time.Parse(time.RFC3339, s) + if err != nil { + panic(err) + } + return ret +} + +func parseTimeP(s string) *time.Time { + ret := parseTime(s) + return &ret +} + func TestInterval_Contain(t *testing.T) { now := time.Now() type args struct { @@ -219,3 +233,121 @@ func TestInterval_Format(t *testing.T) { }) } } + +func TestInterval_Truncate(t *testing.T) { + type fields struct { + NotBefore *time.Time + NotAfter *time.Time + } + type args struct { + d time.Duration + } + tests := []struct { + name string + fields fields + args args + want Interval + }{ + { + name: "regular", + fields: fields{ + NotBefore: parseTimeP("2021-02-02T20:34:10+08:00"), + NotAfter: parseTimeP("2021-02-02T21:34:10+08:00"), + }, + args: args{ + d: time.Minute, + }, + want: Interval{ + NotBefore: parseTimeP("2021-02-02T20:35:00+08:00"), + NotAfter: parseTimeP("2021-02-02T21:34:00+08:00"), + }, + }, + { + name: "unchanged", + fields: fields{ + NotBefore: parseTimeP("2021-02-02T20:35:00+08:00"), + NotAfter: parseTimeP("2021-02-02T21:34:00+08:00"), + }, + args: args{ + d: time.Minute, + }, + want: Interval{ + NotBefore: parseTimeP("2021-02-02T20:35:00+08:00"), + NotAfter: parseTimeP("2021-02-02T21:34:00+08:00"), + }, + }, + { + name: "nil", + fields: fields{ + NotBefore: nil, + NotAfter: nil, + }, + args: args{ + d: time.Minute, + }, + want: Interval{ + NotBefore: nil, + NotAfter: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := Interval{ + NotBefore: tt.fields.NotBefore, + NotAfter: tt.fields.NotAfter, + } + if got := i.Truncate(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Truncate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestInterval_Duration(t *testing.T) { + type fields struct { + NotBefore *time.Time + NotAfter *time.Time + } + tests := []struct { + name string + fields fields + want time.Duration + }{ + { + name: "regular", + fields: fields{ + NotBefore: parseTimeP("2021-02-02T20:35:00+08:00"), + NotAfter: parseTimeP("2021-02-02T20:36:00+08:00"), + }, + want: time.Minute, + }, + { + name: "nil", + fields: fields{ + NotBefore: nil, + NotAfter: parseTimeP("2021-02-02T20:36:00+08:00"), + }, + want: -1, + }, + { + name: "zero", + fields: fields{ + NotBefore: parseTimeP("2021-02-02T20:36:00+08:00"), + NotAfter: parseTimeP("2021-02-02T20:36:00+08:00"), + }, + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := Interval{ + NotBefore: tt.fields.NotBefore, + NotAfter: tt.fields.NotAfter, + } + if got := i.Duration(); got != tt.want { + t.Errorf("Duration() = %v, want %v", got, tt.want) + } + }) + } +}