Skip to content

Commit 3caca36

Browse files
Merge pull request #117 from ErfanMomeniii/main
feat: add StringToTimeLocationHookFunc to convert strings to *time.Location
2 parents 9a861bc + 86ed5b5 commit 3caca36

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

decode_hooks.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,26 @@ func StringToTimeDurationHookFunc() DecodeHookFunc {
183183
}
184184
}
185185

186+
// StringToTimeLocationHookFunc returns a DecodeHookFunc that converts
187+
// strings to *time.Location.
188+
func StringToTimeLocationHookFunc() DecodeHookFunc {
189+
return func(
190+
f reflect.Type,
191+
t reflect.Type,
192+
data any,
193+
) (any, error) {
194+
if f.Kind() != reflect.String {
195+
return data, nil
196+
}
197+
if t != reflect.TypeOf(time.Local) {
198+
return data, nil
199+
}
200+
d, err := time.LoadLocation(data.(string))
201+
202+
return d, wrapTimeParseLocationError(err)
203+
}
204+
}
205+
186206
// StringToURLHookFunc returns a DecodeHookFunc that converts
187207
// strings to *url.URL.
188208
func StringToURLHookFunc() DecodeHookFunc {

decode_hooks_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,35 @@ func TestStringToTimeDurationHookFunc(t *testing.T) {
547547
suite.Run(t)
548548
}
549549

550+
func TestStringToTimeLocationHookFunc(t *testing.T) {
551+
newYork, _ := time.LoadLocation("America/New_York")
552+
london, _ := time.LoadLocation("Europe/London")
553+
tehran, _ := time.LoadLocation("Asia/Tehran")
554+
shanghai, _ := time.LoadLocation("Asia/Shanghai")
555+
556+
suite := decodeHookTestSuite[string, *time.Location]{
557+
fn: StringToTimeLocationHookFunc(),
558+
ok: []decodeHookTestCase[string, *time.Location]{
559+
{"UTC", time.UTC},
560+
{"Local", time.Local},
561+
{"America/New_York", newYork},
562+
{"Europe/London", london},
563+
{"Asia/Tehran", tehran},
564+
{"Asia/Shanghai", shanghai},
565+
},
566+
fail: []decodeHookFailureTestCase[string, *time.Location]{
567+
{"UTC2"}, // Non-existent
568+
{"5s"}, // Duration-like, not a zone
569+
{"Europe\\London"}, // Invalid path separator
570+
{"../etc/passwd"}, // Unsafe path
571+
{"/etc/zoneinfo"}, // Absolute path (rejected by stdlib)
572+
{"Asia\\Tehran"}, // Invalid Windows-style path
573+
},
574+
}
575+
576+
suite.Run(t)
577+
}
578+
550579
func TestStringToURLHookFunc(t *testing.T) {
551580
httpURL, _ := url.Parse("http://example.com")
552581
httpsURL, _ := url.Parse("https://example.com")

errors.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,15 @@ func wrapTimeParseDurationError(err error) error {
230230

231231
return err
232232
}
233+
234+
func wrapTimeParseLocationError(err error) error {
235+
if err == nil {
236+
return nil
237+
}
238+
errMsg := err.Error()
239+
if strings.Contains(errMsg, "unknown time zone") || strings.HasPrefix(errMsg, "time: unknown format") {
240+
return fmt.Errorf("invalid time zone format: %w", err)
241+
}
242+
243+
return err
244+
}

0 commit comments

Comments
 (0)