From 02381fb2944433a01be04dd444fc157cf6835bd2 Mon Sep 17 00:00:00 2001 From: ccoVeille <3875889+ccoVeille@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:35:52 +0100 Subject: [PATCH 1/9] fix(date): use available timezone if any --- registry/time/functions.go | 25 +++------ registry/time/functions_test.go | 94 +++++++++++++++++++++++++++++---- registry/time/helpers.go | 34 ++++++++++++ registry/time/helpers_test.go | 58 ++++++++++++++++++++ 4 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 registry/time/helpers.go create mode 100644 registry/time/helpers_test.go diff --git a/registry/time/functions.go b/registry/time/functions.go index 7e0ad2f..669d757 100644 --- a/registry/time/functions.go +++ b/registry/time/functions.go @@ -22,7 +22,12 @@ import ( // // [Sprout Documentation: date]: https://docs.atom.codes/sprout/registries/time#date func (tr *TimeRegistry) Date(fmt string, date any) (string, error) { - return tr.DateInZone(fmt, date, "Local") + t := computeTimeFromFormat(date) + + // compute the timezone from the date if it has one + loc := time.FixedZone(t.Zone()) + + return t.In(loc).Format(fmt), nil } // DateInZone formats a given date or current time into a specified format string in a specified timezone. @@ -42,23 +47,7 @@ func (tr *TimeRegistry) Date(fmt string, date any) (string, error) { // // [Sprout Documentation: dateInZone]: https://docs.atom.codes/sprout/registries/time#dateinzone func (tr *TimeRegistry) DateInZone(fmt string, date any, zone string) (string, error) { - // TODO: Change signature - var t time.Time - switch date := date.(type) { - default: - t = time.Now() - case time.Time: - t = date - case *time.Time: - t = *date - case int64: - t = time.Unix(date, 0) - case int: - t = time.Unix(int64(date), 0) - case int32: - t = time.Unix(int64(date), 0) - } - + t := computeTimeFromFormat(date) loc, err := time.LoadLocation(zone) if err != nil { return t.In(time.UTC).Format(fmt), err diff --git a/registry/time/functions_test.go b/registry/time/functions_test.go index 70430a3..c0abc57 100644 --- a/registry/time/functions_test.go +++ b/registry/time/functions_test.go @@ -4,21 +4,97 @@ import ( "testing" goTime "time" + "github.com/stretchr/testify/require" + "github.com/go-sprout/sprout/pesticide" "github.com/go-sprout/sprout/registry/time" ) func TestDate(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + t.Run("UTC", func(t *testing.T) { + timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + tc := []pesticide.TestCase{ + {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest}}, + {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": &timeTest}}, + } - tc := []pesticide.TestCase{ - {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest}}, - {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": &timeTest}}, - {Name: "TestTimeObjectUnix", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest.Unix()}}, - {Name: "TestTimeObjectUnixInt", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": int(timeTest.Unix())}}, - } + pesticide.RunTestCases(t, time.NewRegistry(), tc) + }) - pesticide.RunTestCases(t, time.NewRegistry(), tc) + t.Run("New York timezone", func(t *testing.T) { + local, err := goTime.LoadLocation("America/New_York") + require.NoError(t, err) + + timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, local) + + tc := []pesticide.TestCase{ + {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": timeTest}}, + {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, + } + + pesticide.RunTestCases(t, time.NewRegistry(), tc) + }) + + t.Run("New York offset", func(t *testing.T) { + timeTest, err := goTime.Parse("02 Jan 06 15:04 -0700", "07 May 24 15:04 -0400") + require.NoError(t, err) + + tc := []pesticide.TestCase{ + {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": timeTest}}, + {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, + } + + pesticide.RunTestCases(t, time.NewRegistry(), tc) + }) + + t.Run("New York timezone", func(t *testing.T) { + local, err := goTime.LoadLocation("America/New_York") + require.NoError(t, err) + + timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, local) + + tc := []pesticide.TestCase{ + {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": timeTest}}, + {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, + } + + pesticide.RunTestCases(t, time.NewRegistry(), tc) + }) + + t.Run("unixtime", func(t *testing.T) { + t.Run("UTC", func(t *testing.T) { + // temporarily force time.Local to UTC + time.ForceTimeLocal(t, goTime.UTC) + + // here we are simulating a [gotime.Now] + timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + + tc := []pesticide.TestCase{ + {Name: "TestTimeObjectUnix", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest.Unix()}}, + {Name: "TestTimeObjectUnixInt", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": int(timeTest.Unix())}}, + } + + pesticide.RunTestCases(t, time.NewRegistry(), tc) + }) + + t.Run("New York timezone", func(t *testing.T) { + local, err := goTime.LoadLocation("America/New_York") + require.NoError(t, err) + + // temporarily force time.Local to New York + time.ForceTimeLocal(t, local) + + // here we are simulating a [gotime.Now] call + timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, local) + + tc := []pesticide.TestCase{ + {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": timeTest}}, + {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, + } + + pesticide.RunTestCases(t, time.NewRegistry(), tc) + }) + }) } func TestDateInZone(t *testing.T) { @@ -30,7 +106,7 @@ func TestDateInZone(t *testing.T) { {Name: "TestTimeObjectUnix", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest.Unix()}}, {Name: "TestTimeObjectUnixInt", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": int(timeTest.Unix())}}, {Name: "TestTimeObjectUnixInt", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": int32(timeTest.Unix())}}, - {Name: "TestWithInvalidInput", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: goTime.Now().Format("02 Jan 06 15:04 -0700"), Data: map[string]any{"V": "invalid"}}, + {Name: "TestWithInvalidInput", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: goTime.Now().UTC().Format("02 Jan 06 15:04 -0700"), Data: map[string]any{"V": "invalid"}}, {Name: "TestWithInvalidZone", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "invalid" }}`, ExpectedErr: "unknown time zone invalid", Data: map[string]any{"V": timeTest}}, } diff --git a/registry/time/helpers.go b/registry/time/helpers.go new file mode 100644 index 0000000..7f1fe2a --- /dev/null +++ b/registry/time/helpers.go @@ -0,0 +1,34 @@ +package time + +import ( + "testing" + "time" +) + +// computeTimeFromFormat returns a time.Time object from the given date. +func computeTimeFromFormat(date any) time.Time { + switch date := date.(type) { + case time.Time: + return date + case *time.Time: + return *date + case int64: + return time.Unix(date, 0) + case int: + return time.Unix(int64(date), 0) + case int32: + return time.Unix(int64(date), 0) + } + + // otherwise, fallback to the current time + return time.Now().Local() +} + +// ForceTimeLocal temporarily sets [time.Local] for test purpose. +func ForceTimeLocal(t *testing.T, local *time.Location) { + t.Helper() + + originalLocal := time.Local + time.Local = local + t.Cleanup(func() { time.Local = originalLocal }) +} diff --git a/registry/time/helpers_test.go b/registry/time/helpers_test.go new file mode 100644 index 0000000..30cf923 --- /dev/null +++ b/registry/time/helpers_test.go @@ -0,0 +1,58 @@ +package time + +import ( + "testing" + goTime "time" + + "github.com/stretchr/testify/assert" +) + +func TestComputeTimeFromFormat(t *testing.T) { + now := goTime.Now() + + tests := []struct { + name string + date any + want goTime.Time + }{ + { + name: "time.Time", + date: now, + want: now, + }, + { + name: "*time.Time", + date: &now, + want: now, + }, + { + name: "int64", + date: int64(1643723900), + want: goTime.Unix(1643723900, 0), + }, + { + name: "int", + date: 1643723900, + want: goTime.Unix(int64(1643723900), 0), + }, + { + name: "int32", + date: int32(1643723900), + want: goTime.Unix(int64(1643723900), 0), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := computeTimeFromFormat(tt.date) + assert.Equal(t, tt.want, got) + }) + } + + t.Run("invalid format", func(t *testing.T) { + // computeTimeFromFormat generates the current time if the format is invalid + got := computeTimeFromFormat("invalid date") + + // so we can only guess the date is close to the current time + assert.Less(t, goTime.Since(got), 10*goTime.Millisecond) + }) +} From 5ac78cfa4f94a646d2e87784e8fc64eb35fcabd6 Mon Sep 17 00:00:00 2001 From: ccoVeille <3875889+ccoVeille@users.noreply.github.com> Date: Sat, 30 Nov 2024 09:49:12 +0100 Subject: [PATCH 2/9] fix(date): timezone issue with date conversion --- registry/conversion/functions_test.go | 176 ++++++++++++++++++++++---- 1 file changed, 149 insertions(+), 27 deletions(-) diff --git a/registry/conversion/functions_test.go b/registry/conversion/functions_test.go index fb040b0..9cec3b7 100644 --- a/registry/conversion/functions_test.go +++ b/registry/conversion/functions_test.go @@ -3,9 +3,13 @@ package conversion_test import ( "fmt" "testing" + goTime "time" + + "github.com/stretchr/testify/require" "github.com/go-sprout/sprout/pesticide" "github.com/go-sprout/sprout/registry/conversion" + "github.com/go-sprout/sprout/registry/time" ) func TestToBool(t *testing.T) { @@ -120,34 +124,152 @@ func TestToString(t *testing.T) { } func TestToDate(t *testing.T) { - tc := []pesticide.TestCase{ - { - Name: "TestDate", - Input: `{{$v := toDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, - Data: map[string]any{"V": "2024-05-09"}, - ExpectedOutput: "time.Time-2024-05-09 00:00:00 +0000 UTC", - }, - { - Name: "TestDate", - Input: `{{$v := toDate "2006-01-02 15:04:05 MST" .V }}{{typeOf $v}}-{{$v}}`, - Data: map[string]any{"V": "2024-05-09 00:00:00 UTC"}, - ExpectedOutput: "time.Time-2024-05-09 00:00:00 +0000 UTC", - }, - { - Name: "TestInvalidValue", - Input: `{{$v := toDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, - Data: map[string]any{"V": ""}, - ExpectedErr: "cannot parse \"\" as \"2006\"", - }, - { - Name: "TestInvalidLayout", - Input: `{{$v := toDate "invalid" .V }}{{typeOf $v}}-{{$v}}`, - Data: map[string]any{"V": "2024-05-09"}, - ExpectedErr: "cannot parse \"2024-05-09\" as \"invalid\"", - }, - } + t.Run("dates with numeric timezone offset", func(t *testing.T) { + // Please refer to https://pkg.go.dev/time#Parse + // When parsing a time with a zone offset like -0700, + // if the offset corresponds to a time zone used by the current location (Local), + // then Parse uses that location and zone in the returned time. + // Otherwise it records the time as being in a fabricated location with time fixed at the given zone offset. - pesticide.RunTestCases(t, conversion.NewRegistry(), tc) + // So we have to temporarily force time.Local a known timezone + // to validate the behavior of toDate function + local, err := time.LoadLocation("America/New_York") + require.NoError(t, err) + + // temporarily force time.Local to New York + rtime.ForceTimeLocal(t, local) + + tc := []pesticide.TestCase{ + { + Name: "date with UTC timezone", + Input: `{{$v := toDate "2006-01-02 15:04:05 -0700" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 00:00:00 +0000"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 +0000 +0000", + }, + { + Name: "date with non-UTC timezone equal to local timezone", + Input: `{{$v := toDate "2006-01-02 15:04:05 -0700" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 00:00:00 -0400"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 -0400 EDT", + }, + { + Name: "date with non-UTC timezone different than local", + Input: `{{$v := toDate "2006-01-02 15:04:05 -0700" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 00:00:00 -0700"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 -0700 -0700", + }, + } + + pesticide.RunTestCases(t, conversion.NewRegistry(), tc) + }) + + t.Run("dates with abbreviated timezone", func(t *testing.T) { + // Please refer to https://pkg.go.dev/time#Parse + // When parsing a time with a zone abbreviation like MST, + // if the zone abbreviation has a defined offset in the current location, + // then that offset is used. + // The zone abbreviation "UTC" is recognized as UTC regardless of location. + // To avoid such problems, prefer time layouts that use a numeric zone offset, or use ParseInLocation. + + // So we have to temporarily force time.Local a known timezone + // to validate the behavior of toDate function + local, err := time.LoadLocation("America/New_York") + require.NoError(t, err) + + // temporarily force time.Local to New York + rtime.ForceTimeLocal(t, local) + + tc := []pesticide.TestCase{ + { + Name: "date with UTC timezone", + Input: `{{$v := toDate "2006-01-02 15:04:05 MST" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 00:00:00 UTC"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 +0000 UTC", + }, + { + Name: "date with non-UTC timezone equal to local timezone", + Input: `{{$v := toDate "2006-01-02 15:04:05 MST" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 00:00:00 EDT"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 -0400 EDT", + }, + { + Name: "date with non-UTC timezone different than local", + Input: `{{$v := toDate "2006-01-02 15:04:05 MST" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 00:00:00 MST"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 +0000 MST", + }, + } + + pesticide.RunTestCases(t, conversion.NewRegistry(), tc) + }) + + t.Run("dates without timezone (local time should be assumed)", func(t *testing.T) { + t.Run("UTC", func(t *testing.T) { + // temporarily force time.Local to UTC + time.ForceTimeLocal(t, goTime.UTC) + + tc := []pesticide.TestCase{ + { + Name: "short date", + Input: `{{$v := toDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 +0000 UTC", + }, + { + Name: "datetime ", + Input: `{{$v := toDate "2006-01-02 15:04:05" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 01:02:03"}, + ExpectedOutput: "time.Time-2024-05-09 01:02:03 +0000 UTC", + }, + } + + pesticide.RunTestCases(t, conversion.NewRegistry(), tc) + }) + + t.Run("New York timezone", func(t *testing.T) { + local, err := goTime.LoadLocation("America/New_York") + require.NoError(t, err) + + // temporarily force time.Local to New York + time.ForceTimeLocal(t, local) + + tc := []pesticide.TestCase{ + { + Name: "short date", + Input: `{{$v := toDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09"}, + ExpectedOutput: "time.Time-2024-05-09 00:00:00 -0400 EDT", + }, + { + Name: "datetime ", + Input: `{{$v := toDate "2006-01-02 15:04:05" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09 01:02:03"}, + ExpectedOutput: "time.Time-2024-05-09 01:02:03 -0400 EDT", + }, + } + + pesticide.RunTestCases(t, conversion.NewRegistry(), tc) + }) + }) + + t.Run("invalid layout", func(t *testing.T) { + tc := []pesticide.TestCase{ + { + Name: "TestInvalidValue", + Input: `{{$v := toDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": ""}, + ExpectedErr: `cannot parse "" as "2006"`, + }, + { + Name: "TestInvalidLayout", + Input: `{{$v := toDate "invalid" .V }}{{typeOf $v}}-{{$v}}`, + Data: map[string]any{"V": "2024-05-09"}, + ExpectedErr: `cannot parse "2024-05-09" as "invalid"`, + }, + } + + pesticide.RunTestCases(t, conversion.NewRegistry(), tc) + }) } func TestToLocalDate(t *testing.T) { From f096657791159381e535163026a929980efe7d91 Mon Sep 17 00:00:00 2001 From: ccoVeille <3875889+ccoVeille@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:46:44 +0100 Subject: [PATCH 3/9] chore: refactor code to do not use time alias for registry/time --- benchmarks/comparison_test.go | 10 ++-- registry/conversion/functions_test.go | 10 ++-- registry/time/functions_test.go | 86 +++++++++++++-------------- registry/time/helpers_test.go | 14 ++--- 4 files changed, 60 insertions(+), 60 deletions(-) diff --git a/benchmarks/comparison_test.go b/benchmarks/comparison_test.go index c9de91f..7013fff 100644 --- a/benchmarks/comparison_test.go +++ b/benchmarks/comparison_test.go @@ -9,7 +9,7 @@ import ( "sync" "testing" "text/template" - gotime "time" + "time" "github.com/Masterminds/sprig/v3" "github.com/go-sprout/sprout" @@ -29,7 +29,7 @@ import ( "github.com/go-sprout/sprout/registry/slices" "github.com/go-sprout/sprout/registry/std" "github.com/go-sprout/sprout/registry/strings" - "github.com/go-sprout/sprout/registry/time" + rtime "github.com/go-sprout/sprout/registry/time" "github.com/go-sprout/sprout/registry/uniqueid" "github.com/go-sprout/sprout/sprigin" "github.com/stretchr/testify/assert" @@ -48,8 +48,8 @@ var data = map[string]any{ "object": struct{ Name string }{"example object"}, "func": func() string { return "example function" }, "error": fmt.Errorf("example error"), - "time": gotime.Now(), - "duration": 5 * gotime.Second, + "time": time.Now(), + "duration": 5 * time.Second, "channel": make(chan any), "json": `{"foo": "bar"}`, "yaml": "foo: bar", @@ -136,7 +136,7 @@ func sproutBench(templatePath string) { semver.NewRegistry(), backward.NewRegistry(), reflect.NewRegistry(), - time.NewRegistry(), + rtime.NewRegistry(), strings.NewRegistry(), random.NewRegistry(), checksum.NewRegistry(), diff --git a/registry/conversion/functions_test.go b/registry/conversion/functions_test.go index 9cec3b7..e936094 100644 --- a/registry/conversion/functions_test.go +++ b/registry/conversion/functions_test.go @@ -3,13 +3,13 @@ package conversion_test import ( "fmt" "testing" - goTime "time" + "time" "github.com/stretchr/testify/require" "github.com/go-sprout/sprout/pesticide" "github.com/go-sprout/sprout/registry/conversion" - "github.com/go-sprout/sprout/registry/time" + rtime "github.com/go-sprout/sprout/registry/time" ) func TestToBool(t *testing.T) { @@ -206,7 +206,7 @@ func TestToDate(t *testing.T) { t.Run("dates without timezone (local time should be assumed)", func(t *testing.T) { t.Run("UTC", func(t *testing.T) { // temporarily force time.Local to UTC - time.ForceTimeLocal(t, goTime.UTC) + rtime.ForceTimeLocal(t, time.UTC) tc := []pesticide.TestCase{ { @@ -227,11 +227,11 @@ func TestToDate(t *testing.T) { }) t.Run("New York timezone", func(t *testing.T) { - local, err := goTime.LoadLocation("America/New_York") + local, err := time.LoadLocation("America/New_York") require.NoError(t, err) // temporarily force time.Local to New York - time.ForceTimeLocal(t, local) + rtime.ForceTimeLocal(t, local) tc := []pesticide.TestCase{ { diff --git a/registry/time/functions_test.go b/registry/time/functions_test.go index c0abc57..dd29cdf 100644 --- a/registry/time/functions_test.go +++ b/registry/time/functions_test.go @@ -2,41 +2,41 @@ package time_test import ( "testing" - goTime "time" + "time" "github.com/stretchr/testify/require" "github.com/go-sprout/sprout/pesticide" - "github.com/go-sprout/sprout/registry/time" + rtime "github.com/go-sprout/sprout/registry/time" ) func TestDate(t *testing.T) { t.Run("UTC", func(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, time.UTC) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest}}, {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": &timeTest}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) }) t.Run("New York timezone", func(t *testing.T) { - local, err := goTime.LoadLocation("America/New_York") + local, err := time.LoadLocation("America/New_York") require.NoError(t, err) - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, local) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, local) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": timeTest}}, {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) }) t.Run("New York offset", func(t *testing.T) { - timeTest, err := goTime.Parse("02 Jan 06 15:04 -0700", "07 May 24 15:04 -0400") + timeTest, err := time.Parse("02 Jan 06 15:04 -0700", "07 May 24 15:04 -0400") require.NoError(t, err) tc := []pesticide.TestCase{ @@ -44,61 +44,61 @@ func TestDate(t *testing.T) { {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) }) t.Run("New York timezone", func(t *testing.T) { - local, err := goTime.LoadLocation("America/New_York") + local, err := time.LoadLocation("America/New_York") require.NoError(t, err) - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, local) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, local) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": timeTest}}, {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) }) t.Run("unixtime", func(t *testing.T) { t.Run("UTC", func(t *testing.T) { // temporarily force time.Local to UTC - time.ForceTimeLocal(t, goTime.UTC) + rtime.ForceTimeLocal(t, time.UTC) // here we are simulating a [gotime.Now] - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, time.UTC) tc := []pesticide.TestCase{ {Name: "TestTimeObjectUnix", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest.Unix()}}, {Name: "TestTimeObjectUnixInt", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": int(timeTest.Unix())}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) }) t.Run("New York timezone", func(t *testing.T) { - local, err := goTime.LoadLocation("America/New_York") + local, err := time.LoadLocation("America/New_York") require.NoError(t, err) // temporarily force time.Local to New York - time.ForceTimeLocal(t, local) + rtime.ForceTimeLocal(t, local) // here we are simulating a [gotime.Now] call - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, local) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, local) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": timeTest}}, {Name: "TestTimeObjectPointer", Input: `{{ .V | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 15:04 -0400", Data: map[string]any{"V": &timeTest}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) }) }) } func TestDateInZone(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, time.UTC) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest}}, @@ -106,11 +106,11 @@ func TestDateInZone(t *testing.T) { {Name: "TestTimeObjectUnix", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": timeTest.Unix()}}, {Name: "TestTimeObjectUnixInt", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": int(timeTest.Unix())}}, {Name: "TestTimeObjectUnixInt", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: "07 May 24 15:04 +0000", Data: map[string]any{"V": int32(timeTest.Unix())}}, - {Name: "TestWithInvalidInput", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: goTime.Now().UTC().Format("02 Jan 06 15:04 -0700"), Data: map[string]any{"V": "invalid"}}, + {Name: "TestWithInvalidInput", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "UTC" }}`, ExpectedOutput: time.Now().UTC().Format("02 Jan 06 15:04 -0700"), Data: map[string]any{"V": "invalid"}}, {Name: "TestWithInvalidZone", Input: `{{ dateInZone "02 Jan 06 15:04 -0700" .V "invalid" }}`, ExpectedErr: "unknown time zone invalid", Data: map[string]any{"V": timeTest}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestDuration(t *testing.T) { @@ -124,11 +124,11 @@ func TestDuration(t *testing.T) { {Name: "TestDurationWithInvalidType", Input: `{{ .V | duration }}`, ExpectedOutput: "0s", Data: map[string]any{"V": make(chan int)}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestDateAgo(t *testing.T) { - timeTest := goTime.Now().Add(-goTime.Hour * 24) + timeTest := time.Now().Add(-time.Hour * 24) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ .V | dateAgo | substr 0 5 }}`, ExpectedOutput: "24h0m", Data: map[string]any{"V": timeTest}}, @@ -139,29 +139,29 @@ func TestDateAgo(t *testing.T) { {Name: "TestWithInvalidInput", Input: `{{ .V | dateAgo }}`, ExpectedOutput: "0s", Data: map[string]any{"V": "invalid"}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestNow(t *testing.T) { tc := []pesticide.TestCase{ - {Name: "TestNow", Input: `{{ now | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: goTime.Now().Format("02 Jan 06 15:04 -0700")}, + {Name: "TestNow", Input: `{{ now | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: time.Now().Format("02 Jan 06 15:04 -0700")}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestUnixEpoch(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, time.UTC) tc := []pesticide.TestCase{ {Name: "TestUnixEpoch", Input: `{{ .V | unixEpoch }}`, ExpectedOutput: "1715094245", Data: map[string]any{"V": timeTest}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestDateModify(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, time.UTC) tc := []pesticide.TestCase{ {Name: "AddOneHour", Input: `{{ .V | mustDateModify "1h" | date "02 Jan 06 15:04 -0700" }}`, ExpectedOutput: "07 May 24 16:04 +0000", Data: map[string]any{"V": timeTest}}, @@ -173,7 +173,7 @@ func TestDateModify(t *testing.T) { {Name: "WithInvalidInput", Input: `{{ .V | mustDateModify "zz" }}`, Data: map[string]any{"V": timeTest}, ExpectedErr: "invalid duration"}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestDurationRound(t *testing.T) { @@ -182,20 +182,20 @@ func TestDurationRound(t *testing.T) { {Name: "RoundToHour", Input: `{{ .V | durationRound }}`, ExpectedOutput: "2h", Data: map[string]any{"V": "2h5s"}}, {Name: "RoundToDay", Input: `{{ .V | durationRound }}`, ExpectedOutput: "1d", Data: map[string]any{"V": "24h5s"}}, {Name: "RoundToMonth", Input: `{{ .V | durationRound }}`, ExpectedOutput: "3mo", Data: map[string]any{"V": "2400h5s"}}, - {Name: "RoundToMinute", Input: `{{ .V | durationRound }}`, ExpectedOutput: "45m", Data: map[string]any{"V": int64(45*goTime.Minute + 30*goTime.Second)}}, - {Name: "RoundToSecond", Input: `{{ .V | durationRound }}`, ExpectedOutput: "1s", Data: map[string]any{"V": int64(1*goTime.Second + 500*goTime.Millisecond)}}, - {Name: "RoundaDuration", Input: `{{ .V | durationRound }}`, ExpectedOutput: "2s", Data: map[string]any{"V": 2 * goTime.Second}}, - {Name: "RoundToYear", Input: `{{ .V | durationRound }}`, ExpectedOutput: "1y", Data: map[string]any{"V": int64(365*24*goTime.Hour + 12*goTime.Hour)}}, - {Name: "RoundToYearNegative", Input: `{{ .V | durationRound }}`, ExpectedOutput: "1y", Data: map[string]any{"V": goTime.Now().Add(-365*24*goTime.Hour - 72*goTime.Hour)}}, + {Name: "RoundToMinute", Input: `{{ .V | durationRound }}`, ExpectedOutput: "45m", Data: map[string]any{"V": int64(45*time.Minute + 30*time.Second)}}, + {Name: "RoundToSecond", Input: `{{ .V | durationRound }}`, ExpectedOutput: "1s", Data: map[string]any{"V": int64(1*time.Second + 500*time.Millisecond)}}, + {Name: "RoundaDuration", Input: `{{ .V | durationRound }}`, ExpectedOutput: "2s", Data: map[string]any{"V": 2 * time.Second}}, + {Name: "RoundToYear", Input: `{{ .V | durationRound }}`, ExpectedOutput: "1y", Data: map[string]any{"V": int64(365*24*time.Hour + 12*time.Hour)}}, + {Name: "RoundToYearNegative", Input: `{{ .V | durationRound }}`, ExpectedOutput: "1y", Data: map[string]any{"V": time.Now().Add(-365*24*time.Hour - 72*time.Hour)}}, {Name: "InvalidInput", Input: `{{ .V | durationRound }}`, ExpectedOutput: "0s", Data: map[string]any{"V": make(chan int)}}, {Name: "RoundToHourNegative", Input: `{{ .V | durationRound }}`, ExpectedOutput: "-1h", Data: map[string]any{"V": "-1h01s"}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestHtmlDate(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, time.UTC) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ .V | htmlDate }}`, ExpectedOutput: "2024-05-07", Data: map[string]any{"V": timeTest}}, @@ -204,14 +204,14 @@ func TestHtmlDate(t *testing.T) { {Name: "TestTimeObjectUnixInt", Input: `{{ .V | htmlDate }}`, ExpectedOutput: "2024-05-07", Data: map[string]any{"V": int(timeTest.Unix())}}, {Name: "TestTimeObjectUnixInt32", Input: `{{ .V | htmlDate }}`, ExpectedOutput: "2024-05-07", Data: map[string]any{"V": int32(timeTest.Unix())}}, {Name: "TestZeroValue", Input: `{{ .V | htmlDate }}`, ExpectedOutput: "1970-01-01", Data: map[string]any{"V": 0}}, - {Name: "TestWithInvalidInput", Input: `{{ .V | htmlDate }}`, ExpectedOutput: goTime.Now().Format("2006-01-02"), Data: map[string]any{"V": make(chan int)}}, + {Name: "TestWithInvalidInput", Input: `{{ .V | htmlDate }}`, ExpectedOutput: time.Now().Format("2006-01-02"), Data: map[string]any{"V": make(chan int)}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } func TestHtmlDateInZone(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + timeTest := time.Date(2024, 5, 7, 15, 4, 5, 0, time.UTC) tc := []pesticide.TestCase{ {Name: "TestTimeObject", Input: `{{ htmlDateInZone .V "UTC" }}`, ExpectedOutput: "2024-05-07", Data: map[string]any{"V": timeTest}}, @@ -219,8 +219,8 @@ func TestHtmlDateInZone(t *testing.T) { {Name: "TestTimeObjectUnix", Input: `{{ htmlDateInZone .V "UTC" }}`, ExpectedOutput: "2024-05-07", Data: map[string]any{"V": timeTest.Unix()}}, {Name: "TestTimeObjectUnixInt", Input: `{{ htmlDateInZone .V "UTC" }}`, ExpectedOutput: "2024-05-07", Data: map[string]any{"V": int(timeTest.Unix())}}, {Name: "TestTimeObjectUnixInt32", Input: `{{ htmlDateInZone .V "UTC" }}`, ExpectedOutput: "2024-05-07", Data: map[string]any{"V": int32(timeTest.Unix())}}, - {Name: "TestWithInvalidInput", Input: `{{ htmlDateInZone .V "UTC" }}`, ExpectedOutput: goTime.Now().Format("2006-01-02"), Data: map[string]any{"V": make(chan int)}}, + {Name: "TestWithInvalidInput", Input: `{{ htmlDateInZone .V "UTC" }}`, ExpectedOutput: time.Now().Format("2006-01-02"), Data: map[string]any{"V": make(chan int)}}, } - pesticide.RunTestCases(t, time.NewRegistry(), tc) + pesticide.RunTestCases(t, rtime.NewRegistry(), tc) } diff --git a/registry/time/helpers_test.go b/registry/time/helpers_test.go index 30cf923..c5ec12f 100644 --- a/registry/time/helpers_test.go +++ b/registry/time/helpers_test.go @@ -2,18 +2,18 @@ package time import ( "testing" - goTime "time" + "time" "github.com/stretchr/testify/assert" ) func TestComputeTimeFromFormat(t *testing.T) { - now := goTime.Now() + now := time.Now() tests := []struct { name string date any - want goTime.Time + want time.Time }{ { name: "time.Time", @@ -28,17 +28,17 @@ func TestComputeTimeFromFormat(t *testing.T) { { name: "int64", date: int64(1643723900), - want: goTime.Unix(1643723900, 0), + want: time.Unix(1643723900, 0), }, { name: "int", date: 1643723900, - want: goTime.Unix(int64(1643723900), 0), + want: time.Unix(int64(1643723900), 0), }, { name: "int32", date: int32(1643723900), - want: goTime.Unix(int64(1643723900), 0), + want: time.Unix(int64(1643723900), 0), }, } for _, tt := range tests { @@ -53,6 +53,6 @@ func TestComputeTimeFromFormat(t *testing.T) { got := computeTimeFromFormat("invalid date") // so we can only guess the date is close to the current time - assert.Less(t, goTime.Since(got), 10*goTime.Millisecond) + assert.Less(t, time.Since(got), 10*time.Millisecond) }) } From 2a63fd8f0a8ff72a95f205ba8560ce98b3f9ed74 Mon Sep 17 00:00:00 2001 From: ccoVeille <3875889+ccoVeille@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:52:56 +0100 Subject: [PATCH 4/9] chore: refactor to do not use fmt as a variable do not use `fmt`, that is a common import name for Go std `fmt` package Use layout like in the Go package. I faced issues, and frustration by trying to do a simple fmt.Println for debugging purpose. --- docs/registries/conversion.md | 2 +- docs/registries/time.md | 6 +++--- registry/conversion/functions.go | 12 ++++++------ registry/time/functions.go | 24 ++++++++++++------------ 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/registries/conversion.md b/docs/registries/conversion.md index 5846fa6..1cb0bf7 100644 --- a/docs/registries/conversion.md +++ b/docs/registries/conversion.md @@ -192,7 +192,7 @@ See more about Golang Layout on the [official documentation](https://go.dev/src/ toLocalDate converts a string to a time.Time object based on a format specification and the local timezone. -
Name | Value | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Signature |
|