Files
gocryptotrader/common/timeperiods/timeperiods.go
Adrian Gallagher f0d45aa1d2 golangci-lint/CI: Bump versions and introduce new linters (#798)
* golangci-lint/CI: Bump versions

Fix remaining linter issues

* Specifically set AppVeyor version

* Fix the infamous typos 👀

* Add go env cmd to AppVeyor

* Add go version cmd to AppVeyor

* Specify AppVeyor image, adjust linters

* Update go get to go install due to deprecation

* Bump golangci-lint timeout time for AppVeyor

* Change NW contract to NQ

* Address nitters

* GetRandomPair -> Pair{}

* Address nits

* Address time nitterinos plus additional tweaks

* More time inception upgrades!

* Bending time and space
2021-10-14 16:38:53 +11:00

148 lines
4.0 KiB
Go

package timeperiods
import (
"errors"
"sort"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
)
// FindTimeRangesContainingData will break the start and end into time periods using the provided period
// it will then check whether any comparisonTimes are within those periods and concatenate them
// eg if no comparisonTimes match, you will receive 1 TimeRange of Start End with dataInRange = false
// eg2 if 1 comparisonTime matches in the middle of start and end, you will receive three ranges
func FindTimeRangesContainingData(start, end time.Time, period time.Duration, comparisonTimes []time.Time) ([]TimeRange, error) {
var errs common.Errors
if start.IsZero() {
errs = append(errs, errors.New("invalid start time"))
}
if end.IsZero() {
errs = append(errs, errors.New("invalid end time"))
}
if err := validatePeriod(period); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return nil, errs
}
var t TimePeriodCalculator
t.periodDuration = period
t.start = start.Truncate(period)
t.end = end.Truncate(period)
t.comparisonTimes = comparisonTimes
t.setTimePeriodExists()
t.Sort(false)
t.calculateRanges()
return t.TimeRanges, nil
}
func validatePeriod(period time.Duration) error {
if period != time.Hour &&
period != time.Second &&
period != time.Minute &&
period != time.Hour*24 {
return errors.New("invalid period")
}
return nil
}
// CalculateTimePeriodsInRange can break down start and end times into time periods
// eg 1 hourly intervals
func CalculateTimePeriodsInRange(start, end time.Time, period time.Duration) ([]TimePeriod, error) {
var errs common.Errors
if start.IsZero() {
errs = append(errs, errors.New("invalid start time"))
}
if end.IsZero() {
errs = append(errs, errors.New("invalid end time"))
}
if err := validatePeriod(period); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {
return nil, errs
}
var t TimePeriodCalculator
t.periodDuration = period
t.start = start.Truncate(period)
t.end = end.Truncate(period)
t.calculatePeriods()
return t.TimePeriods, nil
}
func (t *TimePeriodCalculator) calculateRanges() {
var tr TimeRange
for i := range t.TimePeriods {
if i != 0 {
if (t.TimePeriods[i].dataInRange && !t.TimePeriods[i-1].dataInRange) ||
(!t.TimePeriods[i].dataInRange && t.TimePeriods[i-1].dataInRange) {
// the status has changed and therefore a range has ended
tr.HasDataInRange = t.TimePeriods[i-1].dataInRange
tr.EndOfRange = t.TimePeriods[i].Time
t.TimeRanges = append(t.TimeRanges, tr)
tr = TimeRange{}
}
}
if tr.StartOfRange.IsZero() {
// start of new time range
tr.StartOfRange = t.TimePeriods[i].Time
}
}
if !tr.StartOfRange.IsZero() {
if tr.EndOfRange.IsZero() {
tr.EndOfRange = t.end
}
tr.HasDataInRange = t.TimePeriods[len(t.TimePeriods)-1].dataInRange
t.TimeRanges = append(t.TimeRanges, tr)
}
}
func (t *TimePeriodCalculator) calculatePeriods() {
if t.start.IsZero() || t.end.IsZero() {
return
}
if t.start.After(t.end) {
return
}
iterateDateMate := t.start
for !iterateDateMate.Equal(t.end) && !iterateDateMate.After(t.end) {
tp := TimePeriod{
Time: iterateDateMate,
dataInRange: false,
}
t.TimePeriods = append(t.TimePeriods, tp)
iterateDateMate = iterateDateMate.Add(t.periodDuration)
}
}
// setTimePeriodExists compares loaded comparisonTimes
// against calculated TimePeriods to determine whether
// there is existing data within the time period
func (t *TimePeriodCalculator) setTimePeriodExists() {
t.calculatePeriods()
for i := range t.TimePeriods {
for j := range t.comparisonTimes {
if t.comparisonTimes[j].Truncate(t.periodDuration).Equal(t.TimePeriods[i].Time) {
t.TimePeriods[i].dataInRange = true
break
}
}
}
}
// Sort will sort the time period asc or desc
func (t *TimePeriodCalculator) Sort(desc bool) {
sort.Slice(t.TimePeriods, func(i, j int) bool {
if desc {
return t.TimePeriods[i].Time.After(t.TimePeriods[j].Time)
}
return t.TimePeriods[i].Time.Before(t.TimePeriods[j].Time)
})
}