func Between(from, to interface{}, bounds ...BoundsKind) TestDeep {
	b := tdBetween{
		base:        newBase(3),
		expectedMin: reflect.ValueOf(from),
		expectedMax: reflect.ValueOf(to),
	}
	const usage = "(NUM|STRING|TIME, NUM|STRING|TIME/DURATION[, BOUNDS_KIND])"
	if len(bounds) > 0 {
		if len(bounds) > 1 {
			b.err = ctxerr.OpTooManyParams("Between", usage)
			return &b
		}
		if bounds[0] == BoundsInIn || bounds[0] == BoundsInOut {
			b.minBound = boundIn
		} else {
			b.minBound = boundOut
		}
		if bounds[0] == BoundsInIn || bounds[0] == BoundsOutIn {
			b.maxBound = boundIn
		} else {
			b.maxBound = boundOut
		}
	} else {
		b.minBound = boundIn
		b.maxBound = boundIn
	}
	if b.expectedMax.Type() == b.expectedMin.Type() {
		return b.initBetween(usage)
	}
	ok, convertible := types.IsTypeOrConvertible(b.expectedMin, types.Time)
	if ok {
		if d, ok := to.(time.Duration); ok {
			if convertible {
				b.expectedMax = reflect.ValueOf(
					b.expectedMin.
						Convert(types.Time).
						Interface().(time.Time).
						Add(d)).
					Convert(b.expectedMin.Type())
			} else {
				b.expectedMax = reflect.ValueOf(from.(time.Time).Add(d))
			}
			return b.initBetween(usage)
		}
		b.err = ctxerr.OpBad("Between",
			"Between(FROM, TO): when FROM type is %[1]s, TO must have the same type or time.Duration: %[2]s ≠ %[1]s|time.Duration",
			b.expectedMin.Type(),
			b.expectedMax.Type(),
		)
		return &b
	}
	b.err = ctxerr.OpBad("Between",
		"Between(FROM, TO): FROM and TO must have the same type: %s ≠ %s",
		b.expectedMin.Type(),
		b.expectedMax.Type(),
	)
	return &b
}
summary(Between): checks that a number, string or time.Time is
between two bounds
input(Between): str,int,float,cplx(todo),struct(time.Time)
Between operator checks that data is between "from" and
"to". "from" and "to" can be any numeric, string or time.Time (or
assignable) value. "from" and "to" must be the same kind as the
compared value if numeric, and the same type if string or time.Time
(or assignable). time.Duration type is accepted as "to" when "from"
is time.Time or convertible. "bounds" allows to specify whether
bounds are included or not:
- BoundsInIn (default): between "from" and "to" both included
- BoundsInOut: between "from" included and "to" excluded
- BoundsOutIn: between "from" excluded and "to" included
- BoundsOutOut: between "from" and "to" both excluded

If "bounds" is missing, it defaults to BoundsInIn.

tc.Cmp(t, 17, td.Between(17, 20))               // succeeds, BoundsInIn by default
tc.Cmp(t, 17, td.Between(10, 17, BoundsInOut))  // fails
tc.Cmp(t, 17, td.Between(10, 17, BoundsOutIn))  // succeeds
tc.Cmp(t, 17, td.Between(17, 20, BoundsOutOut)) // fails

TypeBehind method returns the reflect.Type of "from" (same as the "to" one.)
package td
import (
	"fmt"
	"math"
	"reflect"
	"time"
	"github.com/maxatome/go-testdeep/internal/ctxerr"
	"github.com/maxatome/go-testdeep/internal/types"
	"github.com/maxatome/go-testdeep/internal/util"
)
type boundCmp uint8
const (
	boundNone boundCmp = iota
	boundIn
	boundOut
)
type tdBetween struct {
	base
	expectedMin reflect.Value
	expectedMax reflect.Value
	minBound boundCmp
	maxBound boundCmp
}
var _ TestDeep = &tdBetween{}
type BoundsKind uint8
const (
	BoundsInIn BoundsKind = iota
	BoundsInOut
	BoundsOutIn
	BoundsOutOut
)
type tdBetweenTime struct {
	tdBetween
	expectedType reflect.Type
	mustConvert  bool
}
var _ TestDeep = &tdBetweenTime{}