date, nyse market hours range.
This commit is contained in:
parent
c2f7c99c3f
commit
c4066176cf
305
date/date.go
Normal file
305
date/date.go
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
package date
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
// Eastern returns the eastern timezone.
|
||||||
|
func Eastern() *time.Location {
|
||||||
|
if _eastern == nil {
|
||||||
|
_easternLock.Lock()
|
||||||
|
defer _easternLock.Unlock()
|
||||||
|
if _eastern == nil {
|
||||||
|
_eastern, _ = time.LoadLocation("America/New_York")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _eastern
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional returns a pointer reference to a given time.
|
||||||
|
func Optional(t time.Time) *time.Time {
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWeekDay returns if the day is a monday->friday.
|
||||||
|
func IsWeekDay(day time.Weekday) bool {
|
||||||
|
return !IsWeekendDay(day)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWeekendDay returns if the day is a monday->friday.
|
||||||
|
func IsWeekendDay(day time.Weekday) bool {
|
||||||
|
return day == time.Saturday || day == time.Sunday
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeDate returns if a timestamp is strictly before another date (ignoring hours, minutes etc.)
|
||||||
|
func BeforeDate(before, reference time.Time) bool {
|
||||||
|
if before.Year() < reference.Year() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if before.Month() < reference.Month() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return before.Year() == reference.Year() && before.Month() == reference.Month() && before.Day() < reference.Day()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNYSEHoliday returns if a date was/is on a nyse holiday day.
|
||||||
|
func IsNYSEHoliday(t time.Time) bool {
|
||||||
|
te := t.In(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarketOpen returns 0930 on a given day.
|
||||||
|
func MarketOpen(on time.Time) time.Time {
|
||||||
|
onEastern := on.In(Eastern())
|
||||||
|
return time.Date(onEastern.Year(), onEastern.Month(), onEastern.Day(), 9, 30, 0, 0, Eastern())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarketClose returns 1600 on a given day.
|
||||||
|
func MarketClose(on time.Time) time.Time {
|
||||||
|
onEastern := on.In(Eastern())
|
||||||
|
return time.Date(onEastern.Year(), onEastern.Month(), onEastern.Day(), 16, 0, 0, 0, Eastern())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMarketOpen returns the next market open after a given time.
|
||||||
|
func NextMarketOpen(after time.Time) time.Time {
|
||||||
|
afterEastern := after.In(Eastern())
|
||||||
|
todaysOpen := MarketOpen(afterEastern)
|
||||||
|
|
||||||
|
if afterEastern.Before(todaysOpen) && IsWeekDay(todaysOpen.Weekday()) && !IsNYSEHoliday(todaysOpen) {
|
||||||
|
return todaysOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterEastern.Equal(todaysOpen) { //rare but it might happen.
|
||||||
|
return todaysOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
for cursorDay := 1; cursorDay < 6; cursorDay++ {
|
||||||
|
newDay := todaysOpen.AddDate(0, 0, cursorDay)
|
||||||
|
if IsWeekDay(newDay.Weekday()) && !IsNYSEHoliday(afterEastern) {
|
||||||
|
return time.Date(newDay.Year(), newDay.Month(), newDay.Day(), 9, 30, 0, 0, Eastern())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Epoch //we should never reach this.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMarketClose returns the next market close after a given time.
|
||||||
|
func NextMarketClose(after time.Time) time.Time {
|
||||||
|
afterEastern := after.In(Eastern())
|
||||||
|
|
||||||
|
todaysClose := MarketClose(afterEastern)
|
||||||
|
if afterEastern.Before(todaysClose) && IsWeekDay(todaysClose.Weekday()) && !IsNYSEHoliday(todaysClose) {
|
||||||
|
return todaysClose
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterEastern.Equal(todaysClose) { //rare but it might happen.
|
||||||
|
return todaysClose
|
||||||
|
}
|
||||||
|
|
||||||
|
for cursorDay := 1; cursorDay < 6; cursorDay++ {
|
||||||
|
newDay := todaysClose.AddDate(0, 0, cursorDay)
|
||||||
|
if IsWeekDay(newDay.Weekday()) && !IsNYSEHoliday(newDay) {
|
||||||
|
return time.Date(newDay.Year(), newDay.Month(), newDay.Day(), 16, 0, 0, 0, Eastern())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Epoch //we should never reach this.
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateMarketSecondsBetween calculates the number of seconds the market was open between two dates.
|
||||||
|
func CalculateMarketSecondsBetween(start, end time.Time) (seconds int64) {
|
||||||
|
se := start.In(Eastern())
|
||||||
|
ee := end.In(Eastern())
|
||||||
|
|
||||||
|
startMarketOpen := NextMarketOpen(se)
|
||||||
|
startMarketClose := NextMarketClose(se)
|
||||||
|
|
||||||
|
if (se.Equal(startMarketOpen) || se.After(startMarketOpen)) && se.Before(startMarketClose) {
|
||||||
|
seconds += int64(startMarketClose.Sub(se) / time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := NextMarketOpen(startMarketClose)
|
||||||
|
for BeforeDate(cursor, ee) {
|
||||||
|
if IsWeekDay(cursor.Weekday()) && !IsNYSEHoliday(cursor) {
|
||||||
|
close := NextMarketClose(cursor)
|
||||||
|
seconds += int64(close.Sub(cursor) / time.Second)
|
||||||
|
}
|
||||||
|
cursor = cursor.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalMarketOpen := NextMarketOpen(cursor)
|
||||||
|
finalMarketClose := NextMarketClose(cursor)
|
||||||
|
if end.After(finalMarketOpen) {
|
||||||
|
if end.Before(finalMarketClose) {
|
||||||
|
seconds += int64(end.Sub(finalMarketOpen) / time.Second)
|
||||||
|
} else {
|
||||||
|
seconds += int64(finalMarketClose.Sub(finalMarketOpen) / time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format returns a string representation of a date.
|
||||||
|
func format(t time.Time) string {
|
||||||
|
return t.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a date from a string.
|
||||||
|
func parse(str string) time.Time {
|
||||||
|
res, _ := time.Parse("2006-01-02", str)
|
||||||
|
return res
|
||||||
|
}
|
87
date/date_test.go
Normal file
87
date/date_test.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package date
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
assert "github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBeforeDate(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
assert.True(BeforeDate(parse("2015-07-02"), parse("2016-07-01")))
|
||||||
|
assert.True(BeforeDate(parse("2016-06-01"), parse("2016-07-01")))
|
||||||
|
assert.True(BeforeDate(parse("2016-07-01"), parse("2016-07-02")))
|
||||||
|
|
||||||
|
assert.False(BeforeDate(parse("2016-07-01"), parse("2016-07-01")))
|
||||||
|
assert.False(BeforeDate(parse("2016-07-03"), parse("2016-07-01")))
|
||||||
|
assert.False(BeforeDate(parse("2016-08-03"), parse("2016-07-01")))
|
||||||
|
assert.False(BeforeDate(parse("2017-08-03"), parse("2016-07-01")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextMarketOpen(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
beforeOpen := time.Date(2016, 07, 18, 9, 0, 0, 0, Eastern())
|
||||||
|
todayOpen := time.Date(2016, 07, 18, 9, 30, 0, 0, Eastern())
|
||||||
|
|
||||||
|
afterOpen := time.Date(2016, 07, 18, 9, 31, 0, 0, Eastern())
|
||||||
|
tomorrowOpen := time.Date(2016, 07, 19, 9, 30, 0, 0, Eastern())
|
||||||
|
|
||||||
|
afterFriday := time.Date(2016, 07, 22, 9, 31, 0, 0, Eastern())
|
||||||
|
mondayOpen := time.Date(2016, 07, 25, 9, 30, 0, 0, Eastern())
|
||||||
|
|
||||||
|
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Eastern())
|
||||||
|
|
||||||
|
assert.True(todayOpen.Equal(NextMarketOpen(beforeOpen)))
|
||||||
|
assert.True(tomorrowOpen.Equal(NextMarketOpen(afterOpen)))
|
||||||
|
assert.True(mondayOpen.Equal(NextMarketOpen(afterFriday)))
|
||||||
|
assert.True(mondayOpen.Equal(NextMarketOpen(weekend)))
|
||||||
|
|
||||||
|
testRegression := time.Date(2016, 07, 18, 16, 0, 0, 0, Eastern())
|
||||||
|
shouldbe := time.Date(2016, 07, 19, 9, 30, 0, 0, Eastern())
|
||||||
|
|
||||||
|
assert.True(shouldbe.Equal(NextMarketOpen(testRegression)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextMarketClose(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
beforeClose := time.Date(2016, 07, 18, 15, 0, 0, 0, Eastern())
|
||||||
|
todayClose := time.Date(2016, 07, 18, 16, 00, 0, 0, Eastern())
|
||||||
|
|
||||||
|
afterClose := time.Date(2016, 07, 18, 16, 1, 0, 0, Eastern())
|
||||||
|
tomorrowClose := time.Date(2016, 07, 19, 16, 00, 0, 0, Eastern())
|
||||||
|
|
||||||
|
afterFriday := time.Date(2016, 07, 22, 16, 1, 0, 0, Eastern())
|
||||||
|
mondayClose := time.Date(2016, 07, 25, 16, 0, 0, 0, Eastern())
|
||||||
|
|
||||||
|
weekend := time.Date(2016, 07, 23, 9, 31, 0, 0, Eastern())
|
||||||
|
|
||||||
|
assert.True(todayClose.Equal(NextMarketClose(beforeClose)))
|
||||||
|
assert.True(tomorrowClose.Equal(NextMarketClose(afterClose)))
|
||||||
|
assert.True(mondayClose.Equal(NextMarketClose(afterFriday)))
|
||||||
|
assert.True(mondayClose.Equal(NextMarketClose(weekend)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateMarketSecondsBetween(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2016, 07, 18, 9, 30, 0, 0, Eastern())
|
||||||
|
end := time.Date(2016, 07, 22, 16, 00, 0, 0, Eastern())
|
||||||
|
|
||||||
|
shouldbe := 5 * 6.5 * 60 * 60
|
||||||
|
|
||||||
|
assert.Equal(shouldbe, CalculateMarketSecondsBetween(start, end))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateMarketSecondsBetweenLTM(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2015, 07, 01, 9, 30, 0, 0, Eastern())
|
||||||
|
end := time.Date(2016, 07, 01, 9, 30, 0, 0, Eastern())
|
||||||
|
|
||||||
|
shouldbe := 253 * 6.5 * 60 * 60 //253 full market days since this date last year.
|
||||||
|
assert.Equal(shouldbe, CalculateMarketSecondsBetween(start, end))
|
||||||
|
}
|
65
nyse_market_hours_range.go
Normal file
65
nyse_market_hours_range.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart/date"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NYSEMarketHoursRange is a special type of range that compresses a time range into just the
|
||||||
|
// market (i.e. NYSE operating hours and days) range.
|
||||||
|
type NYSEMarketHoursRange struct {
|
||||||
|
Min time.Time
|
||||||
|
Max time.Time
|
||||||
|
Domain int
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMin returns the min value.
|
||||||
|
func (mhr NYSEMarketHoursRange) GetMin() float64 {
|
||||||
|
return TimeToFloat64(mhr.Min)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMax returns the max value.
|
||||||
|
func (mhr NYSEMarketHoursRange) GetMax() float64 {
|
||||||
|
return TimeToFloat64(mhr.Max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMin sets the min value.
|
||||||
|
func (mhr *NYSEMarketHoursRange) SetMin(min float64) {
|
||||||
|
mhr.Min = Float64ToTime(min)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMax sets the max value.
|
||||||
|
func (mhr *NYSEMarketHoursRange) SetMax(max float64) {
|
||||||
|
mhr.Max = Float64ToTime(max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelta gets the delta.
|
||||||
|
func (mhr NYSEMarketHoursRange) GetDelta() float64 {
|
||||||
|
min := TimeToFloat64(mhr.Min)
|
||||||
|
max := TimeToFloat64(mhr.Min)
|
||||||
|
return max - min
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain gets the domain.
|
||||||
|
func (mhr NYSEMarketHoursRange) GetDomain() int {
|
||||||
|
return mhr.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDomain sets the domain.
|
||||||
|
func (mhr *NYSEMarketHoursRange) SetDomain(domain int) {
|
||||||
|
mhr.Domain = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr NYSEMarketHoursRange) String() string {
|
||||||
|
return fmt.Sprintf("MarketHoursRange [%s, %s] => %d", mhr.Min.Format(DefaultDateFormat), mhr.Max.Format(DefaultDateFormat), mhr.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate maps a given value into the ContinuousRange space.
|
||||||
|
func (mhr NYSEMarketHoursRange) Translate(value float64) int {
|
||||||
|
valueTime := Float64ToTime(value)
|
||||||
|
deltaSeconds := date.CalculateMarketSecondsBetween(mhr.Min, mhr.Max)
|
||||||
|
valueDelta := date.CalculateMarketSecondsBetween(mhr.Min, valueTime)
|
||||||
|
return int(float64(valueDelta) / float64(deltaSeconds))
|
||||||
|
}
|
5
util.go
5
util.go
|
@ -20,6 +20,11 @@ func TimeToFloat64(t time.Time) float64 {
|
||||||
return float64(t.UnixNano())
|
return float64(t.UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Float64ToTime returns a time from a float64.
|
||||||
|
func Float64ToTime(tf float64) time.Time {
|
||||||
|
return time.Unix(0, int64(tf))
|
||||||
|
}
|
||||||
|
|
||||||
// MinAndMax returns both the min and max in one pass.
|
// MinAndMax returns both the min and max in one pass.
|
||||||
func MinAndMax(values ...float64) (min float64, max float64) {
|
func MinAndMax(values ...float64) (min float64, max float64) {
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user