Helper API refactor (#40)
* api cleaup * updates * wtf * updates * snapshot. * tweaks * snapshot * api tweaks. * updates * updates * updates * changes. * updates * updates * sequence => seq * dont need to use curl, just using wget * fixing examples
This commit is contained in:
parent
43212f871f
commit
03708a90ef
100 changed files with 1687 additions and 1055 deletions
396
util/date.go
Normal file
396
util/date.go
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// AllDaysMask is a bitmask of all the days of the week.
|
||||
AllDaysMask = 1<<uint(time.Sunday) | 1<<uint(time.Monday) | 1<<uint(time.Tuesday) | 1<<uint(time.Wednesday) | 1<<uint(time.Thursday) | 1<<uint(time.Friday) | 1<<uint(time.Saturday)
|
||||
// WeekDaysMask is a bitmask of all the weekdays of the week.
|
||||
WeekDaysMask = 1<<uint(time.Monday) | 1<<uint(time.Tuesday) | 1<<uint(time.Wednesday) | 1<<uint(time.Thursday) | 1<<uint(time.Friday)
|
||||
//WeekendDaysMask is a bitmask of the weekend days of the week.
|
||||
WeekendDaysMask = 1<<uint(time.Sunday) | 1<<uint(time.Saturday)
|
||||
)
|
||||
|
||||
var (
|
||||
// DaysOfWeek are all the time.Weekday in an array for utility purposes.
|
||||
DaysOfWeek = []time.Weekday{
|
||||
time.Sunday,
|
||||
time.Monday,
|
||||
time.Tuesday,
|
||||
time.Wednesday,
|
||||
time.Thursday,
|
||||
time.Friday,
|
||||
time.Saturday,
|
||||
}
|
||||
|
||||
// WeekDays are the business time.Weekday in an array.
|
||||
WeekDays = []time.Weekday{
|
||||
time.Monday,
|
||||
time.Tuesday,
|
||||
time.Wednesday,
|
||||
time.Thursday,
|
||||
time.Friday,
|
||||
}
|
||||
|
||||
// WeekendDays are the weekend time.Weekday in an array.
|
||||
WeekendDays = []time.Weekday{
|
||||
time.Sunday,
|
||||
time.Saturday,
|
||||
}
|
||||
|
||||
//Epoch is unix epoc saved for utility purposes.
|
||||
Epoch = time.Unix(0, 0)
|
||||
)
|
||||
|
||||
var (
|
||||
_easternLock sync.Mutex
|
||||
_eastern *time.Location
|
||||
)
|
||||
|
||||
// NYSEOpen is when the NYSE opens.
|
||||
func NYSEOpen() time.Time { return Date.Time(9, 30, 0, 0, Date.Eastern()) }
|
||||
|
||||
// NYSEClose is when the NYSE closes.
|
||||
func NYSEClose() time.Time { return Date.Time(16, 0, 0, 0, Date.Eastern()) }
|
||||
|
||||
// NASDAQOpen is when NASDAQ opens.
|
||||
func NASDAQOpen() time.Time { return Date.Time(9, 30, 0, 0, Date.Eastern()) }
|
||||
|
||||
// NASDAQClose is when NASDAQ closes.
|
||||
func NASDAQClose() time.Time { return Date.Time(16, 0, 0, 0, Date.Eastern()) }
|
||||
|
||||
// NYSEArcaOpen is when NYSEARCA opens.
|
||||
func NYSEArcaOpen() time.Time { return Date.Time(4, 0, 0, 0, Date.Eastern()) }
|
||||
|
||||
// NYSEArcaClose is when NYSEARCA closes.
|
||||
func NYSEArcaClose() time.Time { return Date.Time(20, 0, 0, 0, Date.Eastern()) }
|
||||
|
||||
// HolidayProvider is a function that returns if a given time falls on a holiday.
|
||||
type HolidayProvider func(time.Time) bool
|
||||
|
||||
// defaultHolidayProvider implements `HolidayProvider` and just returns false.
|
||||
func defaultHolidayProvider(_ time.Time) bool { return false }
|
||||
|
||||
var (
|
||||
// Date contains utility functions that operate on dates.
|
||||
Date = &date{}
|
||||
)
|
||||
|
||||
type date struct{}
|
||||
|
||||
// IsNYSEHoliday returns if a date was/is on a nyse holiday day.
|
||||
func (d date) IsNYSEHoliday(t time.Time) bool {
|
||||
te := t.In(d.Eastern())
|
||||
if te.Year() == 2013 {
|
||||
if te.Month() == 1 {
|
||||
return te.Day() == 1 || te.Day() == 21
|
||||
} else if te.Month() == 2 {
|
||||
return te.Day() == 18
|
||||
} else if te.Month() == 3 {
|
||||
return te.Day() == 29
|
||||
} else if te.Month() == 5 {
|
||||
return te.Day() == 27
|
||||
} else if te.Month() == 7 {
|
||||
return te.Day() == 4
|
||||
} else if te.Month() == 9 {
|
||||
return te.Day() == 2
|
||||
} else if te.Month() == 11 {
|
||||
return te.Day() == 28
|
||||
} else if te.Month() == 12 {
|
||||
return te.Day() == 25
|
||||
}
|
||||
} else if te.Year() == 2014 {
|
||||
if te.Month() == 1 {
|
||||
return te.Day() == 1 || te.Day() == 20
|
||||
} else if te.Month() == 2 {
|
||||
return te.Day() == 17
|
||||
} else if te.Month() == 4 {
|
||||
return te.Day() == 18
|
||||
} else if te.Month() == 5 {
|
||||
return te.Day() == 26
|
||||
} else if te.Month() == 7 {
|
||||
return te.Day() == 4
|
||||
} else if te.Month() == 9 {
|
||||
return te.Day() == 1
|
||||
} else if te.Month() == 11 {
|
||||
return te.Day() == 27
|
||||
} else if te.Month() == 12 {
|
||||
return te.Day() == 25
|
||||
}
|
||||
} else if te.Year() == 2015 {
|
||||
if te.Month() == 1 {
|
||||
return te.Day() == 1 || te.Day() == 19
|
||||
} else if te.Month() == 2 {
|
||||
return te.Day() == 16
|
||||
} else if te.Month() == 4 {
|
||||
return te.Day() == 3
|
||||
} else if te.Month() == 5 {
|
||||
return te.Day() == 25
|
||||
} else if te.Month() == 7 {
|
||||
return te.Day() == 3
|
||||
} else if te.Month() == 9 {
|
||||
return te.Day() == 7
|
||||
} else if te.Month() == 11 {
|
||||
return te.Day() == 26
|
||||
} else if te.Month() == 12 {
|
||||
return te.Day() == 25
|
||||
}
|
||||
} else if te.Year() == 2016 {
|
||||
if te.Month() == 1 {
|
||||
return te.Day() == 1 || te.Day() == 18
|
||||
} else if te.Month() == 2 {
|
||||
return te.Day() == 15
|
||||
} else if te.Month() == 3 {
|
||||
return te.Day() == 25
|
||||
} else if te.Month() == 5 {
|
||||
return te.Day() == 30
|
||||
} else if te.Month() == 7 {
|
||||
return te.Day() == 4
|
||||
} else if te.Month() == 9 {
|
||||
return te.Day() == 5
|
||||
} else if te.Month() == 11 {
|
||||
return te.Day() == 24 || te.Day() == 25
|
||||
} else if te.Month() == 12 {
|
||||
return te.Day() == 26
|
||||
}
|
||||
} else if te.Year() == 2017 {
|
||||
if te.Month() == 1 {
|
||||
return te.Day() == 1 || te.Day() == 16
|
||||
} else if te.Month() == 2 {
|
||||
return te.Day() == 20
|
||||
} else if te.Month() == 4 {
|
||||
return te.Day() == 15
|
||||
} else if te.Month() == 5 {
|
||||
return te.Day() == 29
|
||||
} else if te.Month() == 7 {
|
||||
return te.Day() == 4
|
||||
} else if te.Month() == 9 {
|
||||
return te.Day() == 4
|
||||
} else if te.Month() == 11 {
|
||||
return te.Day() == 23
|
||||
} else if te.Month() == 12 {
|
||||
return te.Day() == 25
|
||||
}
|
||||
} else if te.Year() == 2018 {
|
||||
if te.Month() == 1 {
|
||||
return te.Day() == 1 || te.Day() == 15
|
||||
} else if te.Month() == 2 {
|
||||
return te.Day() == 19
|
||||
} else if te.Month() == 3 {
|
||||
return te.Day() == 30
|
||||
} else if te.Month() == 5 {
|
||||
return te.Day() == 28
|
||||
} else if te.Month() == 7 {
|
||||
return te.Day() == 4
|
||||
} else if te.Month() == 9 {
|
||||
return te.Day() == 3
|
||||
} else if te.Month() == 11 {
|
||||
return te.Day() == 22
|
||||
} else if te.Month() == 12 {
|
||||
return te.Day() == 25
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNYSEArcaHoliday returns that returns if a given time falls on a holiday.
|
||||
func (d date) IsNYSEArcaHoliday(t time.Time) bool {
|
||||
return d.IsNYSEHoliday(t)
|
||||
}
|
||||
|
||||
// IsNASDAQHoliday returns if a date was a NASDAQ holiday day.
|
||||
func (d date) IsNASDAQHoliday(t time.Time) bool {
|
||||
return d.IsNYSEHoliday(t)
|
||||
}
|
||||
|
||||
// Time returns a new time.Time for the given clock components.
|
||||
func (d date) Time(hour, min, sec, nsec int, loc *time.Location) time.Time {
|
||||
return time.Date(0, 0, 0, hour, min, sec, nsec, loc)
|
||||
}
|
||||
|
||||
func (d date) Date(year, month, day int, loc *time.Location) time.Time {
|
||||
return time.Date(year, time.Month(month), day, 12, 0, 0, 0, loc)
|
||||
}
|
||||
|
||||
// On returns the clock components of clock (hour,minute,second) on the date components of d.
|
||||
func (d date) On(clock, cd time.Time) time.Time {
|
||||
tzAdjusted := cd.In(clock.Location())
|
||||
return time.Date(tzAdjusted.Year(), tzAdjusted.Month(), tzAdjusted.Day(), clock.Hour(), clock.Minute(), clock.Second(), clock.Nanosecond(), clock.Location())
|
||||
}
|
||||
|
||||
// NoonOn is a shortcut for On(Time(12,0,0), cd) a.k.a. noon on a given date.
|
||||
func (d date) NoonOn(cd time.Time) time.Time {
|
||||
return time.Date(cd.Year(), cd.Month(), cd.Day(), 12, 0, 0, 0, cd.Location())
|
||||
}
|
||||
|
||||
// Optional returns a pointer reference to a given time.
|
||||
func (d date) Optional(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
// IsWeekDay returns if the day is a monday->friday.
|
||||
func (d date) IsWeekDay(day time.Weekday) bool {
|
||||
return !d.IsWeekendDay(day)
|
||||
}
|
||||
|
||||
// IsWeekendDay returns if the day is a monday->friday.
|
||||
func (d date) IsWeekendDay(day time.Weekday) bool {
|
||||
return day == time.Saturday || day == time.Sunday
|
||||
}
|
||||
|
||||
// Before returns if a timestamp is strictly before another date (ignoring hours, minutes etc.)
|
||||
func (d date) Before(before, reference time.Time) bool {
|
||||
tzAdjustedBefore := before.In(reference.Location())
|
||||
if tzAdjustedBefore.Year() < reference.Year() {
|
||||
return true
|
||||
}
|
||||
if tzAdjustedBefore.Month() < reference.Month() {
|
||||
return true
|
||||
}
|
||||
return tzAdjustedBefore.Year() == reference.Year() && tzAdjustedBefore.Month() == reference.Month() && tzAdjustedBefore.Day() < reference.Day()
|
||||
}
|
||||
|
||||
// NextMarketOpen returns the next market open after a given time.
|
||||
func (d date) NextMarketOpen(after, openTime time.Time, isHoliday HolidayProvider) time.Time {
|
||||
afterLocalized := after.In(openTime.Location())
|
||||
todaysOpen := d.On(openTime, afterLocalized)
|
||||
|
||||
if isHoliday == nil {
|
||||
isHoliday = defaultHolidayProvider
|
||||
}
|
||||
|
||||
todayIsValidTradingDay := d.IsWeekDay(todaysOpen.Weekday()) && !isHoliday(todaysOpen)
|
||||
|
||||
if (afterLocalized.Equal(todaysOpen) || afterLocalized.Before(todaysOpen)) && todayIsValidTradingDay {
|
||||
return todaysOpen
|
||||
}
|
||||
|
||||
for cursorDay := 1; cursorDay < 7; cursorDay++ {
|
||||
newDay := todaysOpen.AddDate(0, 0, cursorDay)
|
||||
isValidTradingDay := d.IsWeekDay(newDay.Weekday()) && !isHoliday(newDay)
|
||||
if isValidTradingDay {
|
||||
return d.On(openTime, newDay)
|
||||
}
|
||||
}
|
||||
panic("Have exhausted day window looking for next market open.")
|
||||
}
|
||||
|
||||
// NextMarketClose returns the next market close after a given time.
|
||||
func (d date) NextMarketClose(after, closeTime time.Time, isHoliday HolidayProvider) time.Time {
|
||||
afterLocalized := after.In(closeTime.Location())
|
||||
|
||||
if isHoliday == nil {
|
||||
isHoliday = defaultHolidayProvider
|
||||
}
|
||||
|
||||
todaysClose := d.On(closeTime, afterLocalized)
|
||||
if afterLocalized.Before(todaysClose) && d.IsWeekDay(todaysClose.Weekday()) && !isHoliday(todaysClose) {
|
||||
return todaysClose
|
||||
}
|
||||
|
||||
if afterLocalized.Equal(todaysClose) { //rare but it might happen.
|
||||
return todaysClose
|
||||
}
|
||||
|
||||
for cursorDay := 1; cursorDay < 6; cursorDay++ {
|
||||
newDay := todaysClose.AddDate(0, 0, cursorDay)
|
||||
if d.IsWeekDay(newDay.Weekday()) && !isHoliday(newDay) {
|
||||
return d.On(closeTime, newDay)
|
||||
}
|
||||
}
|
||||
panic("Have exhausted day window looking for next market close.")
|
||||
}
|
||||
|
||||
// CalculateMarketSecondsBetween calculates the number of seconds the market was open between two dates.
|
||||
func (d date) CalculateMarketSecondsBetween(start, end, marketOpen, marketClose time.Time, isHoliday HolidayProvider) (seconds int64) {
|
||||
startEastern := start.In(d.Eastern())
|
||||
endEastern := end.In(d.Eastern())
|
||||
|
||||
startMarketOpen := d.On(marketOpen, startEastern)
|
||||
startMarketClose := d.On(marketClose, startEastern)
|
||||
|
||||
if !d.IsWeekendDay(startMarketOpen.Weekday()) && !isHoliday(startMarketOpen) {
|
||||
if (startEastern.Equal(startMarketOpen) || startEastern.After(startMarketOpen)) && startEastern.Before(startMarketClose) {
|
||||
if endEastern.Before(startMarketClose) {
|
||||
seconds += int64(endEastern.Sub(startEastern) / time.Second)
|
||||
} else {
|
||||
seconds += int64(startMarketClose.Sub(startEastern) / time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor := d.NextMarketOpen(startMarketClose, marketOpen, isHoliday)
|
||||
for d.Before(cursor, endEastern) {
|
||||
if d.IsWeekDay(cursor.Weekday()) && !isHoliday(cursor) {
|
||||
close := d.NextMarketClose(cursor, marketClose, isHoliday)
|
||||
seconds += int64(close.Sub(cursor) / time.Second)
|
||||
}
|
||||
cursor = cursor.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
finalMarketOpen := d.NextMarketOpen(cursor, marketOpen, isHoliday)
|
||||
finalMarketClose := d.NextMarketClose(cursor, marketClose, isHoliday)
|
||||
if endEastern.After(finalMarketOpen) {
|
||||
if endEastern.Before(finalMarketClose) {
|
||||
seconds += int64(endEastern.Sub(finalMarketOpen) / time.Second)
|
||||
} else {
|
||||
seconds += int64(finalMarketClose.Sub(finalMarketOpen) / time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
_secondsPerHour = 60 * 60
|
||||
_secondsPerDay = 60 * 60 * 24
|
||||
)
|
||||
|
||||
func (d date) DiffDays(t1, t2 time.Time) (days int) {
|
||||
t1n := t1.Unix()
|
||||
t2n := t2.Unix()
|
||||
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.
|
||||
func (d date) NextDay(ts time.Time) time.Time {
|
||||
return ts.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
// NextHour returns the next timestamp on the hour.
|
||||
func (d date) NextHour(ts time.Time) time.Time {
|
||||
//advance a full hour ...
|
||||
advanced := ts.Add(time.Hour)
|
||||
minutes := time.Duration(advanced.Minute()) * time.Minute
|
||||
final := advanced.Add(-minutes)
|
||||
return time.Date(final.Year(), final.Month(), final.Day(), final.Hour(), 0, 0, 0, final.Location())
|
||||
}
|
||||
|
||||
// NextDayOfWeek returns the next instance of a given weekday after a given timestamp.
|
||||
func (d date) NextDayOfWeek(after time.Time, dayOfWeek time.Weekday) time.Time {
|
||||
afterWeekday := after.Weekday()
|
||||
if afterWeekday == dayOfWeek {
|
||||
return after.AddDate(0, 0, 7)
|
||||
}
|
||||
|
||||
// 1 vs 5 ~ add 4 days
|
||||
if afterWeekday < dayOfWeek {
|
||||
dayDelta := int(dayOfWeek - afterWeekday)
|
||||
return after.AddDate(0, 0, dayDelta)
|
||||
}
|
||||
|
||||
// 5 vs 1, add 7-(5-1) ~ 3 days
|
||||
dayDelta := 7 - int(afterWeekday-dayOfWeek)
|
||||
return after.AddDate(0, 0, dayDelta)
|
||||
}
|
||||
17
util/date_posix.go
Normal file
17
util/date_posix.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// +build !windows
|
||||
|
||||
package util
|
||||
|
||||
import "time"
|
||||
|
||||
// Eastern returns the eastern timezone.
|
||||
func (d date) Eastern() *time.Location {
|
||||
if _eastern == nil {
|
||||
_easternLock.Lock()
|
||||
defer _easternLock.Unlock()
|
||||
if _eastern == nil {
|
||||
_eastern, _ = time.LoadLocation("America/New_York")
|
||||
}
|
||||
}
|
||||
return _eastern
|
||||
}
|
||||
260
util/date_test.go
Normal file
260
util/date_test.go
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
assert "github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func parse(v string) time.Time {
|
||||
ts, _ := time.Parse("2006-01-02", v)
|
||||
return ts
|
||||
}
|
||||
|
||||
func TestDateTime(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ts := Date.Time(5, 6, 7, 8, time.UTC)
|
||||
assert.Equal(05, ts.Hour())
|
||||
assert.Equal(06, ts.Minute())
|
||||
assert.Equal(07, ts.Second())
|
||||
assert.Equal(8, ts.Nanosecond())
|
||||
assert.Equal(time.UTC, ts.Location())
|
||||
}
|
||||
|
||||
func TestDateDate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ts := Date.Date(2015, 5, 6, time.UTC)
|
||||
assert.Equal(2015, ts.Year())
|
||||
assert.Equal(5, ts.Month())
|
||||
assert.Equal(6, ts.Day())
|
||||
assert.Equal(time.UTC, ts.Location())
|
||||
}
|
||||
|
||||
func TestDateOn(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ts := Date.On(Date.Time(5, 4, 3, 2, time.UTC), Date.Date(2016, 6, 7, Date.Eastern()))
|
||||
assert.Equal(2016, ts.Year())
|
||||
assert.Equal(6, ts.Month())
|
||||
assert.Equal(7, ts.Day())
|
||||
assert.Equal(5, ts.Hour())
|
||||
assert.Equal(4, ts.Minute())
|
||||
assert.Equal(3, ts.Second())
|
||||
assert.Equal(2, ts.Nanosecond())
|
||||
assert.Equal(time.UTC, ts.Location())
|
||||
}
|
||||
|
||||
func TestDateNoonOn(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
noon := Date.NoonOn(time.Date(2016, 04, 03, 02, 01, 0, 0, time.UTC))
|
||||
|
||||
assert.Equal(2016, noon.Year())
|
||||
assert.Equal(4, noon.Month())
|
||||
assert.Equal(3, noon.Day())
|
||||
assert.Equal(12, noon.Hour())
|
||||
assert.Equal(0, noon.Minute())
|
||||
assert.Equal(time.UTC, noon.Location())
|
||||
}
|
||||
|
||||
func TestDateBefore(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.True(Date.Before(parse("2015-07-02"), parse("2016-07-01")))
|
||||
assert.True(Date.Before(parse("2016-06-01"), parse("2016-07-01")))
|
||||
assert.True(Date.Before(parse("2016-07-01"), parse("2016-07-02")))
|
||||
|
||||
assert.False(Date.Before(parse("2016-07-01"), parse("2016-07-01")))
|
||||
assert.False(Date.Before(parse("2016-07-03"), parse("2016-07-01")))
|
||||
assert.False(Date.Before(parse("2016-08-03"), parse("2016-07-01")))
|
||||
assert.False(Date.Before(parse("2017-08-03"), parse("2016-07-01")))
|
||||
}
|
||||
|
||||
func TestDateBeforeHandlesTimezones(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
tuesdayUTC := time.Date(2016, 8, 02, 22, 00, 0, 0, time.UTC)
|
||||
mondayUTC := time.Date(2016, 8, 01, 1, 00, 0, 0, time.UTC)
|
||||
sundayEST := time.Date(2016, 7, 31, 22, 00, 0, 0, Date.Eastern())
|
||||
|
||||
assert.True(Date.Before(sundayEST, tuesdayUTC))
|
||||
assert.False(Date.Before(sundayEST, mondayUTC))
|
||||
}
|
||||
|
||||
func TestNextMarketOpen(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
beforeOpen := time.Date(2016, 07, 18, 9, 0, 0, 0, Date.Eastern())
|
||||
todayOpen := time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern())
|
||||
|
||||
afterOpen := time.Date(2016, 07, 18, 9, 31, 0, 0, Date.Eastern())
|
||||
tomorrowOpen := time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern())
|
||||
|
||||
afterFriday := time.Date(2016, 07, 22, 9, 31, 0, 0, Date.Eastern())
|
||||
mondayOpen := time.Date(2016, 07, 25, 9, 30, 0, 0, Date.Eastern())
|
||||
|
||||
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Date.Eastern())
|
||||
|
||||
assert.True(todayOpen.Equal(Date.NextMarketOpen(beforeOpen, NYSEOpen(), Date.IsNYSEHoliday)))
|
||||
assert.True(tomorrowOpen.Equal(Date.NextMarketOpen(afterOpen, NYSEOpen(), Date.IsNYSEHoliday)))
|
||||
assert.True(mondayOpen.Equal(Date.NextMarketOpen(afterFriday, NYSEOpen(), Date.IsNYSEHoliday)))
|
||||
assert.True(mondayOpen.Equal(Date.NextMarketOpen(weekend, NYSEOpen(), Date.IsNYSEHoliday)))
|
||||
|
||||
assert.Equal(Date.Eastern(), todayOpen.Location())
|
||||
assert.Equal(Date.Eastern(), tomorrowOpen.Location())
|
||||
assert.Equal(Date.Eastern(), mondayOpen.Location())
|
||||
|
||||
testRegression := time.Date(2016, 07, 18, 16, 0, 0, 0, Date.Eastern())
|
||||
shouldbe := time.Date(2016, 07, 19, 9, 30, 0, 0, Date.Eastern())
|
||||
|
||||
assert.True(shouldbe.Equal(Date.NextMarketOpen(testRegression, NYSEOpen(), Date.IsNYSEHoliday)))
|
||||
}
|
||||
|
||||
func TestNextMarketClose(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
beforeClose := time.Date(2016, 07, 18, 15, 0, 0, 0, Date.Eastern())
|
||||
todayClose := time.Date(2016, 07, 18, 16, 00, 0, 0, Date.Eastern())
|
||||
|
||||
afterClose := time.Date(2016, 07, 18, 16, 1, 0, 0, Date.Eastern())
|
||||
tomorrowClose := time.Date(2016, 07, 19, 16, 00, 0, 0, Date.Eastern())
|
||||
|
||||
afterFriday := time.Date(2016, 07, 22, 16, 1, 0, 0, Date.Eastern())
|
||||
mondayClose := time.Date(2016, 07, 25, 16, 0, 0, 0, Date.Eastern())
|
||||
|
||||
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Date.Eastern())
|
||||
|
||||
assert.True(todayClose.Equal(Date.NextMarketClose(beforeClose, NYSEClose(), Date.IsNYSEHoliday)))
|
||||
assert.True(tomorrowClose.Equal(Date.NextMarketClose(afterClose, NYSEClose(), Date.IsNYSEHoliday)))
|
||||
assert.True(mondayClose.Equal(Date.NextMarketClose(afterFriday, NYSEClose(), Date.IsNYSEHoliday)))
|
||||
assert.True(mondayClose.Equal(Date.NextMarketClose(weekend, NYSEClose(), Date.IsNYSEHoliday)))
|
||||
|
||||
assert.Equal(Date.Eastern(), todayClose.Location())
|
||||
assert.Equal(Date.Eastern(), tomorrowClose.Location())
|
||||
assert.Equal(Date.Eastern(), mondayClose.Location())
|
||||
}
|
||||
|
||||
func TestCalculateMarketSecondsBetween(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
start := time.Date(2016, 07, 18, 9, 30, 0, 0, Date.Eastern())
|
||||
end := time.Date(2016, 07, 22, 16, 00, 0, 0, Date.Eastern())
|
||||
|
||||
shouldbe := 5 * 6.5 * 60 * 60
|
||||
|
||||
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday))
|
||||
}
|
||||
|
||||
func TestCalculateMarketSecondsBetween1D(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
start := time.Date(2016, 07, 22, 9, 45, 0, 0, Date.Eastern())
|
||||
end := time.Date(2016, 07, 22, 15, 45, 0, 0, Date.Eastern())
|
||||
|
||||
shouldbe := 6 * 60 * 60
|
||||
|
||||
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday))
|
||||
}
|
||||
|
||||
func TestCalculateMarketSecondsBetweenLTM(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
||||
end := time.Date(2016, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
||||
|
||||
shouldbe := 253 * 6.5 * 60 * 60 //253 full market days since this date last year.
|
||||
assert.Equal(shouldbe, Date.CalculateMarketSecondsBetween(start, end, NYSEOpen(), NYSEClose(), Date.IsNYSEHoliday))
|
||||
}
|
||||
|
||||
func TestDateNextHour(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Date.Eastern())
|
||||
next := Date.NextHour(start)
|
||||
assert.Equal(2015, next.Year())
|
||||
assert.Equal(07, next.Month())
|
||||
assert.Equal(01, next.Day())
|
||||
assert.Equal(10, next.Hour())
|
||||
assert.Equal(00, next.Minute())
|
||||
|
||||
next = Date.NextHour(next)
|
||||
assert.Equal(11, next.Hour())
|
||||
|
||||
next = Date.NextHour(next)
|
||||
assert.Equal(12, next.Hour())
|
||||
|
||||
}
|
||||
|
||||
func TestDateNextDayOfWeek(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
weds := Date.Date(2016, 8, 10, time.UTC)
|
||||
fri := Date.Date(2016, 8, 12, time.UTC)
|
||||
sun := Date.Date(2016, 8, 14, time.UTC)
|
||||
mon := Date.Date(2016, 8, 15, time.UTC)
|
||||
weds2 := Date.Date(2016, 8, 17, time.UTC)
|
||||
|
||||
nextFri := Date.NextDayOfWeek(weds, time.Friday)
|
||||
nextSunday := Date.NextDayOfWeek(weds, time.Sunday)
|
||||
nextMonday := Date.NextDayOfWeek(weds, time.Monday)
|
||||
nextWeds := Date.NextDayOfWeek(weds, time.Wednesday)
|
||||
|
||||
assert.Equal(fri.Year(), nextFri.Year())
|
||||
assert.Equal(fri.Month(), nextFri.Month())
|
||||
assert.Equal(fri.Day(), nextFri.Day())
|
||||
|
||||
assert.Equal(sun.Year(), nextSunday.Year())
|
||||
assert.Equal(sun.Month(), nextSunday.Month())
|
||||
assert.Equal(sun.Day(), nextSunday.Day())
|
||||
|
||||
assert.Equal(mon.Year(), nextMonday.Year())
|
||||
assert.Equal(mon.Month(), nextMonday.Month())
|
||||
assert.Equal(mon.Day(), nextMonday.Day())
|
||||
|
||||
assert.Equal(weds2.Year(), nextWeds.Year())
|
||||
assert.Equal(weds2.Month(), nextWeds.Month())
|
||||
assert.Equal(weds2.Day(), nextWeds.Day())
|
||||
|
||||
assert.Equal(time.UTC, nextFri.Location())
|
||||
assert.Equal(time.UTC, nextSunday.Location())
|
||||
assert.Equal(time.UTC, nextMonday.Location())
|
||||
}
|
||||
|
||||
func TestDateIsNYSEHoliday(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cursor := time.Date(2013, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
var holidays int
|
||||
for Date.Before(cursor, end) {
|
||||
if Date.IsNYSEHoliday(cursor) {
|
||||
holidays++
|
||||
}
|
||||
cursor = cursor.AddDate(0, 0, 1)
|
||||
}
|
||||
assert.Equal(holidays, 55)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
17
util/date_windows.go
Normal file
17
util/date_windows.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// +build windows
|
||||
|
||||
package util
|
||||
|
||||
import "time"
|
||||
|
||||
// Eastern returns the eastern timezone.
|
||||
func (d date) Eastern() *time.Location {
|
||||
if _eastern == nil {
|
||||
_easternLock.Lock()
|
||||
defer _easternLock.Unlock()
|
||||
if _eastern == nil {
|
||||
_eastern, _ = time.LoadLocation("EST")
|
||||
}
|
||||
}
|
||||
return _eastern
|
||||
}
|
||||
57
util/file_util.go
Normal file
57
util/file_util.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// File contains file utility functions
|
||||
File = fileUtil{}
|
||||
)
|
||||
|
||||
type fileUtil struct{}
|
||||
|
||||
// ReadByLines reads a file and calls the handler for each line.
|
||||
func (fu fileUtil) ReadByLines(filePath string, handler func(line string) error) error {
|
||||
var f *os.File
|
||||
var err error
|
||||
if f, err = os.Open(filePath); err == nil {
|
||||
defer f.Close()
|
||||
var line string
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
err = handler(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// ReadByChunks reads a file in `chunkSize` pieces, dispatched to the handler.
|
||||
func (fu fileUtil) ReadByChunks(filePath string, chunkSize int, handler func(line []byte) error) error {
|
||||
var f *os.File
|
||||
var err error
|
||||
if f, err = os.Open(filePath); err == nil {
|
||||
defer f.Close()
|
||||
|
||||
chunk := make([]byte, chunkSize)
|
||||
for {
|
||||
readBytes, err := f.Read(chunk)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
readData := chunk[:readBytes]
|
||||
err = handler(readData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
253
util/math.go
Normal file
253
util/math.go
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
_pi = math.Pi
|
||||
_2pi = 2 * math.Pi
|
||||
_3pi4 = (3 * math.Pi) / 4.0
|
||||
_4pi3 = (4 * math.Pi) / 3.0
|
||||
_3pi2 = (3 * math.Pi) / 2.0
|
||||
_5pi4 = (5 * math.Pi) / 4.0
|
||||
_7pi4 = (7 * math.Pi) / 4.0
|
||||
_pi2 = math.Pi / 2.0
|
||||
_pi4 = math.Pi / 4.0
|
||||
_d2r = (math.Pi / 180.0)
|
||||
_r2d = (180.0 / math.Pi)
|
||||
)
|
||||
|
||||
var (
|
||||
// Math contains helper methods for common math operations.
|
||||
Math = &mathUtil{}
|
||||
)
|
||||
|
||||
type mathUtil struct{}
|
||||
|
||||
// Max returns the maximum value of a group of floats.
|
||||
func (m mathUtil) Max(values ...float64) float64 {
|
||||
if len(values) == 0 {
|
||||
return 0
|
||||
}
|
||||
max := values[0]
|
||||
for _, v := range values {
|
||||
if max < v {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// MinAndMax returns both the min and max in one pass.
|
||||
func (m mathUtil) MinAndMax(values ...float64) (min float64, max float64) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
min = values[0]
|
||||
max = values[0]
|
||||
for _, v := range values[1:] {
|
||||
if max < v {
|
||||
max = v
|
||||
}
|
||||
if min > v {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MinAndMaxOfTime returns the min and max of a given set of times
|
||||
// in one pass.
|
||||
func (m mathUtil) MinAndMaxOfTime(values ...time.Time) (min time.Time, max time.Time) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
min = values[0]
|
||||
max = values[0]
|
||||
|
||||
for _, v := range values[1:] {
|
||||
if max.Before(v) {
|
||||
max = v
|
||||
}
|
||||
if min.After(v) {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetRoundToForDelta returns a `roundTo` value for a given delta.
|
||||
func (m mathUtil) GetRoundToForDelta(delta float64) float64 {
|
||||
startingDeltaBound := math.Pow(10.0, 10.0)
|
||||
for cursor := startingDeltaBound; cursor > 0; cursor /= 10.0 {
|
||||
if delta > cursor {
|
||||
return cursor / 10.0
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// RoundUp rounds up to a given roundTo value.
|
||||
func (m mathUtil) RoundUp(value, roundTo float64) float64 {
|
||||
d1 := math.Ceil(value / roundTo)
|
||||
return d1 * roundTo
|
||||
}
|
||||
|
||||
// RoundDown rounds down to a given roundTo value.
|
||||
func (m mathUtil) RoundDown(value, roundTo float64) float64 {
|
||||
d1 := math.Floor(value / roundTo)
|
||||
return d1 * roundTo
|
||||
}
|
||||
|
||||
// Normalize returns a set of numbers on the interval [0,1] for a given set of inputs.
|
||||
// An example: 4,3,2,1 => 0.4, 0.3, 0.2, 0.1
|
||||
// Caveat; the total may be < 1.0; there are going to be issues with irrational numbers etc.
|
||||
func (m mathUtil) Normalize(values ...float64) []float64 {
|
||||
var total float64
|
||||
for _, v := range values {
|
||||
total += v
|
||||
}
|
||||
output := make([]float64, len(values))
|
||||
for x, v := range values {
|
||||
output[x] = m.RoundDown(v/total, 0.0001)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// MinInt returns the minimum of a set of integers.
|
||||
func (m mathUtil) MinInt(values ...int) int {
|
||||
min := math.MaxInt32
|
||||
for _, v := range values {
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
// MaxInt returns the maximum of a set of integers.
|
||||
func (m mathUtil) MaxInt(values ...int) int {
|
||||
max := math.MinInt32
|
||||
for _, v := range values {
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// AbsInt returns the absolute value of an integer.
|
||||
func (m mathUtil) AbsInt(value int) int {
|
||||
if value < 0 {
|
||||
return -value
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
// MeanInt returns the mean of a set of integer values.
|
||||
func (m mathUtil) MeanInt(values ...int) int {
|
||||
return m.SumInt(values...) / len(values)
|
||||
}
|
||||
|
||||
// Sum sums a set of values.
|
||||
func (m mathUtil) Sum(values ...float64) float64 {
|
||||
var total float64
|
||||
for _, v := range values {
|
||||
total += v
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// SumInt sums a set of values.
|
||||
func (m mathUtil) SumInt(values ...int) int {
|
||||
var total int
|
||||
for _, v := range values {
|
||||
total += v
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// PercentDifference computes the percentage difference between two values.
|
||||
// The formula is (v2-v1)/v1.
|
||||
func (m mathUtil) PercentDifference(v1, v2 float64) float64 {
|
||||
if v1 == 0 {
|
||||
return 0
|
||||
}
|
||||
return (v2 - v1) / v1
|
||||
}
|
||||
|
||||
// DegreesToRadians returns degrees as radians.
|
||||
func (m mathUtil) DegreesToRadians(degrees float64) float64 {
|
||||
return degrees * _d2r
|
||||
}
|
||||
|
||||
// RadiansToDegrees translates a radian value to a degree value.
|
||||
func (m mathUtil) RadiansToDegrees(value float64) float64 {
|
||||
return math.Mod(value, _2pi) * _r2d
|
||||
}
|
||||
|
||||
// PercentToRadians converts a normalized value (0,1) to radians.
|
||||
func (m mathUtil) PercentToRadians(pct float64) float64 {
|
||||
return m.DegreesToRadians(360.0 * pct)
|
||||
}
|
||||
|
||||
// RadianAdd adds a delta to a base in radians.
|
||||
func (m mathUtil) RadianAdd(base, delta float64) float64 {
|
||||
value := base + delta
|
||||
if value > _2pi {
|
||||
return math.Mod(value, _2pi)
|
||||
} else if value < 0 {
|
||||
return math.Mod(_2pi+value, _2pi)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// DegreesAdd adds a delta to a base in radians.
|
||||
func (m mathUtil) DegreesAdd(baseDegrees, deltaDegrees float64) float64 {
|
||||
value := baseDegrees + deltaDegrees
|
||||
if value > _2pi {
|
||||
return math.Mod(value, 360.0)
|
||||
} else if value < 0 {
|
||||
return math.Mod(360.0+value, 360.0)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// DegreesToCompass returns the degree value in compass / clock orientation.
|
||||
func (m mathUtil) DegreesToCompass(deg float64) float64 {
|
||||
return m.DegreesAdd(deg, -90.0)
|
||||
}
|
||||
|
||||
// CirclePoint returns the absolute position of a circle diameter point given
|
||||
// by the radius and the theta.
|
||||
func (m mathUtil) CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) {
|
||||
x = cx + int(radius*math.Sin(thetaRadians))
|
||||
y = cy - int(radius*math.Cos(thetaRadians))
|
||||
return
|
||||
}
|
||||
|
||||
func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) {
|
||||
tempX, tempY := float64(x-cx), float64(y-cy)
|
||||
rotatedX := tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians)
|
||||
rotatedY := tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians)
|
||||
rx = int(rotatedX) + cx
|
||||
ry = int(rotatedY) + cy
|
||||
return
|
||||
}
|
||||
184
util/math_test.go
Normal file
184
util/math_test.go
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/blendlabs/go-assert"
|
||||
)
|
||||
|
||||
func TestMinAndMax(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
values := []float64{1.0, 2.0, 3.0, 4.0}
|
||||
min, max := Math.MinAndMax(values...)
|
||||
assert.Equal(1.0, min)
|
||||
assert.Equal(4.0, max)
|
||||
}
|
||||
|
||||
func TestMinAndMaxReversed(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
values := []float64{4.0, 2.0, 3.0, 1.0}
|
||||
min, max := Math.MinAndMax(values...)
|
||||
assert.Equal(1.0, min)
|
||||
assert.Equal(4.0, max)
|
||||
}
|
||||
|
||||
func TestMinAndMaxEmpty(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
values := []float64{}
|
||||
min, max := Math.MinAndMax(values...)
|
||||
assert.Equal(0.0, min)
|
||||
assert.Equal(0.0, max)
|
||||
}
|
||||
|
||||
func TestMinAndMaxOfTime(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
values := []time.Time{
|
||||
time.Now().AddDate(0, 0, -1),
|
||||
time.Now().AddDate(0, 0, -2),
|
||||
time.Now().AddDate(0, 0, -3),
|
||||
time.Now().AddDate(0, 0, -4),
|
||||
}
|
||||
min, max := Math.MinAndMaxOfTime(values...)
|
||||
assert.Equal(values[3], min)
|
||||
assert.Equal(values[0], max)
|
||||
}
|
||||
|
||||
func TestMinAndMaxOfTimeReversed(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
values := []time.Time{
|
||||
time.Now().AddDate(0, 0, -4),
|
||||
time.Now().AddDate(0, 0, -2),
|
||||
time.Now().AddDate(0, 0, -3),
|
||||
time.Now().AddDate(0, 0, -1),
|
||||
}
|
||||
min, max := Math.MinAndMaxOfTime(values...)
|
||||
assert.Equal(values[0], min)
|
||||
assert.Equal(values[3], max)
|
||||
}
|
||||
|
||||
func TestMinAndMaxOfTimeEmpty(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
values := []time.Time{}
|
||||
min, max := Math.MinAndMaxOfTime(values...)
|
||||
assert.Equal(time.Time{}, min)
|
||||
assert.Equal(time.Time{}, max)
|
||||
}
|
||||
|
||||
func TestGetRoundToForDelta(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(100.0, Math.GetRoundToForDelta(1001.00))
|
||||
assert.Equal(10.0, Math.GetRoundToForDelta(101.00))
|
||||
assert.Equal(1.0, Math.GetRoundToForDelta(11.00))
|
||||
}
|
||||
|
||||
func TestRoundUp(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(0.5, Math.RoundUp(0.49, 0.1))
|
||||
assert.Equal(1.0, Math.RoundUp(0.51, 1.0))
|
||||
assert.Equal(0.4999, Math.RoundUp(0.49988, 0.0001))
|
||||
}
|
||||
|
||||
func TestRoundDown(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.Equal(0.5, Math.RoundDown(0.51, 0.1))
|
||||
assert.Equal(1.0, Math.RoundDown(1.01, 1.0))
|
||||
assert.Equal(0.5001, Math.RoundDown(0.50011, 0.0001))
|
||||
}
|
||||
|
||||
func TestPercentDifference(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(0.5, Math.PercentDifference(1.0, 1.5))
|
||||
assert.Equal(-0.5, Math.PercentDifference(2.0, 1.0))
|
||||
}
|
||||
|
||||
func TestNormalize(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
values := []float64{10, 9, 8, 7, 6}
|
||||
normalized := Math.Normalize(values...)
|
||||
assert.Len(normalized, 5)
|
||||
assert.Equal(0.25, normalized[0])
|
||||
assert.Equal(0.1499, normalized[4])
|
||||
}
|
||||
|
||||
var (
|
||||
_degreesToRadians = map[float64]float64{
|
||||
0: 0, // !_2pi b/c no irrational nums in floats.
|
||||
45: _pi4,
|
||||
90: _pi2,
|
||||
135: _3pi4,
|
||||
180: _pi,
|
||||
225: _5pi4,
|
||||
270: _3pi2,
|
||||
315: _7pi4,
|
||||
}
|
||||
|
||||
_compassToRadians = map[float64]float64{
|
||||
0: _pi2,
|
||||
45: _pi4,
|
||||
90: 0, // !_2pi b/c no irrational nums in floats.
|
||||
135: _7pi4,
|
||||
180: _3pi2,
|
||||
225: _5pi4,
|
||||
270: _pi,
|
||||
315: _3pi4,
|
||||
}
|
||||
)
|
||||
|
||||
func TestDegreesToRadians(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
for d, r := range _degreesToRadians {
|
||||
assert.Equal(r, Math.DegreesToRadians(d))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPercentToRadians(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
for d, r := range _degreesToRadians {
|
||||
assert.Equal(r, Math.PercentToRadians(d/360.0))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRadiansToDegrees(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
for d, r := range _degreesToRadians {
|
||||
assert.Equal(d, Math.RadiansToDegrees(r))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRadianAdd(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(_pi, Math.RadianAdd(_pi2, _pi2))
|
||||
assert.Equal(_3pi2, Math.RadianAdd(_pi2, _pi))
|
||||
assert.Equal(_pi, Math.RadianAdd(_pi, _2pi))
|
||||
assert.Equal(_pi, Math.RadianAdd(_pi, -_2pi))
|
||||
}
|
||||
|
||||
func TestRotateCoordinate90(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cx, cy := 10, 10
|
||||
x, y := 5, 10
|
||||
|
||||
rx, ry := Math.RotateCoordinate(cx, cy, x, y, Math.DegreesToRadians(90))
|
||||
assert.Equal(10, rx)
|
||||
assert.Equal(5, ry)
|
||||
}
|
||||
|
||||
func TestRotateCoordinate45(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cx, cy := 10, 10
|
||||
x, y := 5, 10
|
||||
|
||||
rx, ry := Math.RotateCoordinate(cx, cy, x, y, Math.DegreesToRadians(45))
|
||||
assert.Equal(7, rx)
|
||||
assert.Equal(7, ry)
|
||||
}
|
||||
20
util/time.go
Normal file
20
util/time.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package util
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
// Time contains time utility functions.
|
||||
Time = timeUtil{}
|
||||
)
|
||||
|
||||
type timeUtil struct{}
|
||||
|
||||
// TimeToFloat64 returns a float64 representation of a time.
|
||||
func (tu timeUtil) ToFloat64(t time.Time) float64 {
|
||||
return float64(t.UnixNano())
|
||||
}
|
||||
|
||||
// Float64ToTime returns a time from a float64.
|
||||
func (tu timeUtil) FromFloat64(tf float64) time.Time {
|
||||
return time.Unix(0, int64(tf))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue