Repository: go-playground/validator
Pull request #641 (2020-07-25 17:48:58)

Add support for time zone validation
Enhances #640 .

**Make sure that you've checked the boxes below before you submit PR:**
- [X] Tests exist or have been written that cover this particular change.

Change Details:

- Add new validator tag called "timeZone" to validate that a value is a valid time zone name
- Add test for this new validator
- Update documentation for this new validator


@go-playground/admins


Patch:
diff --git a/baked_in.go b/baked_in.go
index 36e8057..9708165 100644
--- a/baked_in.go
+++ b/baked_in.go
@@ -174,6 +174,7 @@ var (
 		"lowercase":            isLowercase,
 		"uppercase":            isUppercase,
 		"datetime":             isDatetime,
+		"timezone":             isTimeZone,
 	}
 )
 
@@ -2096,3 +2097,29 @@ func isDatetime(fl FieldLevel) bool {
 
 	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
 }
+
+// isTimeZone is the validation function for validating if the current field's value is a valid time zone string.
+func isTimeZone(fl FieldLevel) bool {
+	field := fl.Field()
+
+	if field.Kind() == reflect.String {
+		// empty value is converted to UTC by time.LoadLocation but disallow it as it is not a valid time zone name
+		if field.String() == "" {
+			return false
+		}
+
+		// Local value is converted to the current system time zone by time.LoadLocation but disallow it as it is not a valid time zone name
+		if strings.ToLower(field.String()) == "local" {
+			return false
+		}
+
+		_, err := time.LoadLocation(field.String())
+		if err != nil {
+			return false
+		}
+
+		return true
+	}
+
+	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+}
diff --git a/doc.go b/doc.go
index 4aba75f..afdb2ab 100644
--- a/doc.go
+++ b/doc.go
@@ -1088,6 +1088,14 @@ Supplied format must match the official Go time format layout as documented in h
 
 	Usage: datetime=2006-01-02
 
+TimeZone
+
+This validates that a string value is a valid time zone based on the time zone database present on the system.
+Although empty value and Local value are allowed by time.LoadLocation golang function, they are not allowed by this validator.
+More information on https://golang.org/pkg/time/#LoadLocation
+
+	Usage: timezone
+
 Alias Validators and Tags
 
 NOTE: When returning an error, the tag returned in "FieldError" will be


Test patch:
diff --git a/validator_test.go b/validator_test.go
index e76a3cd..8e39e0c 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -9201,3 +9201,44 @@ func TestDatetimeValidation(t *testing.T) {
 		_ = validate.Var(2, "datetime")
 	}, "Bad field type int")
 }
+
+func TestTimeZoneValidation(t *testing.T) {
+	tests := []struct {
+		value    string `validate:"timezone"`
+		tag      string
+		expected bool
+	}{
+		// systems may have different time zone database, some systems time zone are case insensitive
+		{"America/New_York", `timezone`, true},
+		{"UTC", `timezone`, true},
+		{"", `timezone`, false},
+		{"Local", `timezone`, false},
+		{"Unknown", `timezone`, false},
+	}
+
+	validate := New()
+
+	for i, test := range tests {
+
+		errs := validate.Var(test.value, test.tag)
+
+		if test.expected {
+			if !IsEqual(errs, nil) {
+				t.Fatalf("Index: %d time zone failed Error: %s", i, errs)
+			}
+		} else {
+			if IsEqual(errs, nil) {
+				t.Fatalf("Index: %d time zone failed Error: %s", i, errs)
+			} else {
+				val := getError(errs, "", "")
+				if val.Tag() != "timezone" {
+					t.Fatalf("Index: %d time zone failed Error: %s", i, errs)
+				}
+			}
+		}
+	}
+
+	PanicMatches(t, func() {
+		_ = validate.Var(2, "timezone")
+	}, "Bad field type int")
+}