Signature | ToLocalDate(fmt, timezone, str string) (time.Time, error)
+Name | Value |
---|
Signature | ToLocalDate(layout, timezone, value string) (time.Time, error)
|
{% tabs %}
diff --git a/docs/registries/time.md b/docs/registries/time.md
index 92a6194..1b703d4 100644
--- a/docs/registries/time.md
+++ b/docs/registries/time.md
@@ -19,7 +19,7 @@ import "github.com/go-sprout/sprout/registry/time"
The function formats a given date or the current time into a specified format string.
-Name | Value |
---|
Signature | Date(fmt string, date any) (string, error)
+Name | Value |
---|
Signature | Date(layout string, date any) (string, error)
|
{% tabs %}
@@ -34,7 +34,7 @@ The function formats a given date or the current time into a specified format st
The function formats a given date or the current time into a specified format string for a specified timezone.
-Name | Value |
---|
Signature | DateInZone(fmt string, date any, zone string) (string, error)
+Name | Value |
---|
Signature | DateInZone(layout string, date any, zone string) (string, error)
|
{% tabs %}
@@ -121,7 +121,7 @@ The function returns the Unix epoch timestamp for a given date.
The function adjusts a given date by a specified duration, returning the modified date. If the duration format is incorrect, it returns the original date without any changes, in case of must version, an error is returned.
-Name | Value |
---|
Signature | DateModify(fmt string, date time.Time) (time.Time, error)
+Name | Value |
---|
Signature | DateModify(layout string, date time.Time) (time.Time, error)
|
{% tabs %}
diff --git a/handler.go b/handler.go
index f873b58..5757e7f 100644
--- a/handler.go
+++ b/handler.go
@@ -3,7 +3,7 @@ package sprout
import (
"log/slog"
"slices"
- gostrings "strings"
+ "strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
@@ -259,7 +259,7 @@ func safeFuncName(name string) string {
return ""
}
- var b gostrings.Builder
+ var b strings.Builder
b.Grow(len(name) + 4)
b.WriteString("safe")
diff --git a/pesticide/time_test_helpers.go b/pesticide/time_test_helpers.go
new file mode 100644
index 0000000..df38288
--- /dev/null
+++ b/pesticide/time_test_helpers.go
@@ -0,0 +1,15 @@
+package pesticide
+
+import (
+ "testing"
+ "time"
+)
+
+// 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/conversion/functions.go b/registry/conversion/functions.go
index ffac425..c488813 100644
--- a/registry/conversion/functions.go
+++ b/registry/conversion/functions.go
@@ -170,7 +170,7 @@ func (cr *ConversionRegistry) ToString(value any) string {
//
// Parameters:
//
-// fmt string - the date format string.
+// layout string - the date format string.
// value string - the date string to parse.
//
// Returns:
@@ -181,8 +181,8 @@ func (cr *ConversionRegistry) ToString(value any) string {
// For an example of this function in a Go template, refer to [Sprout Documentation: toDate].
//
// [Sprout Documentation: toDate]: https://docs.atom.codes/sprout/registries/conversion#todate
-func (cr *ConversionRegistry) ToDate(fmt, value string) (time.Time, error) {
- return time.ParseInLocation(fmt, value, time.Local)
+func (cr *ConversionRegistry) ToDate(layout, value string) (time.Time, error) {
+ return time.ParseInLocation(layout, value, time.Local)
}
// ToLocalDate converts a string to a time.Time object based on a format specification
@@ -190,7 +190,7 @@ func (cr *ConversionRegistry) ToDate(fmt, value string) (time.Time, error) {
//
// Parameters:
//
-// fmt string - the date format string.
+// layout string - the date format string.
// value string - the date string to parse.
//
// Returns:
@@ -201,13 +201,13 @@ func (cr *ConversionRegistry) ToDate(fmt, value string) (time.Time, error) {
// For an example of this function in a Go template, refer to [Sprout Documentation: toLocalDate].
//
// [Sprout Documentation: toLocalDate]: https://docs.atom.codes/sprout/registries/conversion#tolocaldate
-func (cr *ConversionRegistry) ToLocalDate(fmt, timezone, value string) (time.Time, error) {
+func (cr *ConversionRegistry) ToLocalDate(layout, timezone, value string) (time.Time, error) {
location, err := time.LoadLocation(timezone)
if err != nil {
return time.Time{}, err
}
- return time.ParseInLocation(fmt, value, location)
+ return time.ParseInLocation(layout, value, location)
}
// ToDuration converts a value to a time.Duration.
diff --git a/registry/conversion/functions_test.go b/registry/conversion/functions_test.go
index fb040b0..b62e83e 100644
--- a/registry/conversion/functions_test.go
+++ b/registry/conversion/functions_test.go
@@ -3,6 +3,9 @@ package conversion_test
import (
"fmt"
"testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
"github.com/go-sprout/sprout/pesticide"
"github.com/go-sprout/sprout/registry/conversion"
@@ -120,34 +123,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
+ pesticide.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
+ pesticide.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
+ pesticide.ForceTimeLocal(t, time.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 := time.LoadLocation("America/New_York")
+ require.NoError(t, err)
+
+ // temporarily force time.Local to New York
+ pesticide.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) {
diff --git a/registry/time/functions.go b/registry/time/functions.go
index 7e0ad2f..6b846e0 100644
--- a/registry/time/functions.go
+++ b/registry/time/functions.go
@@ -10,8 +10,8 @@ import (
//
// Parameters:
//
-// fmt string - the format string.
-// date any - the date to format or the current time if not a date type.
+// layout string - the format string.
+// date any - the date to format or the current time if not a date type.
//
// Returns:
//
@@ -21,16 +21,21 @@ import (
// For an example of this function in a Go template, refer to [Sprout Documentation: date].
//
// [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")
+func (tr *TimeRegistry) Date(layout string, date any) (string, error) {
+ t := computeTimeFromFormat(date)
+
+ // compute the timezone from the date if it has one
+ loc := time.FixedZone(t.Zone())
+
+ return t.In(loc).Format(layout), nil
}
// DateInZone formats a given date or current time into a specified format string in a specified timezone.
//
// Parameters:
//
-// fmt string - the format string.
-// date any - the date to format, in various acceptable formats.
+// layout string - the format string.
+// date any - the date to format, in various acceptable formats.
// zone string - the timezone name.
//
// Returns:
@@ -41,30 +46,14 @@ func (tr *TimeRegistry) Date(fmt string, date any) (string, error) {
// For an example of this function in a Go template, refer to [Sprout Documentation: dateInZone].
//
// [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)
- }
-
+func (tr *TimeRegistry) DateInZone(layout string, date any, zone string) (string, error) {
+ t := computeTimeFromFormat(date)
loc, err := time.LoadLocation(zone)
if err != nil {
- return t.In(time.UTC).Format(fmt), err
+ return t.In(time.UTC).Format(layout), err
}
- return t.In(loc).Format(fmt), nil
+ return t.In(loc).Format(layout), nil
}
// Duration converts seconds into a human-readable duration string.
@@ -169,7 +158,7 @@ func (tr *TimeRegistry) UnixEpoch(date time.Time) string {
//
// Parameters:
//
-// fmt string - the duration string to add to the date, such as "2h" for two hours.
+// layout string - the duration string to add to the date, such as "2h" for two hours.
// date time.Time - the date to modify.
//
// Returns:
@@ -180,8 +169,8 @@ func (tr *TimeRegistry) UnixEpoch(date time.Time) string {
// For an example of this function in a Go template, refer to [Sprout Documentation: dateModify].
//
// [Sprout Documentation: dateModify]: https://docs.atom.codes/sprout/registries/time#datemodify
-func (tr *TimeRegistry) DateModify(fmt string, date time.Time) (time.Time, error) {
- d, err := time.ParseDuration(fmt)
+func (tr *TimeRegistry) DateModify(layout string, date time.Time) (time.Time, error) {
+ d, err := time.ParseDuration(layout)
if err != nil {
return time.Time{}, err
}
diff --git a/registry/time/functions_test.go b/registry/time/functions_test.go
index 70430a3..76faef7 100644
--- a/registry/time/functions_test.go
+++ b/registry/time/functions_test.go
@@ -2,27 +2,103 @@ 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) {
- timeTest := goTime.Date(2024, 5, 7, 15, 4, 5, 0, goTime.UTC)
+ t.Run("UTC", func(t *testing.T) {
+ 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}},
+ }
- 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, rtime.NewRegistry(), tc)
+ })
+
+ t.Run("New York timezone", func(t *testing.T) {
+ local, err := time.LoadLocation("America/New_York")
+ require.NoError(t, err)
+
+ 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, rtime.NewRegistry(), tc)
+ })
+
+ t.Run("New York offset", func(t *testing.T) {
+ timeTest, err := time.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, rtime.NewRegistry(), tc)
+ })
+
+ t.Run("New York timezone", func(t *testing.T) {
+ local, err := time.LoadLocation("America/New_York")
+ require.NoError(t, err)
+
+ 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, rtime.NewRegistry(), tc)
+ })
+
+ t.Run("unixtime", func(t *testing.T) {
+ t.Run("UTC", func(t *testing.T) {
+ // temporarily force time.Local to UTC
+ pesticide.ForceTimeLocal(t, time.UTC)
+
+ // here we are simulating a [gotime.Now]
+ 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, rtime.NewRegistry(), tc)
+ })
+
+ t.Run("New York timezone", func(t *testing.T) {
+ local, err := time.LoadLocation("America/New_York")
+ require.NoError(t, err)
+
+ // temporarily force time.Local to New York
+ pesticide.ForceTimeLocal(t, local)
+
+ // here we are simulating a [gotime.Now] call
+ 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}},
@@ -30,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().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) {
@@ -48,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}},
@@ -63,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}},
@@ -97,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) {
@@ -106,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}},
@@ -128,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}},
@@ -143,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.go b/registry/time/helpers.go
new file mode 100644
index 0000000..36317c9
--- /dev/null
+++ b/registry/time/helpers.go
@@ -0,0 +1,24 @@
+package time
+
+import (
+ "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()
+}
diff --git a/registry/time/helpers_test.go b/registry/time/helpers_test.go
new file mode 100644
index 0000000..c5ec12f
--- /dev/null
+++ b/registry/time/helpers_test.go
@@ -0,0 +1,58 @@
+package time
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestComputeTimeFromFormat(t *testing.T) {
+ now := time.Now()
+
+ tests := []struct {
+ name string
+ date any
+ want time.Time
+ }{
+ {
+ name: "time.Time",
+ date: now,
+ want: now,
+ },
+ {
+ name: "*time.Time",
+ date: &now,
+ want: now,
+ },
+ {
+ name: "int64",
+ date: int64(1643723900),
+ want: time.Unix(1643723900, 0),
+ },
+ {
+ name: "int",
+ date: 1643723900,
+ want: time.Unix(int64(1643723900), 0),
+ },
+ {
+ name: "int32",
+ date: int32(1643723900),
+ want: time.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, time.Since(got), 10*time.Millisecond)
+ })
+}
diff --git a/sprigin/sprig_backward_compatibility.go b/sprigin/sprig_backward_compatibility.go
index d01ec9c..511ce7e 100644
--- a/sprigin/sprig_backward_compatibility.go
+++ b/sprigin/sprig_backward_compatibility.go
@@ -3,7 +3,7 @@ package sprigin
import (
htemplate "html/template"
"log/slog"
- gostrings "strings"
+ "strings"
ttemplate "text/template"
"github.com/go-sprout/sprout"
@@ -23,7 +23,7 @@ import (
"github.com/go-sprout/sprout/registry/semver"
"github.com/go-sprout/sprout/registry/slices"
"github.com/go-sprout/sprout/registry/std"
- "github.com/go-sprout/sprout/registry/strings"
+ rstrings "github.com/go-sprout/sprout/registry/strings"
"github.com/go-sprout/sprout/registry/time"
"github.com/go-sprout/sprout/registry/uniqueid"
)
@@ -164,7 +164,7 @@ func (sh *SprigHandler) Build() sprout.FunctionMap {
backward.NewRegistry(),
reflect.NewRegistry(),
time.NewRegistry(),
- strings.NewRegistry(),
+ rstrings.NewRegistry(),
random.NewRegistry(),
checksum.NewRegistry(),
conversion.NewRegistry(),
@@ -197,7 +197,7 @@ func (sh *SprigHandler) Build() sprout.FunctionMap {
// BACKWARDS COMPATIBILITY
// Ensure error handling is consistent with sprig functions
for funcName, fn := range sh.funcsMap {
- if !gostrings.HasPrefix(funcName, "must") {
+ if !strings.HasPrefix(funcName, "must") {
sh.funcsMap[funcName] = func(args ...any) (any, error) {
out, _ := runtime.SafeCall(fn, args...)
return out, nil
|
|
|
|