diff --git a/registry/time/functions.go b/registry/time/functions.go index a3f63da..4ccc629 100644 --- a/registry/time/functions.go +++ b/registry/time/functions.go @@ -22,7 +22,12 @@ import ( // // {{ "2023-05-04T15:04:05Z" | date "Jan 2, 2006" }} // Output: "May 4, 2023" 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. @@ -44,28 +49,32 @@ func (tr *TimeRegistry) Date(fmt string, date any) (string, error) { // // TODO: Change signature func (tr *TimeRegistry) DateInZone(fmt string, date any, zone string) (string, error) { - var t time.Time + t := computeTimeFromFormat(date) + loc, err := time.LoadLocation(zone) + if err != nil { + return t.In(time.UTC).Format(fmt), err + } + + return t.In(loc).Format(fmt), nil +} + +// computeTimeFromFormat returns a time.Time object from the given date. +func computeTimeFromFormat(date any) time.Time { switch date := date.(type) { - default: - t = time.Now() case time.Time: - t = date + return date case *time.Time: - t = *date + return *date case int64: - t = time.Unix(date, 0) + return time.Unix(date, 0) case int: - t = time.Unix(int64(date), 0) + return time.Unix(int64(date), 0) case int32: - t = time.Unix(int64(date), 0) + return time.Unix(int64(date), 0) } - loc, err := time.LoadLocation(zone) - if err != nil { - return t.In(time.UTC).Format(fmt), err - } - - return t.In(loc).Format(fmt), nil + // otherwise, fallback to the current time, in current timezone + return time.Now() } // Duration converts seconds into a human-readable duration string. diff --git a/registry/time/functions_test.go b/registry/time/functions_test.go index 70430a3..767f0cb 100644 --- a/registry/time/functions_test.go +++ b/registry/time/functions_test.go @@ -8,17 +8,83 @@ import ( "github.com/go-sprout/sprout/registry/time" ) -func TestDate(t *testing.T) { - timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) +// forceTimeLocal temporarily sets [time.Local] for test purpose +func forceTimeLocal(t *testing.T, local *goTime.Location) { + t.Helper() - 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())}}, - } + originalLocal := goTime.Local + goTime.Local = local + t.Cleanup(func() { goTime.Local = originalLocal }) +} - pesticide.RunTestCases(t, time.NewRegistry(), tc) +func TestDate(t *testing.T) { + 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}}, + } + + pesticide.RunTestCases(t, time.NewRegistry(), tc) + }) + + t.Run("New York timezone", func(t *testing.T) { + local, err := goTime.LoadLocation("America/New_York") + if err != nil { + t.Fatal(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") + if err != nil { + t.Fatal(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") + if err != nil { + t.Fatal(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) { + timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC) + + // temporarily force UTC + forceTimeLocal(t, 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) + }) } func TestDateInZone(t *testing.T) { @@ -30,7 +96,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}}, }