1#![warn(missing_docs)]
2#![doc = "A library for handling Chinese identification numbers."]
3
4use chrono::NaiveDate;
5
6const LENGTH: usize = 18;
8
9const COEFFICIENT: [usize; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
11
12const CHECK: [char; 11] = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
14
15#[derive(Debug, Clone, Eq, PartialEq)]
18pub enum Gender {
19 Male,
22 Female,
25}
26
27impl ::std::fmt::Display for Gender {
28 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
29 match self {
30 Gender::Male => write!(f, "男"),
31 Gender::Female => write!(f, "女"),
32 }
33 }
34}
35
36#[derive(Debug)]
39pub enum Error {
40 Length(usize),
43 InvalidDate(String),
46 NotNumber(char),
49}
50
51impl ::std::fmt::Display for Error {
52 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
53 match self {
54 Error::Length(len) => write!(f, "长度必须是18位,当前为{}位", len),
55 Error::InvalidDate(curr) => write!(f, "无效的生日日期 '{}'", curr),
56 Error::NotNumber(c) => write!(f, "'{}'不是有效的数字", c),
57 }
58 }
59}
60
61pub struct ChinaId {
63 raw: String,
64 birthday: NaiveDate,
65 gender: Gender,
66}
67
68impl ::std::fmt::Display for ChinaId {
69 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
70 write!(f, "{}", self.raw)
71 }
72}
73
74pub type Result<T> = core::result::Result<T, Error>;
76
77impl ChinaId {
78 pub fn valid(raw: &str) -> Result<ChinaId> {
81 if raw.len() != LENGTH {
83 return Err(Error::Length(raw.len()));
84 }
85
86 let text = raw.to_uppercase();
87
88 let chars = text.chars();
89
90 let mut sum = 0_usize;
91 let mut gender_char: char = ' ';
92
93 for (i, c) in chars.enumerate() {
94 if i == LENGTH - 1 {
96 let y = (sum % 11) as usize;
97 if CHECK[y] == c {
98 break;
100 }
101 }
102
103 match c.to_string().parse::<usize>() {
106 Ok(c) => {
107 sum += c * COEFFICIENT[i];
108 }
109 Err(_) => return Err(Error::NotNumber(c)),
110 }
111
112 if i == 16 {
113 gender_char = c;
114 }
115 }
116
117 let birthday = {
119 let date_str = &text[6..14];
120 match NaiveDate::parse_from_str(date_str, "%Y%m%d") {
121 Ok(date) => date,
122 Err(_) => return Err(Error::InvalidDate(date_str.to_string())),
123 }
124 };
125
126 let gender = {
127 if let Ok(u) = gender_char.to_string().parse::<usize>() {
128 match u % 2 {
129 0 => Gender::Female,
130 _ => Gender::Male,
131 };
132 }
133
134 Gender::Male
135 };
136
137 Ok(ChinaId {
138 raw: text,
139 birthday,
140 gender,
141 })
142 }
143
144 pub fn adcode(&self) -> &str {
147 &self.raw[..6]
148 }
149
150 pub fn birthday(&self) -> NaiveDate {
153 self.birthday.clone()
154 }
155
156 pub fn gender(&self) -> Gender {
159 self.gender.clone()
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn ut_parse() {
169 let id = ChinaId::valid("43102220200101133x").unwrap();
170
171 assert_eq!(format!("{}", id), "43102220200101133X");
172 assert_eq!(id.adcode(), "431022");
173 assert_eq!(id.birthday().to_string(), "2020-01-01");
174 assert_eq!(id.gender(), Gender::Male);
175 }
176}