adding quickfix
This commit is contained in:
15
quickfix/internal/event.go
Normal file
15
quickfix/internal/event.go
Normal file
@ -0,0 +1,15 @@
|
||||
package internal
|
||||
|
||||
// Event is an abstraction for session events.
|
||||
type Event int
|
||||
|
||||
const (
|
||||
// PeerTimeout indicates the session peer has become unresponsive.
|
||||
PeerTimeout Event = iota
|
||||
// NeedHeartbeat indicates the session should send a heartbeat.
|
||||
NeedHeartbeat
|
||||
// LogonTimeout indicates the peer has not sent a logon request.
|
||||
LogonTimeout
|
||||
// LogoutTimeout indicates the peer has not sent a logout request.
|
||||
LogoutTimeout
|
||||
)
|
||||
70
quickfix/internal/event_timer.go
Normal file
70
quickfix/internal/event_timer.go
Normal file
@ -0,0 +1,70 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EventTimer struct {
|
||||
f func()
|
||||
timer *time.Timer
|
||||
done chan struct{}
|
||||
wg sync.WaitGroup
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func NewEventTimer(task func()) *EventTimer {
|
||||
t := &EventTimer{
|
||||
f: task,
|
||||
timer: newStoppedTimer(),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
t.wg.Add(1)
|
||||
go func() {
|
||||
defer t.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case <-t.timer.C:
|
||||
t.f()
|
||||
|
||||
case <-t.done:
|
||||
t.timer.Stop()
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *EventTimer) Stop() {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.once.Do(func() {
|
||||
close(t.done)
|
||||
})
|
||||
|
||||
t.wg.Wait()
|
||||
}
|
||||
|
||||
func (t *EventTimer) Reset(timeout time.Duration) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.timer.Reset(timeout)
|
||||
}
|
||||
|
||||
func newStoppedTimer() *time.Timer {
|
||||
timer := time.NewTimer(time.Second)
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return timer
|
||||
}
|
||||
27
quickfix/internal/event_timer_test.go
Normal file
27
quickfix/internal/event_timer_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) quickfixengine.org All rights reserved.
|
||||
//
|
||||
// This file may be distributed under the terms of the quickfixengine.org
|
||||
// license as defined by quickfixengine.org and appearing in the file
|
||||
// LICENSE included in the packaging of this file.
|
||||
//
|
||||
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
|
||||
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
|
||||
// PARTICULAR PURPOSE.
|
||||
//
|
||||
// See http://www.quickfixengine.org/LICENSE for licensing information.
|
||||
//
|
||||
// Contact ask@quickfixengine.org if any conditions of this licensing
|
||||
// are not clear to you.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEventTimer_Stop_idempotent(*testing.T) {
|
||||
t := NewEventTimer(func() {})
|
||||
|
||||
t.Stop()
|
||||
t.Stop()
|
||||
}
|
||||
34
quickfix/internal/session_settings.go
Normal file
34
quickfix/internal/session_settings.go
Normal file
@ -0,0 +1,34 @@
|
||||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
// SessionSettings stores all of the configuration for a given session.
|
||||
type SessionSettings struct {
|
||||
ResetOnLogon bool
|
||||
RefreshOnLogon bool
|
||||
ResetOnLogout bool
|
||||
ResetOnDisconnect bool
|
||||
HeartBtInt time.Duration
|
||||
HeartBtIntOverride bool
|
||||
SessionTime *TimeRange
|
||||
InitiateLogon bool
|
||||
ResendRequestChunkSize int
|
||||
EnableLastMsgSeqNumProcessed bool
|
||||
EnableNextExpectedMsgSeqNum bool
|
||||
SkipCheckLatency bool
|
||||
MaxLatency time.Duration
|
||||
DisableMessagePersist bool
|
||||
TimeZone *time.Location
|
||||
ResetSeqTime time.Time
|
||||
EnableResetSeqTime bool
|
||||
InChanCapacity int
|
||||
|
||||
// Required on logon for FIX.T.1 messages.
|
||||
DefaultApplVerID string
|
||||
|
||||
// Specific to initiators.
|
||||
ReconnectInterval time.Duration
|
||||
LogoutTimeout time.Duration
|
||||
LogonTimeout time.Duration
|
||||
SocketConnectAddress []string
|
||||
}
|
||||
234
quickfix/internal/testsuite/store_suite.go
Normal file
234
quickfix/internal/testsuite/store_suite.go
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright (c) quickfixengine.org All rights reserved.
|
||||
//
|
||||
// This file may be distributed under the terms of the quickfixengine.org
|
||||
// license as defined by quickfixengine.org and appearing in the file
|
||||
// LICENSE included in the packaging of this file.
|
||||
//
|
||||
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
|
||||
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
|
||||
// PARTICULAR PURPOSE.
|
||||
//
|
||||
// See http://www.quickfixengine.org/LICENSE for licensing information.
|
||||
//
|
||||
// Contact ask@quickfixengine.org if any conditions of this licensing
|
||||
// are not clear to you.
|
||||
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"quantex.com/qfixdpl/quickfix"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type StoreTestSuite struct {
|
||||
suite.Suite
|
||||
MsgStore quickfix.MessageStore
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) TestMessageStoreSetNextMsgSeqNumRefreshIncrNextMsgSeqNum() {
|
||||
// Given a MessageStore with the following sender and target seqnums
|
||||
s.Require().Nil(s.MsgStore.SetNextSenderMsgSeqNum(867))
|
||||
s.Require().Nil(s.MsgStore.SetNextTargetMsgSeqNum(5309))
|
||||
|
||||
// When the store is refreshed from its backing store
|
||||
s.Require().Nil(s.MsgStore.Refresh())
|
||||
|
||||
// Then the sender and target seqnums should still be
|
||||
s.Equal(867, s.MsgStore.NextSenderMsgSeqNum())
|
||||
s.Equal(5309, s.MsgStore.NextTargetMsgSeqNum())
|
||||
|
||||
// When the sender and target seqnums are incremented
|
||||
s.Require().Nil(s.MsgStore.IncrNextSenderMsgSeqNum())
|
||||
s.Require().Nil(s.MsgStore.IncrNextTargetMsgSeqNum())
|
||||
|
||||
// Then the sender and target seqnums should be
|
||||
s.Equal(868, s.MsgStore.NextSenderMsgSeqNum())
|
||||
s.Equal(5310, s.MsgStore.NextTargetMsgSeqNum())
|
||||
|
||||
// When the store is refreshed from its backing store
|
||||
s.Require().Nil(s.MsgStore.Refresh())
|
||||
|
||||
// Then the sender and target seqnums should still be
|
||||
s.Equal(868, s.MsgStore.NextSenderMsgSeqNum())
|
||||
s.Equal(5310, s.MsgStore.NextTargetMsgSeqNum())
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) TestMessageStoreReset() {
|
||||
// Given a MessageStore with the following sender and target seqnums
|
||||
s.Require().Nil(s.MsgStore.SetNextSenderMsgSeqNum(1234))
|
||||
s.Require().Nil(s.MsgStore.SetNextTargetMsgSeqNum(5678))
|
||||
|
||||
// When the store is reset
|
||||
s.Require().Nil(s.MsgStore.Reset())
|
||||
|
||||
// Then the sender and target seqnums should be
|
||||
s.Equal(1, s.MsgStore.NextSenderMsgSeqNum())
|
||||
s.Equal(1, s.MsgStore.NextTargetMsgSeqNum())
|
||||
|
||||
// When the store is refreshed from its backing store
|
||||
s.Require().Nil(s.MsgStore.Refresh())
|
||||
|
||||
// Then the sender and target seqnums should still be
|
||||
s.Equal(1, s.MsgStore.NextSenderMsgSeqNum())
|
||||
s.Equal(1, s.MsgStore.NextTargetMsgSeqNum())
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) fetchMessages(beginSeqNum, endSeqNum int) (msgs [][]byte) {
|
||||
s.T().Helper()
|
||||
|
||||
// Fetch messages from the new iterator
|
||||
err := s.MsgStore.IterateMessages(beginSeqNum, endSeqNum, func(msg []byte) error {
|
||||
msgs = append(msgs, msg)
|
||||
return nil
|
||||
})
|
||||
s.Require().Nil(err)
|
||||
|
||||
// Fetch messages from the old getter
|
||||
oldMsgs, err := s.MsgStore.GetMessages(beginSeqNum, endSeqNum)
|
||||
s.Require().Nil(err)
|
||||
|
||||
// Ensure the output is the same
|
||||
s.Require().Len(msgs, len(oldMsgs))
|
||||
for idx, msg := range msgs {
|
||||
s.Require().EqualValues(msg, oldMsgs[idx])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) TestMessageStoreSaveMessageGetMessage() {
|
||||
// Given the following saved messages
|
||||
expectedMsgsBySeqNum := map[int]string{
|
||||
1: "In the frozen land of Nador",
|
||||
2: "they were forced to eat Robin's minstrels",
|
||||
3: "and there was much rejoicing",
|
||||
}
|
||||
var seqNums []int
|
||||
for seqNum := range expectedMsgsBySeqNum {
|
||||
seqNums = append(seqNums, seqNum)
|
||||
}
|
||||
sort.Ints(seqNums)
|
||||
for _, seqNum := range seqNums {
|
||||
s.Require().Nil(s.MsgStore.SaveMessage(seqNum, []byte(expectedMsgsBySeqNum[seqNum])))
|
||||
}
|
||||
|
||||
// When the messages are retrieved from the MessageStore
|
||||
actualMsgs := s.fetchMessages(1, 3)
|
||||
|
||||
// Then the messages should be
|
||||
s.Require().Len(actualMsgs, 3)
|
||||
s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
|
||||
s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
|
||||
s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
|
||||
|
||||
// When the store is refreshed from its backing store
|
||||
s.Require().Nil(s.MsgStore.Refresh())
|
||||
|
||||
// And the messages are retrieved from the MessageStore
|
||||
actualMsgs = s.fetchMessages(1, 3)
|
||||
|
||||
// Then the messages should still be
|
||||
s.Require().Len(actualMsgs, 3)
|
||||
s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
|
||||
s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
|
||||
s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) TestMessageStoreSaveMessageAndIncrementGetMessage() {
|
||||
s.Require().Nil(s.MsgStore.SetNextSenderMsgSeqNum(420))
|
||||
|
||||
// Given the following saved messages
|
||||
expectedMsgsBySeqNum := map[int]string{
|
||||
1: "In the frozen land of Nador",
|
||||
2: "they were forced to eat Robin's minstrels",
|
||||
3: "and there was much rejoicing",
|
||||
}
|
||||
var seqNums []int
|
||||
for seqNum := range expectedMsgsBySeqNum {
|
||||
seqNums = append(seqNums, seqNum)
|
||||
}
|
||||
sort.Ints(seqNums)
|
||||
for _, seqNum := range seqNums {
|
||||
s.Require().Nil(s.MsgStore.SaveMessageAndIncrNextSenderMsgSeqNum(seqNum, []byte(expectedMsgsBySeqNum[seqNum])))
|
||||
}
|
||||
s.Equal(423, s.MsgStore.NextSenderMsgSeqNum())
|
||||
|
||||
// When the messages are retrieved from the MessageStore
|
||||
actualMsgs := s.fetchMessages(1, 3)
|
||||
|
||||
// Then the messages should be
|
||||
s.Require().Len(actualMsgs, 3)
|
||||
s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
|
||||
s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
|
||||
s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
|
||||
|
||||
// When the store is refreshed from its backing store
|
||||
s.Require().Nil(s.MsgStore.Refresh())
|
||||
|
||||
// And the messages are retrieved from the MessageStore
|
||||
actualMsgs = s.fetchMessages(1, 3)
|
||||
|
||||
s.Equal(423, s.MsgStore.NextSenderMsgSeqNum())
|
||||
|
||||
// Then the messages should still be
|
||||
s.Require().Len(actualMsgs, 3)
|
||||
s.Equal(expectedMsgsBySeqNum[1], string(actualMsgs[0]))
|
||||
s.Equal(expectedMsgsBySeqNum[2], string(actualMsgs[1]))
|
||||
s.Equal(expectedMsgsBySeqNum[3], string(actualMsgs[2]))
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) TestMessageStoreGetMessagesEmptyStore() {
|
||||
// When messages are retrieved from an empty store
|
||||
messages := s.fetchMessages(1, 2)
|
||||
|
||||
// Then no messages should be returned
|
||||
require.Empty(s.T(), messages, "Did not expect messages from empty store")
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) TestMessageStoreGetMessagesVariousRanges() {
|
||||
t := s.T()
|
||||
|
||||
// Given the following saved messages
|
||||
require.Nil(t, s.MsgStore.SaveMessage(1, []byte("hello")))
|
||||
require.Nil(t, s.MsgStore.SaveMessage(2, []byte("cruel")))
|
||||
require.Nil(t, s.MsgStore.SaveMessage(3, []byte("world")))
|
||||
|
||||
// When the following requests are made to the store
|
||||
var testCases = []struct {
|
||||
beginSeqNo, endSeqNo int
|
||||
expectedBytes [][]byte
|
||||
}{
|
||||
{beginSeqNo: 1, endSeqNo: 1, expectedBytes: [][]byte{[]byte("hello")}},
|
||||
{beginSeqNo: 1, endSeqNo: 2, expectedBytes: [][]byte{[]byte("hello"), []byte("cruel")}},
|
||||
{beginSeqNo: 1, endSeqNo: 3, expectedBytes: [][]byte{[]byte("hello"), []byte("cruel"), []byte("world")}},
|
||||
{beginSeqNo: 1, endSeqNo: 4, expectedBytes: [][]byte{[]byte("hello"), []byte("cruel"), []byte("world")}},
|
||||
{beginSeqNo: 2, endSeqNo: 3, expectedBytes: [][]byte{[]byte("cruel"), []byte("world")}},
|
||||
{beginSeqNo: 3, endSeqNo: 3, expectedBytes: [][]byte{[]byte("world")}},
|
||||
{beginSeqNo: 3, endSeqNo: 4, expectedBytes: [][]byte{[]byte("world")}},
|
||||
{beginSeqNo: 4, endSeqNo: 4, expectedBytes: [][]byte{}},
|
||||
{beginSeqNo: 4, endSeqNo: 10, expectedBytes: [][]byte{}},
|
||||
}
|
||||
|
||||
// Then the returned messages should be
|
||||
for _, tc := range testCases {
|
||||
actualMsgs := s.fetchMessages(tc.beginSeqNo, tc.endSeqNo)
|
||||
require.Len(t, actualMsgs, len(tc.expectedBytes))
|
||||
for i, expectedMsg := range tc.expectedBytes {
|
||||
assert.Equal(t, string(expectedMsg), string(actualMsgs[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StoreTestSuite) TestMessageStoreCreationTime() {
|
||||
s.False(s.MsgStore.CreationTime().IsZero())
|
||||
|
||||
t0 := time.Now()
|
||||
s.Require().Nil(s.MsgStore.Reset())
|
||||
t1 := time.Now()
|
||||
s.Require().True(s.MsgStore.CreationTime().After(t0))
|
||||
s.Require().True(s.MsgStore.CreationTime().Before(t1))
|
||||
}
|
||||
222
quickfix/internal/time_range.go
Normal file
222
quickfix/internal/time_range.go
Normal file
@ -0,0 +1,222 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TimeOfDay represents the time of day.
|
||||
type TimeOfDay struct {
|
||||
hour, minute, second int
|
||||
d time.Duration
|
||||
}
|
||||
|
||||
const shortForm = "15:04:05"
|
||||
|
||||
// NewTimeOfDay returns a newly initialized TimeOfDay.
|
||||
func NewTimeOfDay(hour, minute, second int) TimeOfDay {
|
||||
d := time.Duration(second)*time.Second +
|
||||
time.Duration(minute)*time.Minute +
|
||||
time.Duration(hour)*time.Hour
|
||||
|
||||
return TimeOfDay{hour: hour, minute: minute, second: second, d: d}
|
||||
}
|
||||
|
||||
// ParseTimeOfDay parses a TimeOfDay from a string in the format HH:MM:SS.
|
||||
func ParseTimeOfDay(str string) (TimeOfDay, error) {
|
||||
t, err := time.Parse(shortForm, str)
|
||||
if err != nil {
|
||||
return TimeOfDay{}, errors.Wrap(err, "time must be in the format HH:MM:SS")
|
||||
}
|
||||
|
||||
return NewTimeOfDay(t.Clock()), nil
|
||||
}
|
||||
|
||||
// TimeRange represents a time band in a given time zone.
|
||||
type TimeRange struct {
|
||||
startTime, endTime TimeOfDay
|
||||
weekdays []time.Weekday
|
||||
startDay, endDay *time.Weekday
|
||||
loc *time.Location
|
||||
}
|
||||
|
||||
// NewUTCTimeRange returns a time range in UTC.
|
||||
func NewUTCTimeRange(start, end TimeOfDay, weekdays []time.Weekday) (*TimeRange, error) {
|
||||
return NewTimeRangeInLocation(start, end, weekdays, time.UTC)
|
||||
}
|
||||
|
||||
// NewTimeRangeInLocation returns a time range in a given location.
|
||||
func NewTimeRangeInLocation(start, end TimeOfDay, weekdays []time.Weekday, loc *time.Location) (*TimeRange, error) {
|
||||
|
||||
if loc == nil {
|
||||
return nil, errors.New("time: missing Location in call to NewTimeRangeInLocation")
|
||||
}
|
||||
|
||||
return &TimeRange{
|
||||
startTime: start,
|
||||
endTime: end,
|
||||
weekdays: weekdays,
|
||||
loc: loc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewUTCWeekRange returns a weekly TimeRange.
|
||||
func NewUTCWeekRange(startTime, endTime TimeOfDay, startDay, endDay time.Weekday) (*TimeRange, error) {
|
||||
return NewWeekRangeInLocation(startTime, endTime, startDay, endDay, time.UTC)
|
||||
}
|
||||
|
||||
// NewWeekRangeInLocation returns a time range in a given location.
|
||||
func NewWeekRangeInLocation(startTime, endTime TimeOfDay, startDay, endDay time.Weekday, loc *time.Location) (*TimeRange, error) {
|
||||
r, err := NewTimeRangeInLocation(startTime, endTime, []time.Weekday{}, loc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.startDay = &startDay
|
||||
r.endDay = &endDay
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *TimeRange) isInWeekdays(day time.Weekday) bool {
|
||||
if len(r.weekdays) > 0 {
|
||||
found := false
|
||||
|
||||
for _, weekday := range r.weekdays {
|
||||
if day == weekday {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *TimeRange) addWeekdayOffset(day time.Weekday, offset int) time.Weekday {
|
||||
return (day + time.Weekday(offset)) % 7
|
||||
}
|
||||
|
||||
func (r *TimeRange) isInTimeRange(t time.Time) bool {
|
||||
t = t.In(r.loc)
|
||||
ts := NewTimeOfDay(t.Clock()).d
|
||||
|
||||
if r.startTime.d < r.endTime.d {
|
||||
if r.isInWeekdays(t.Weekday()) {
|
||||
return r.startTime.d <= ts && ts <= r.endTime.d
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if ts <= r.endTime.d {
|
||||
return r.isInWeekdays(r.addWeekdayOffset(t.Weekday(), -1))
|
||||
}
|
||||
|
||||
if ts >= r.startTime.d {
|
||||
return r.isInWeekdays(t.Weekday())
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *TimeRange) isInWeekRange(t time.Time) bool {
|
||||
t = t.In(r.loc)
|
||||
day := t.Weekday()
|
||||
|
||||
if *r.startDay == *r.endDay {
|
||||
if day == *r.startDay {
|
||||
return r.isInTimeRange(t)
|
||||
}
|
||||
|
||||
if r.startTime.d < r.endTime.d {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
switch {
|
||||
case *r.startDay < *r.endDay:
|
||||
if day < *r.startDay || *r.endDay < day {
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
if *r.endDay < day && day < *r.startDay {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
timeOfDay := NewTimeOfDay(t.Clock())
|
||||
|
||||
if day == *r.startDay {
|
||||
return timeOfDay.d >= r.startTime.d
|
||||
}
|
||||
|
||||
if day == *r.endDay {
|
||||
return timeOfDay.d <= r.endTime.d
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsInRange returns true if time t is within in the time range.
|
||||
func (r *TimeRange) IsInRange(t time.Time) bool {
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if r.startDay != nil {
|
||||
return r.isInWeekRange(t)
|
||||
}
|
||||
|
||||
return r.isInTimeRange(t)
|
||||
}
|
||||
|
||||
// IsInSameRange determines if two points in time are in the same time range.
|
||||
func (r *TimeRange) IsInSameRange(t1, t2 time.Time) bool {
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if !(r.IsInRange(t1) && r.IsInRange(t2)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if t2.Before(t1) {
|
||||
t1, t2 = t2, t1
|
||||
}
|
||||
|
||||
t1 = t1.In(r.loc)
|
||||
t1Time := NewTimeOfDay(t1.Clock())
|
||||
dayOffset := 0
|
||||
|
||||
if r.endDay == nil {
|
||||
if r.startTime.d >= r.endTime.d && t1Time.d >= r.startTime.d {
|
||||
dayOffset = 1
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case *r.endDay < t1.Weekday():
|
||||
dayOffset = 7 + int(*(r.endDay)-t1.Weekday())
|
||||
|
||||
case t1.Weekday() == *r.endDay:
|
||||
if r.endTime.d <= t1Time.d {
|
||||
dayOffset = 7
|
||||
}
|
||||
|
||||
default:
|
||||
dayOffset = int(*(r.endDay) - t1.Weekday())
|
||||
}
|
||||
}
|
||||
|
||||
sessionEnd := time.Date(t1.Year(), t1.Month(), t1.Day(), r.endTime.hour, r.endTime.minute, r.endTime.second, 0, r.loc)
|
||||
sessionEnd = sessionEnd.AddDate(0, 0, dayOffset)
|
||||
|
||||
return t2.Before(sessionEnd)
|
||||
}
|
||||
1046
quickfix/internal/time_range_test.go
Normal file
1046
quickfix/internal/time_range_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user