diff --git a/validator/validator.go b/validator/validator.go index 3e16f86..757d152 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -6,12 +6,14 @@ package validator import ( "encoding/json" + "fmt" "net" "net/url" "reflect" "regexp" "strconv" "strings" + "time" "unicode" ) @@ -24,7 +26,7 @@ var ( dnsMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$`) emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`) chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`) - chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^[1-9]\d{5}(18|19|20|21|22)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$`) + chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^(\d{17})([0-9]|X|x)$`) chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]") chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`) creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`) @@ -39,6 +41,52 @@ var ( chinaUnionPay *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`) ) +var ( + // Identity card formula + factor = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2} + // ID verification bit + verifyStr = [11]string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"} + // Starting year of ID card + birthStartYear = 1900 + // Province code + provinceKv = map[string]struct{}{ + "11": {}, + "12": {}, + "13": {}, + "14": {}, + "15": {}, + "21": {}, + "22": {}, + "23": {}, + "31": {}, + "32": {}, + "33": {}, + "34": {}, + "35": {}, + "36": {}, + "37": {}, + "41": {}, + "42": {}, + "43": {}, + "44": {}, + "45": {}, + "46": {}, + "50": {}, + "51": {}, + "52": {}, + "53": {}, + "54": {}, + "61": {}, + "62": {}, + "63": {}, + "64": {}, + "65": {}, + //"71": {}, + //"81": {}, + //"82": {}, + } +) + // IsAlpha checks if the string contains only letters (a-zA-Z). // Play: https://go.dev/play/p/7Q5sGOz2izQ func IsAlpha(str string) bool { @@ -120,7 +168,7 @@ func ContainLetter(str string) bool { return letterRegexMatcher.MatchString(str) } -// ContainLetter check if the string contain at least one number. +// ContainNumber check if the string contain at least one number. func ContainNumber(input string) bool { return numberRegexMatcher.MatchString(input) } @@ -228,7 +276,32 @@ func IsChineseMobile(mobileNum string) bool { // IsChineseIdNum check if the string is chinese id card. // Play: https://go.dev/play/p/d8EWhl2UGDF func IsChineseIdNum(id string) bool { - return chineseIdMatcher.MatchString(id) + // All characters should be numbers, and the last digit can be either x or X + if !chineseIdMatcher.MatchString(id) { + return false + } + + // Verify province codes and complete all province codes according to GB/T2260 + _, ok := provinceKv[id[0:2]] + if !ok { + return false + } + + // Verify birthday, must be greater than birthStartYear and less than the current year + birthStr := fmt.Sprintf("%s-%s-%s", id[6:10], id[10:12], id[12:14]) + birthday, err := time.Parse("2006-01-02", birthStr) + if err != nil || birthday.After(time.Now()) || birthday.Year() < birthStartYear { + return false + } + + // Verification code + sum := 0 + for i, c := range id[:17] { + v, _ := strconv.Atoi(string(c)) + sum += v * factor[i] + } + + return verifyStr[sum%11] == strings.ToUpper(id[17:18]) } // ContainChinese check if the string contain mandarin chinese. @@ -384,7 +457,7 @@ func IsGBK(data []byte) bool { return true } -// IsNumberStr check if the value is number(integer, float) or not. +// IsNumber check if the value is number(integer, float) or not. // Play: https://go.dev/play/p/mdJHOAvtsvF func IsNumber(v any) bool { return IsInt(v) || IsFloat(v) diff --git a/validator/validator_example_test.go b/validator/validator_example_test.go index 4056f23..51242f1 100644 --- a/validator/validator_example_test.go +++ b/validator/validator_example_test.go @@ -154,7 +154,7 @@ func ExampleIsChineseMobile() { } func ExampleIsChineseIdNum() { - result1 := IsChineseIdNum("210911192105130715") + result1 := IsChineseIdNum("210911192105130714") result2 := IsChineseIdNum("123456") fmt.Println(result1) diff --git a/validator/validator_test.go b/validator/validator_test.go index d1ea1c1..abe0454 100644 --- a/validator/validator_test.go +++ b/validator/validator_test.go @@ -320,10 +320,13 @@ func TestIsChineseIdNum(t *testing.T) { assert := internal.NewAssert(t, "TestIsChineseIdNum") - assert.Equal(true, IsChineseIdNum("210911192105130715")) - assert.Equal(true, IsChineseIdNum("21091119210513071X")) - assert.Equal(true, IsChineseIdNum("21091119210513071x")) + assert.Equal(true, IsChineseIdNum("210911192105130714")) + assert.Equal(true, IsChineseIdNum("11010519491231002X")) + assert.Equal(true, IsChineseIdNum("11010519491231002x")) assert.Equal(false, IsChineseIdNum("123456")) + assert.Equal(false, IsChineseIdNum("990911192105130714")) + assert.Equal(false, IsChineseIdNum("990911189905130714")) + assert.Equal(false, IsChineseIdNum("210911222205130714")) } func TestIsCreditCard(t *testing.T) {