func tsToString(ts primitive.Timestamp) string {
	return fmt.Sprintf(
		"%s, I=%d",
		time.Unix(int64(ts.T), 0).Format(time.RFC3339),
		ts.I,
	)
}
Converts a bson timestamp to string
package oplog
import (
	"fmt"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"log"
	"sync"
	"time"
)
type TimestampResolver interface {
	GetResumeTimestamp(candidateTs primitive.Timestamp, source string) chan primitive.Timestamp
}
const TS_SOURCE_MONSTACHE = "monstache"
const TS_SOURCE_OPLOG = "oplog"
type TimestampResolverSimple struct{}
func (r TimestampResolverSimple) GetResumeTimestamp(candidateTs primitive.Timestamp, source string) chan primitive.Timestamp {
	tmpResultChan := make(chan primitive.Timestamp, 1)
	tmpResultChan <- candidateTs
	return tmpResultChan
}
type TimestampResolverEarliest struct {
	connectionsTotal   int
	connectionsQueried int
	earliestTs         primitive.Timestamp
	earliestTsSource   string
	resultChan         chan primitive.Timestamp
	logger             *log.Logger
	m                  sync.Mutex
}
func NewTimestampResolverEarliest(connectionsTotal int, logger *log.Logger) *TimestampResolverEarliest {
	return &TimestampResolverEarliest{
		connectionsTotal: connectionsTotal,
		resultChan:       make(chan primitive.Timestamp, connectionsTotal),
		logger:           logger,
	}
}
func (resolver *TimestampResolverEarliest) GetResumeTimestamp(candidateTs primitive.Timestamp, source string) chan primitive.Timestamp {
	resolver.m.Lock()
	defer resolver.m.Unlock()
	if resolver.connectionsQueried >= resolver.connectionsTotal {
		resolver.logger.Printf(
			"Earliest oplog resume timestamp is already calculated: %s",
			tsToString(resolver.earliestTs),
		)
		tmpResultChan := make(chan primitive.Timestamp, 1)
		tmpResultChan <- resolver.earliestTs
		return tmpResultChan
	}
	resolver.connectionsQueried++
	resolver.updateEarliestTs(source, candidateTs)
	if resolver.connectionsQueried == resolver.connectionsTotal {
		resolver.logger.Printf(
			"Earliest oplog resume timestamp calculated: %s, source: %s",
			tsToString(resolver.earliestTs),
			resolver.earliestTsSource,
		)
		for i := 0; i < resolver.connectionsTotal; i++ {
			resolver.resultChan <- resolver.earliestTs
		}
	}
	return resolver.resultChan
}
func (resolver *TimestampResolverEarliest) updateEarliestTs(source string, candidateTs primitive.Timestamp) {
	if resolver.earliestTsSource == TS_SOURCE_MONSTACHE && source == TS_SOURCE_OPLOG {
		return
	}
	if resolver.earliestTs.T == 0 ||
		(resolver.earliestTsSource == TS_SOURCE_OPLOG && source == TS_SOURCE_MONSTACHE) ||
		(primitive.CompareTimestamp(candidateTs, resolver.earliestTs) < 0) {
		resolver.logger.Printf(
			"Candidate resume timestamp: %s, source: %s",
			tsToString(candidateTs),
			source,
		)
		resolver.earliestTs = candidateTs
		resolver.earliestTsSource = source
	}
}