diff --git a/README.md b/README.md index d5ec407..b66d05c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # algorithm -搜集的大量算法库 + +A large collection of algorithms. diff --git a/chinaid/README.md b/chinaid/README.md new file mode 100644 index 0000000..bec7e6d --- /dev/null +++ b/chinaid/README.md @@ -0,0 +1,3 @@ +# chinaid + +Generate and verify the Chinese identity card number. \ No newline at end of file diff --git a/chinaid/china_id.go b/chinaid/china_id.go new file mode 100644 index 0000000..bd72c40 --- /dev/null +++ b/chinaid/china_id.go @@ -0,0 +1,259 @@ +package chinaid + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "unicode" + "unsafe" +) + +// ErrInvalidChinaID error +var ErrInvalidChinaID = errors.New("invalid Chinese identity card number") + +// ChinaID Chinese identity card number +type ChinaID struct { + whole []byte + body []byte + addr []byte + birth []byte + seq []byte + sex int // 1 male / 0 female + check []byte +} + +// Make18 generates an 18-character length number +func Make18(addr [6]byte, birth [8]byte, seq [3]byte) (*ChinaID, bool) { + body := string(addr[:]) + string(birth[:]) + string(seq[:]) + if !isDigit(body, -1) { + return nil, false + } + c := &ChinaID{ + body: []byte(body), + addr: addr[:], + birth: birth[:], + seq: seq[:], + } + c.check = []byte{c.createCheckCode()} + c.whole = append(c.body, c.check...) + c.setSex() + return c, true +} + +// Make15 generates an 15-character length number +func Make15(addr [6]byte, birth [6]byte, seq [3]byte) (*ChinaID, bool) { + body := string(addr[:]) + string(birth[:]) + string(seq[:]) + if !isDigit(body, -1) { + return nil, false + } + c := &ChinaID{ + body: []byte(body), + addr: addr[:], + birth: birth[:], + seq: seq[:], + } + c.whole = c.body + c.setSex() + return c, true +} + +// Validate judge whether chinese identity card number is valid. +func Validate(id string) bool { + l := len(id) + switch l { + case 15: + return true + case 18: + var sum int + for k, v := range id[:l-1] { + d, _ := strconv.Atoi(string(v)) + sum += weight[k] * d + } + return validate[sum%11] == id[l-1] + default: + return false + } +} + +// CheckCode generates check code of chinese identity card number. +func CheckCode(idBody string) (string, bool) { + if len(idBody) == 17 { + var sum int + for k, v := range idBody { + d, _ := strconv.Atoi(string(v)) + sum += weight[k] * d + } + return string(validate[sum%11]), true + } + return "", false +} + +// Parse parse chinese identity card number to *ChinaID +func Parse(id string) (*ChinaID, error) { + if !isDigit(id, 17) { + return nil, ErrInvalidChinaID + } + whole := []byte(id) + switch len(whole) { + case 18: + c := &ChinaID{ + whole: whole, + body: whole[:17], + addr: whole[:6], + birth: whole[6:14], + seq: whole[14:17], + check: whole[17:], + } + if !c.validate() { + return nil, ErrInvalidChinaID + } + c.setSex() + return c, nil + + case 15: + c := &ChinaID{ + whole: whole, + body: whole, + addr: whole[:6], + birth: whole[6:12], + seq: whole[12:], + } + c.setSex() + return c, nil + default: + return nil, ErrInvalidChinaID + } +} + +// String returns chinese identity card number +func (c *ChinaID) String() string { + return bytes2String(c.whole) +} + +// MarshalJSON implements json.Marshaler +func (c *ChinaID) MarshalJSON() ([]byte, error) { + var m = map[string]interface{}{ + "whole": c.String(), + "body": c.Body(), + "addr": c.Addr(), + "birth": c.Birth(), + "seq": c.Seq(), + "sex": c.sex, + } + if len(c.check) > 0 { + m["check"] = c.Check() + } + return json.Marshal(m) +} + +// func (c *ChinaID) GoString() string { +// b, _ := c.MarshalJSON() +// return bytes2String(b) +// } + +// Format implements fmt.Formatter +func (c *ChinaID) Format(f fmt.State, r rune) { + if r == 'v' && (f.Flag('+') || f.Flag('#')) { + b, _ := c.MarshalJSON() + f.Write(b) + return + } + f.Write(c.whole) +} + +// Body returns main part of chinese identity card number +func (c *ChinaID) Body() string { + return bytes2String(c.body) +} + +// Addr returns address code +func (c *ChinaID) Addr() string { + return bytes2String(c.addr) +} + +// Birth returns date of birth +func (c *ChinaID) Birth() string { + return bytes2String(c.birth) +} + +// Seq returns sequence number in the persons whose date of birth are same +func (c *ChinaID) Seq() string { + return bytes2String(c.seq) +} + +// Check returns check code +func (c *ChinaID) Check() string { + return bytes2String(c.check) +} + +// Sex returns sex +// 1 male / 0 female +func (c *ChinaID) Sex() int { + return c.sex +} + +// SexText returns sex text, male or female +func (c *ChinaID) SexText() string { + if c.IsMale() { + return "male" + } + return "female" +} + +// IsMale judge whether is male or not +func (c *ChinaID) IsMale() bool { + return c.sex == 1 +} + +// IsMale judge whether is female or not +func (c *ChinaID) IsFemale() bool { + return c.sex == 0 +} + +func (c *ChinaID) setSex() { + i, _ := strconv.Atoi(bytes2String(c.seq[2:])) + c.sex = i % 2 +} + +var ( + weight = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2} + validate = [17]byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'} +) + +func (c *ChinaID) createCheckCode() byte { + var sum int + for k, v := range c.body { + d, _ := strconv.Atoi(string(v)) + sum += weight[k] * d + } + return validate[sum%11] +} + +func (c *ChinaID) validate() bool { + return c.check[0] == c.createCheckCode() +} + +func isDigit(s string, maxLen int) bool { + if maxLen < 0 { + for _, r := range s { + if !unicode.IsDigit(r) { + return false + } + } + } else { + for i, r := range s { + if i >= maxLen { + break + } + if !unicode.IsDigit(r) { + return false + } + } + } + return true +} + +func bytes2String(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} diff --git a/chinaid/chinaid_test.go b/chinaid/chinaid_test.go new file mode 100644 index 0000000..0025f46 --- /dev/null +++ b/chinaid/chinaid_test.go @@ -0,0 +1,78 @@ +package chinaid + +import ( + "testing" +) + +func Test18(t *testing.T) { + // make ChinaID18 + + addr := [6]byte{} + for k, v := range "326221" { + addr[k] = byte(v) + } + birth := [8]byte{} + for k, v := range "19880102" { + birth[k] = byte(v) + } + seq := [3]byte{} + for k, v := range "124" { + seq[k] = byte(v) + } + id18, ok := Make18(addr, birth, seq) + t.Logf("Make18(%v): %s", ok, id18) + + // check ChinaID18 + chinaId, err := Parse(id18.String()) + if err != nil { + t.Fatalf("%v", err) + } + t.Logf("id18 format(%%v): %v, sex: %s\n\n", chinaId, chinaId.SexText()) + t.Logf("id18 format(%%+v): %#v\n\n", chinaId) +} + +func Test15(t *testing.T) { + // make ChinaID15 + + addr := [6]byte{} + for k, v := range "326221" { + addr[k] = byte(v) + } + birth := [6]byte{} + for k, v := range "880102" { + birth[k] = byte(v) + } + seq := [3]byte{} + for k, v := range "124" { + seq[k] = byte(v) + } + id15, ok := Make15(addr, birth, seq) + t.Logf("Make15(%v): %s", ok, id15) + + // check ChinaID15 + chinaId, err := Parse(id15.String()) + if err != nil { + t.Fatalf("%v", err) + } + t.Logf("id15 format(%%v): %v, sex: %s\n\n", chinaId, chinaId.SexText()) + t.Logf("id15 format(%%+v): %#v\n\n", chinaId) +} + +func TestValidate(t *testing.T) { + ok := Validate("32622119880102124X") + if !ok { + t.Fail() + } +} + +func TestCheckCode(t *testing.T) { + checkCode, ok := CheckCode("32622119880102124") + if !ok || checkCode != "X" { + t.Fatalf("get '%s', expect 'X'", checkCode) + } + + checkCode, ok = CheckCode("326221880102124") + if ok { + t.Fatalf("get '%s', expect ''", checkCode) + } +}