diff options
author | Kevin Chabowski <kevin@kch42.de> | 2013-08-26 14:17:17 +0200 |
---|---|---|
committer | Kevin Chabowski <kevin@kch42.de> | 2013-08-26 14:17:17 +0200 |
commit | f94eab4b4b830ac47f20e6d91b02c0a01584becf (patch) | |
tree | 3f2f448b6a4f961d8e61ab268e8b04fe8ce3ad64 /chronos | |
download | mailremind-f94eab4b4b830ac47f20e6d91b02c0a01584becf.tar.gz mailremind-f94eab4b4b830ac47f20e6d91b02c0a01584becf.tar.bz2 mailremind-f94eab4b4b830ac47f20e6d91b02c0a01584becf.zip |
Added Chronos for calculating times in the future.
Diffstat (limited to 'chronos')
-rw-r--r-- | chronos/chronos.go | 163 | ||||
-rw-r--r-- | chronos/chronos_test.go | 37 |
2 files changed, 200 insertions, 0 deletions
diff --git a/chronos/chronos.go b/chronos/chronos.go new file mode 100644 index 0000000..18bf775 --- /dev/null +++ b/chronos/chronos.go @@ -0,0 +1,163 @@ +package chronos + +import ( + "math" + "time" +) + +type TimeUnit int + +const ( + Minute TimeUnit = iota + Hour + Day + Week + Month + Year +) + +var NilTime time.Time + +func (tu TimeUnit) String() string { + switch tu { + case Minute: + return "Minute" + case Hour: + return "Hour" + case Day: + return "Day" + case Week: + return "Week" + case Month: + return "Month" + case Year: + return "Year" + default: + return "(Unknown TimeUnit)" + } +} + +func (tu TimeUnit) minApprox() time.Duration { + const ( + maMinute = time.Minute + maHour = time.Hour + maDay = 24*time.Hour - time.Second + maWeek = 7 * maDay + maMonth = 28 * maDay + maYear = 365 * maDay + ) + + switch tu { + case Minute: + return maMinute + case Hour: + return maHour + case Day: + return maDay + case Week: + return maWeek + case Month: + return maMonth + case Year: + return maYear + default: + return 0 + } +} + +func (tu TimeUnit) maxApprox() time.Duration { + const ( + maMinute = time.Minute + maHour = time.Hour + maDay = 24*time.Hour + time.Second + maWeek = 7 * maDay + maMonth = 31 * maDay + maYear = 366 * maDay + ) + + switch tu { + case Minute: + return maMinute + case Hour: + return maHour + case Day: + return maDay + case Week: + return maWeek + case Month: + return maMonth + case Year: + return maYear + default: + return 0 + } +} + +type Frequency struct { + Unit TimeUnit + Count uint +} + +func (f Frequency) addTo(t time.Time, mul uint) time.Time { + sec := t.Second() + min := t.Minute() + hour := t.Hour() + day := t.Day() + month := t.Month() + year := t.Year() + loc := t.Location() + + fq := int(f.Count * mul) + + switch f.Unit { + case Minute: + return t.Add(time.Minute * time.Duration(fq)) + case Hour: + return t.Add(time.Hour * time.Duration(fq)) + case Day: + return time.Date(year, month, day+fq, hour, min, sec, 0, loc) + case Week: + return time.Date(year, month, day+fq*7, hour, min, sec, 0, loc) + case Month: + return time.Date(year, month+time.Month(fq), day, hour, min, sec, 0, loc) + case Year: + return time.Date(year+fq, month, day, hour, min, sec, 0, loc) + default: + return NilTime + } +} + +func (f Frequency) minApprox() time.Duration { return time.Duration(f.Count) * f.Unit.minApprox() } +func (f Frequency) maxApprox() time.Duration { return time.Duration(f.Count) * f.Unit.maxApprox() } + +type Chronos struct { + Start, End time.Time + Freq Frequency +} + +func (c Chronos) NextAfter(t time.Time) time.Time { + if !t.After(c.Start) { + return c.Start + } + if c.Freq.Count == 0 { + return NilTime + } + + d := t.Sub(c.Start) + + fmin := uint(math.Floor(float64(d) / float64(c.Freq.maxApprox()))) + fmax := uint(math.Ceil(float64(d) / float64(c.Freq.minApprox()))) + + for f := fmin; f <= fmax; f++ { + t2 := c.Freq.addTo(c.Start, f) + if t2.Before(c.Start) || t2.Before(t) { + continue + } + if (!c.End.IsZero()) && t2.After(c.End) { + return NilTime + } + return t2 + } + + return NilTime // Should actually never happen... +} diff --git a/chronos/chronos_test.go b/chronos/chronos_test.go new file mode 100644 index 0000000..5efb7e9 --- /dev/null +++ b/chronos/chronos_test.go @@ -0,0 +1,37 @@ +package chronos + +import ( + "testing" + "time" +) + +func mktime(y int, month time.Month, d, h, min int) time.Time { + return time.Date(y, month, d, h, min, 0, 0, time.Local) +} + +func TestChronos(t *testing.T) { + tbl := []struct { + start time.Time + end time.Time + unit TimeUnit + count uint + now time.Time + want time.Time + }{ + {mktime(1991, 4, 30, 0, 0), NilTime, Year, 1, mktime(2013, 8, 26, 13, 37), mktime(2014, 4, 30, 0, 0)}, + {mktime(2013, 1, 1, 0, 0), NilTime, Year, 0, mktime(2013, 8, 26, 13, 37), NilTime}, + {mktime(2013, 1, 1, 0, 0), NilTime, Year, 0, mktime(2012, 1, 1, 0, 0), mktime(2013, 1, 1, 0, 0)}, + {mktime(1900, 12, 24, 12, 34), NilTime, Year, 5, mktime(2013, 8, 26, 13, 37), mktime(2015, 12, 24, 12, 34)}, + {mktime(1900, 12, 24, 12, 34), mktime(2010, 1, 1, 1, 1), Year, 5, mktime(2013, 8, 26, 13, 37), NilTime}, + {mktime(2013, 8, 1, 4, 2), NilTime, Week, 3, mktime(2013, 8, 26, 13, 37), mktime(2013, 9, 12, 4, 2)}, + {mktime(2013, 8, 26, 13, 37), NilTime, Year, 0, mktime(2013, 8, 26, 13, 37), mktime(2013, 8, 26, 13, 37)}, + {mktime(2013, 8, 25, 13, 37), NilTime, Day, 1, mktime(2013, 8, 26, 13, 37), mktime(2013, 8, 26, 13, 37)}, + } + + for i, e := range tbl { + have := Chronos{e.start, e.end, Frequency{e.unit, e.count}}.NextAfter(e.now) + if !have.Equal(e.want) { + t.Errorf("#%d: Want: %s, Have: %s", i, e.want, have) + } + } +} |