china_id/
lib.rs

1#![warn(missing_docs)]
2#![doc = "A library for handling Chinese identification numbers."]
3
4use chrono::NaiveDate;
5
6// 统一身份证长度
7const LENGTH: usize = 18;
8
9// 不同位 有不同系数
10const COEFFICIENT: [usize; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
11
12// 按索引找到对应校验码
13const CHECK: [char; 11] = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
14
15/// Represents the gender of an individual.
16/// 有效性别:男性/女性
17#[derive(Debug, Clone, Eq, PartialEq)]
18pub enum Gender {
19    /// Male gender.
20    /// 男性
21    Male,
22    /// Female gender.
23    /// 女性
24    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/// Represents the different types of errors that can occur when validating a Chinese ID.
37/// 指定在解析时可能遇到的错误
38#[derive(Debug)]
39pub enum Error {
40    /// The length of the ID is incorrect.
41    /// 长度不匹配时的错误
42    Length(usize),
43    /// The date of birth in the ID is invalid.
44    /// 无效的出生日期
45    InvalidDate(String),
46    /// A character in the ID is not a valid number.
47    /// 出乎意料的内容
48    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
61/// Represents a Chinese identification number.
62pub 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
74/// A specialized `Result` type for Chinese ID operations.
75pub type Result<T> = core::result::Result<T, Error>;
76
77impl ChinaId {
78    /// Validates the Chinese ID.
79    /// 检查身份证ID
80    pub fn valid(raw: &str) -> Result<ChinaId> {
81        // must be 18
82        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            // index: 18
95            if i == LENGTH - 1 {
96                let y = (sum % 11) as usize;
97                if CHECK[y] == c {
98                    //  ALL Good
99                    break;
100                }
101            }
102
103            // index: 0-17
104            // must be number
105            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        // birthday must be valid
118        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    /// Returns the area code in the ID.
145    /// 返回身份证中的地区编码
146    pub fn adcode(&self) -> &str {
147        &self.raw[..6]
148    }
149
150    /// Returns the birth date in the ID.
151    /// 返回身份证中的出生日期
152    pub fn birthday(&self) -> NaiveDate {
153        self.birthday.clone()
154    }
155
156    /// Returns the gender in the ID.
157    /// 返回身份证中的性别
158    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}