Codebase list golang-github-timberio-go-datemath / multiarch-fixes/main datemath_test.go
multiarch-fixes/main

Tree @multiarch-fixes/main (Download .tar.gz)

datemath_test.go @multiarch-fixes/mainraw · history · blame

package datemath_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/timberio/go-datemath"
)

// much are based on tests from Elasticsearch to ensure we handle dates in a compatible manner
// https://github.com/elastic/elasticsearch/blob/2d3f3cd61ef4b218082609928d6ffc9d20c30ba4/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java#L35
func TestParseAndEvaluate(t *testing.T) {
	tests := []struct {
		in              string
		out             string
		err             error
		businessDayFunc func(time.Time) bool

		now      string
		location *time.Location
		roundUp  bool
	}{

		// basic dates
		{
			in:  "2014",
			out: "2014-01-01T00:00:00.000Z",
		},
		{
			in:  "2014-05",
			out: "2014-05-01T00:00:00.000Z",
		},
		{
			in:  "2014-05-30",
			out: "2014-05-30T00:00:00.000Z",
		},
		{
			in:  "2014-05-30T20",
			out: "2014-05-30T20:00:00.000Z",
		},
		{
			in:  "2014-05-30T20:21",
			out: "2014-05-30T20:21:00.000Z",
		},
		{
			in:  "2014-05-30T20:21:35",
			out: "2014-05-30T20:21:35.000Z",
		},
		{
			in:  "2014-05-30T20:21:35.123",
			out: "2014-05-30T20:21:35.123Z",
		},

		// basic math
		{
			in:  "2014-11-18||+y",
			out: "2015-11-18T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||-2y",
			out: "2012-11-18T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||+3M",
			out: "2015-02-18T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||-M",
			out: "2014-10-18T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||+1w",
			out: "2014-11-25T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||-3w",
			out: "2014-10-28T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||+22d",
			out: "2014-12-10T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||-423d",
			out: "2013-09-21T00:00:00.000Z",
		},
		{
			in:  "2014-11-18T14||+13h",
			out: "2014-11-19T03:00:00.000Z",
		},
		{
			in:  "2014-11-18T14||-1h",
			out: "2014-11-18T13:00:00.000Z",
		},
		{
			in:  "2014-11-18T14||+13H",
			out: "2014-11-19T03:00:00.000Z",
		},
		{
			in:  "2014-11-18T14||-1H",
			out: "2014-11-18T13:00:00.000Z",
		},
		{
			in:  "2014-11-18T14:27||+10240m",
			out: "2014-11-25T17:07:00.000Z",
		},
		{
			in:  "2014-11-18T14:27||-10m",
			out: "2014-11-18T14:17:00.000Z",
		},
		{
			in:  "2014-11-18T14:27:32||+60s",
			out: "2014-11-18T14:28:32.000Z",
		},
		{
			in:  "2014-11-18T14:27:32||-3600s",
			out: "2014-11-18T13:27:32.000Z",
		},
		{
			in:  "2014-11-19T14:27:32||/w",
			out: "2014-11-17T00:00:00.000Z",
		},
		{
			in:  "2014-11-01T14:27:32||/w",
			out: "2014-10-27T00:00:00.000Z",
		},

		// lenient math
		{
			in:  "2014-05-30T20:21||",
			out: "2014-05-30T20:21:00.000Z",
		},

		// multiple adjustments
		{
			in:  "2014-11-18||+1M-1M",
			out: "2014-11-18T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||+1M-1m",
			out: "2014-12-17T23:59:00.000Z",
		},
		{
			in:  "2014-11-18||-1m+1M",
			out: "2014-12-17T23:59:00.000Z",
		},
		{
			in:  "2014-11-18||+1M/M",
			out: "2014-12-01T00:00:00.000Z",
		},
		{
			in:  "2014-11-18||+1M/M+1h",
			out: "2014-12-01T01:00:00.000Z",
		},

		// now
		{
			now: "2014-11-18T14:27:32.000Z",

			in:  "now",
			out: "2014-11-18T14:27:32.000Z",
		},
		{
			now: "2014-11-18T14:27:32.000Z",

			in:  "now+M",
			out: "2014-12-18T14:27:32.000Z",
		},
		{
			now: "2014-11-18T14:27:32.000Z",

			in:  "now/m",
			out: "2014-11-18T14:27:00.000Z",
		},
		{
			now:     "2014-11-18T14:27:32.000Z",
			roundUp: true,

			in:  "now/m",
			out: "2014-11-18T14:27:59.999Z",
		},

		// epoch times
		{
			in:  "04:52:20",
			out: "1970-01-01T04:52:20.000Z",
		},

		// timestamps
		{
			in:  "1418248078000",
			out: "2014-12-10T21:47:58.000Z",
		},
		{
			in:  "32484216259000",
			out: "2999-05-20T17:24:19.000Z",
		},
		{
			in:  "253382837059000",
			out: "9999-05-20T17:24:19.000Z",
		},
		{
			in:  "1418248078000||/m",
			out: "2014-12-10T21:47:00.000Z",
		},

		// timezones
		{
			in:  "2014-05-30T20:21+03:00",
			out: "2014-05-30T17:21:00.000Z",
		},
		{
			in:  "2014-05-30T20:21Z",
			out: "2014-05-30T20:21:00.000Z",
		},
		{
			location: time.FixedZone("custom", 2*60*60),

			in:  "2014-05-30T20:21",
			out: "2014-05-30T18:21:00.000Z",
		},
		{
			location: time.FixedZone("custom", 2*60*60),

			in:  "2014-05-30T20:21+03:00",
			out: "2014-05-30T17:21:00.000Z",
		},

		// business days
		{
			now: "2014-11-18T14:27:32.000Z",
			in:  "now+5b",
			out: "2014-11-25T14:27:32.000Z",
		},
		{
			now: "2014-11-18T14:27:32.000Z",
			in:  "now-5b",
			out: "2014-11-11T14:27:32.000Z",
		},
		{
			now: "2014-12-23T14:27:32.000Z",
			in:  "now+2b",
			businessDayFunc: func(t time.Time) bool {
				return t.Format("20060102") != "20141225"
			},
			out: "2014-12-26T14:27:32.000Z",
		},

		// errors
		{
			in:  "2014-05-35T20:21:21Z",
			out: "2014-05-30T20:21:21Z",

			err: fmt.Errorf(`day 35 out of bounds for month 5 at character 11 starting with "5"`),
		},
	}
	for _, tt := range tests {
		t.Run(tt.in, func(t *testing.T) {
			var err error

			now := time.Now()
			if tt.now != "" {
				now, err = time.Parse(time.RFC3339Nano, tt.now)
				if err != nil {
					t.Fatal(err)
				}
			}

			location := time.UTC
			if tt.location != nil {
				location = tt.location
			}

			out, err := datemath.ParseAndEvaluate(tt.in, datemath.WithNow(now), datemath.WithLocation(location), datemath.WithBusinessDayFunc(tt.businessDayFunc), datemath.WithRoundUp(tt.roundUp))
			switch {
			case err == nil && tt.err != nil:
				t.Errorf("ParseAndEvaluate(%+v) returned no error, expected error %q", tt.in, tt.err)
				return
			case err != nil && tt.err == nil:
				t.Errorf("ParseAndEvaluate(%+v) returned error %q, expected no error", tt.in, err)
				return
			case err != nil && tt.err != nil:
				if tt.err.Error() != err.Error() {
					t.Errorf("ParseAndEvaluate(%+v) returned error %q, expected error %q", tt.in, err, tt.err)
					return
				}
				return
			}

			expected, err := time.Parse(time.RFC3339Nano, tt.out)
			if err != nil {
				t.Fatal(err)
			}

			if !out.Equal(expected) {
				t.Errorf("ParseAndEvaluate(%q) returned %s, expected %s", tt.in, out, expected)
			}
		})
	}
}

