diff --git a/date.go b/date.go index 097ec58..21db081 100644 --- a/date.go +++ b/date.go @@ -345,14 +345,22 @@ func (d date) CalculateMarketSecondsBetween(start, end, marketOpen, marketClose } const ( - _secondsPerDay = 60 * 60 * 24 + _secondsPerHour = 60 * 60 + _secondsPerDay = 60 * 60 * 24 ) -func (d date) Diff(t1, t2 time.Time) (days int64) { +func (d date) DiffDays(t1, t2 time.Time) (days int) { t1n := t1.Unix() t2n := t2.Unix() - diff := t1n - t2n - return diff / (_secondsPerDay) + diff := t2n - t1n //yields seconds + return int(diff / (_secondsPerDay)) +} + +func (d date) DiffHours(t1, t2 time.Time) (hours int) { + t1n := t1.Unix() + t2n := t2.Unix() + diff := t2n - t1n //yields seconds + return int(diff / (_secondsPerHour)) } // NextDay returns the timestamp advanced a day. @@ -386,3 +394,33 @@ func (d date) NextDayOfWeek(after time.Time, dayOfWeek time.Weekday) time.Time { dayDelta := 7 - int(afterWeekday-dayOfWeek) return after.AddDate(0, 0, dayDelta) } + +// Start returns the earliest (min) time in a list of times. +func (d date) Start(times []time.Time) time.Time { + if len(times) == 0 { + return time.Time{} + } + + start := times[0] + for _, t := range times[1:] { + if t.Before(start) { + start = t + } + } + return start +} + +// Start returns the earliest (min) time in a list of times. +func (d date) End(times []time.Time) time.Time { + if len(times) == 0 { + return time.Time{} + } + + end := times[0] + for _, t := range times[1:] { + if t.After(end) { + end = t + } + } + return end +} diff --git a/date_test.go b/date_test.go index 63164bb..cb1602c 100644 --- a/date_test.go +++ b/date_test.go @@ -236,3 +236,53 @@ func TestDateIsNYSEHoliday(t *testing.T) { } assert.Equal(holidays, 55) } + +func TestTimeStart(t *testing.T) { + assert := assert.New(t) + + times := []time.Time{ + time.Now().AddDate(0, 0, -4), + time.Now().AddDate(0, 0, -2), + time.Now().AddDate(0, 0, -1), + time.Now().AddDate(0, 0, -3), + time.Now().AddDate(0, 0, -5), + } + + assert.InTimeDelta(Date.Start(times), times[4], time.Millisecond) +} + +func TestTimeEnd(t *testing.T) { + assert := assert.New(t) + + times := []time.Time{ + time.Now().AddDate(0, 0, -4), + time.Now().AddDate(0, 0, -2), + time.Now().AddDate(0, 0, -1), + time.Now().AddDate(0, 0, -3), + time.Now().AddDate(0, 0, -5), + } + + assert.InTimeDelta(Date.End(times), times[2], time.Millisecond) +} + +func TestDateDiffDays(t *testing.T) { + assert := assert.New(t) + + t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC) + t2 := time.Date(2017, 01, 10, 3, 0, 0, 0, time.UTC) + t3 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC) + + assert.Equal(48, Date.DiffDays(t2, t1)) + assert.Equal(2, Date.DiffDays(t3, t1)) // technically we should round down. +} + +func TestDateDiffHours(t *testing.T) { + assert := assert.New(t) + + t1 := time.Date(2017, 02, 27, 12, 0, 0, 0, time.UTC) + t2 := time.Date(2017, 02, 24, 16, 0, 0, 0, time.UTC) + t3 := time.Date(2017, 02, 28, 12, 0, 0, 0, time.UTC) + + assert.Equal(68, Date.DiffHours(t2, t1)) + assert.Equal(24, Date.DiffHours(t1, t3)) +} diff --git a/math_util.go b/math_util.go index 4fc1a84..43ff428 100644 --- a/math_util.go +++ b/math_util.go @@ -148,6 +148,14 @@ func (m mathUtil) AbsInt(value int) int { return value } +// AbsInt64 returns the absolute value of a long. +func (m mathUtil) AbsInt64(value int64) int64 { + if value < 0 { + return -value + } + return value +} + // Mean returns the mean of a set of values func (m mathUtil) Mean(values ...float64) float64 { return m.Sum(values...) / float64(len(values)) diff --git a/sequence.go b/sequence.go index 6801d64..cf8f55a 100644 --- a/sequence.go +++ b/sequence.go @@ -155,3 +155,34 @@ func (s sequence) MarketDayMondayCloses(from, to time.Time, marketOpen, marketCl } return times } + +func (s sequence) Hours(start time.Time, totalHours int) []time.Time { + times := make([]time.Time, totalHours) + + last := start + for i := 0; i < totalHours; i++ { + times[i] = last + last = last.Add(time.Hour) + } + + return times +} + +// HoursFill adds zero values for the data bounded by the start and end of the xdata array. +func (s sequence) HoursFill(xdata []time.Time, ydata []float64) ([]time.Time, []float64) { + start := Date.Start(xdata) + end := Date.End(xdata) + + totalHours := Math.AbsInt(Date.DiffHours(start, end)) + + finalTimes := s.Hours(start, totalHours+1) + finalValues := make([]float64, totalHours+1) + + var hoursFromStart int + for i, xd := range xdata { + hoursFromStart = Date.DiffHours(start, xd) + finalValues[hoursFromStart] = ydata[i] + } + + return finalTimes, finalValues +} diff --git a/sequence_test.go b/sequence_test.go index 32f733e..32109e0 100644 --- a/sequence_test.go +++ b/sequence_test.go @@ -43,3 +43,50 @@ func TestSequenceMarketQuarters(t *testing.T) { assert.Equal(00, mh[2].Minute()) assert.Equal(Date.Eastern(), mh[2].Location()) } + +func TestSequenceHours(t *testing.T) { + assert := assert.New(t) + + today := time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC) + seq := Sequence.Hours(today, 24) + + end := Date.End(seq) + assert.Len(seq, 24) + assert.Equal(2016, end.Year()) + assert.Equal(07, int(end.Month())) + assert.Equal(02, end.Day()) + assert.Equal(11, end.Hour()) +} + +func TestSequenceHoursFill(t *testing.T) { + assert := assert.New(t) + + xdata := []time.Time{ + time.Date(2016, 07, 01, 12, 0, 0, 0, time.UTC), + time.Date(2016, 07, 01, 13, 0, 0, 0, time.UTC), + time.Date(2016, 07, 01, 14, 0, 0, 0, time.UTC), + time.Date(2016, 07, 02, 4, 0, 0, 0, time.UTC), + time.Date(2016, 07, 02, 5, 0, 0, 0, time.UTC), + time.Date(2016, 07, 03, 12, 0, 0, 0, time.UTC), + time.Date(2016, 07, 03, 14, 0, 0, 0, time.UTC), + } + + ydata := []float64{ + 1.1, + 1.2, + 1.4, + 0.8, + 2.1, + 0.4, + 0.6, + } + + filledTimes, filledValues := Sequence.HoursFill(xdata, ydata) + assert.Len(filledTimes, Date.DiffHours(Date.Start(xdata), Date.End(xdata))+1) + assert.Equal(len(filledValues), len(filledTimes)) + + assert.NotZero(filledValues[0]) + assert.NotZero(filledValues[len(filledValues)-1]) + + assert.NotZero(filledValues[16]) +} diff --git a/time_util.go b/time_util.go index 8937546..4e81743 100644 --- a/time_util.go +++ b/time_util.go @@ -17,4 +17,4 @@ func (tu timeUtil) ToFloat64(t time.Time) float64 { // Float64ToTime returns a time from a float64. func (tu timeUtil) FromFloat64(tf float64) time.Time { return time.Unix(0, int64(tf)) -} +} \ No newline at end of file