1use crate::region::REGION_CODES;
2use chrono::NaiveDate;
3
4static WEIGHT: [u32; 18] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
6static CHECK_CODES: &str = "10X98765432";
8
9pub fn is_valid(id: &str) -> bool {
10 if id.len() != 18 {
11 return false;
12 }
13 is_valid_18(id)
14}
15
16fn is_valid_18(id: &str) -> bool {
17 if !is_valid_date(&id[6..14]) {
18 return false;
19 }
20
21 let check_code = &id[17..].chars().next().unwrap();
23 let cal_code = cal_check_code(id);
25 cal_code.eq(check_code)
26}
27
28pub(crate) fn cal_check_code(id: &str) -> char {
30 let mut sum = 0;
31 for (i, c) in id[..17].chars().enumerate() {
32 if let Some(digit) = c.to_digit(10) {
33 sum += digit * WEIGHT[i];
34 }
35 }
36 CHECK_CODES.chars().nth((sum % 11) as usize).unwrap()
37}
38
39fn is_valid_date(date_str: &str) -> bool {
41 if date_str.len() != 8 {
42 return false;
43 }
44 NaiveDate::parse_from_str(date_str, "%Y%m%d").is_ok()
45}
46
47#[derive(Debug)]
48pub struct IdInfo {
49 pub id: String,
50 pub code: String,
51 pub region: String,
52 pub date: Option<NaiveDate>,
53}
54
55pub fn get_info(id: &str) -> Option<IdInfo> {
57 is_valid(id).then_some(IdInfo {
58 id: id.to_string(),
59 code: id[..6].to_string(),
60 region: REGION_CODES.get(&id[..6]).unwrap_or(&"未知").to_string(),
61 date: NaiveDate::parse_from_str(&id[6..14], "%Y%m%d").ok(),
62 })
63}