var benchmarkParseResult datemath.Expression // used to avoid compiler optimizations

func Benchmark_Parse(b *testing.B) {
	var (
		expr datemath.Expression
		err  error
	)

	bench := func(s string) func(*testing.B) {
		return func(b *testing.B) {
			b.ReportAllocs()

			for i := 0; i < b.N; i++ {
				expr, err = datemath.Parse(s)
				if err != nil {
					b.Fatal(err)
				}
			}
		}
	}

	b.Run("now", bench("now"))
	b.Run("rfc3339", bench("2014-05-30T20:21Z"))
	b.Run("now with one operation", bench("now+10240m"))
	b.Run("fixed with one operation", bench("2014-05-30T20:21Z||+10240m"))

	benchmarkParseResult = expr
}

var benchmarkExpressionTime time.Time // used to avoid compiler optimizations
func BenchmarkExpression_Time(b *testing.B) {
	var t time.Time

	bench := func(s string) func(*testing.B) {
		expr, err := datemath.Parse(s)
		if err != nil {
			panic(err)
		}
		return func(b *testing.B) {
			b.ReportAllocs()

			for i := 0; i < b.N; i++ {
				t = expr.Time()
				if err != nil {
					b.Fatal(err)
				}
			}
		}
	}

	b.Run("now", bench("now"))
	b.Run("rfc3339", bench("2014-05-30T20:21Z"))
	b.Run("now with one operation", bench("now+10240m"))
	b.Run("fixed with one operation", bench("2014-05-30T20:21Z||+10240m"))

	benchmarkExpressionTime = t
}

func ExampleParse() {
	now, _ := time.Parse(time.RFC3339, "2014-05-30T20:21:35.123Z")

	expressions := []string{
		"now-15m",
		"now/w",
		"now+1M",
		"2014-05-31||+1M/w",
	}

	for _, expression := range expressions {
		t, err := datemath.Parse(expression)
		if err != nil {
			panic(err)
		}
		fmt.Println(t.Time(datemath.WithNow(now)))
	}

	//Output:
	//2014-05-30 20:06:35.123 +0000 UTC
	//2014-05-26 00:00:00 +0000 UTC
	//2014-06-30 20:21:35.123 +0000 UTC
	//2014-06-30 00:00:00 +0000 UTC
}