1#[macro_use]
47extern crate lazy_static;
48
49use chrono::{Datelike, Local, NaiveDate};
50use std::collections::HashMap;
51use std::fmt;
52
53pub mod fake;
54pub mod hk;
55pub mod mo;
56pub mod region;
57pub mod tw;
58
59const ID_V1_LEN: usize = 15;
60const ID_V2_LEN: usize = 18;
61
62static CHINESE_ZODIAC: [&'static str; 12] = [
63 "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗",
64];
65
66static CELESTIAL_STEM: [&'static str; 10] =
67 ["癸", "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "任"];
68
69static TERRESTRIAL_BRANCH: [&'static str; 12] = [
70 "亥", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌",
71];
72
73lazy_static! {
74 static ref PROVINCE_CODE_NAME: HashMap<&'static str, &'static str> = {
75 let mut map = HashMap::new();
76 map.insert("11", "北京");
77 map.insert("12", "天津");
78 map.insert("13", "河北");
79 map.insert("14", "山西");
80 map.insert("15", "内蒙古");
81 map.insert("21", "辽宁");
82 map.insert("22", "吉林");
83 map.insert("23", "黑龙江");
84 map.insert("31", "上海");
85 map.insert("32", "江苏");
86 map.insert("33", "浙江");
87 map.insert("34", "安徽");
88 map.insert("35", "福建");
89 map.insert("36", "江西");
90 map.insert("37", "山东");
91 map.insert("41", "河南");
92 map.insert("42", "湖北");
93 map.insert("43", "湖南");
94 map.insert("44", "广东");
95 map.insert("45", "广西");
96 map.insert("46", "海南");
97 map.insert("50", "重庆");
98 map.insert("51", "四川");
99 map.insert("52", "贵州");
100 map.insert("53", "云南");
101 map.insert("54", "西藏");
102 map.insert("61", "陕西");
103 map.insert("62", "甘肃");
104 map.insert("63", "青海");
105 map.insert("64", "宁夏");
106 map.insert("65", "新疆");
107 map.insert("71", "台湾");
108 map.insert("81", "香港");
109 map.insert("82", "澳门");
110 map.insert("83", "台湾");
111 map.insert("91", "国外");
112 map
113 };
114}
115
116#[derive(Debug)]
118pub enum Error {
119 InvalidNumber,
120 UpgradeError,
121 GenerateFakeIDError(String),
122}
123
124impl std::error::Error for Error {}
125
126impl fmt::Display for Error {
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128 match self {
129 Error::InvalidNumber => write!(f, "Invalid Number"),
130 Error::UpgradeError => write!(f, "Upgrade Failed"),
131 Error::GenerateFakeIDError(msg) => write!(f, "Generate Fake ID Error: {}", msg),
132 }
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
138pub enum Gender {
139 Male,
140 Female,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
145pub struct Identity {
146 number: String,
147 valid: bool,
148}
149
150impl Identity {
151 pub fn new(number: &str) -> Self {
153 let mut id = Identity {
154 number: number.trim().to_ascii_uppercase(),
155 valid: false,
156 };
157 if id.number.len() == ID_V1_LEN {
158 match upgrade(&id.number) {
159 Ok(value) => {
160 id.number = value;
161 id.valid = true;
162 }
163 _ => id.valid = false,
164 }
165 } else if id.number.len() == ID_V2_LEN {
166 id.valid = if validate_v2(&id.number) { true } else { false }
167 } else {
168 id.valid = false;
169 }
170 id
171 }
172
173 pub fn number(&self) -> &str {
175 &self.number
176 }
177
178 pub fn birth_date(&self) -> Option<String> {
180 if !self.is_valid() {
181 return None;
182 }
183 let birth = &self.number[6..14];
184 let year = &birth[0..4];
185 let month = &birth[4..6];
186 let date = &birth[6..8];
187 Some(format!("{}-{}-{}", year, month, date))
188 }
189
190 pub fn year(&self) -> Option<u32> {
192 if !self.is_valid() {
193 return None;
194 }
195 if let Ok(year) = self.number[6..10].parse::<u32>() {
196 Some(year)
197 } else {
198 None
199 }
200 }
201
202 pub fn month(&self) -> Option<u32> {
204 if !self.is_valid() {
205 return None;
206 }
207 if let Ok(month) = self.number[10..12].parse::<u32>() {
208 Some(month)
209 } else {
210 None
211 }
212 }
213
214 pub fn day(&self) -> Option<u32> {
216 if !self.is_valid() {
217 return None;
218 }
219 if let Ok(day) = self.number[12..14].parse::<u32>() {
220 Some(day)
221 } else {
222 None
223 }
224 }
225
226 pub fn age(&self) -> Option<u32> {
229 if !self.is_valid() {
230 return None;
231 }
232 if let Ok(year) = self.number[6..10].parse::<u32>() {
233 let current = Local::now().year() as u32;
234 if current < year {
235 return None;
236 }
237 Some(current - year)
238 } else {
239 None
240 }
241 }
242
243 pub fn age_in_year(&self, year: u32) -> Option<u32> {
246 if !self.is_valid() {
247 return None;
248 }
249 if let Ok(value) = self.number[6..10].parse::<u32>() {
250 if year < value {
251 return None;
252 }
253 Some(year - value)
254 } else {
255 None
256 }
257 }
258
259 pub fn gender(&self) -> Option<Gender> {
261 if !self.is_valid() {
262 return None;
263 }
264 if let Ok(code) = self.number[16..17].parse::<u32>() {
265 if code % 2 != 0 {
266 Some(Gender::Male)
267 } else {
268 Some(Gender::Female)
269 }
270 } else {
271 None
272 }
273 }
274
275 pub fn province(&self) -> Option<&str> {
277 if !self.is_valid() {
278 return None;
279 }
280 let code = &self.number[0..2];
281 match PROVINCE_CODE_NAME.get(code) {
282 Some(name) => Some(*name),
283 None => None,
284 }
285 }
286
287 pub fn region(&self) -> Option<&str> {
289 if !self.is_valid() {
290 return None;
291 }
292 region::query(&self.number[0..6])
293 }
294
295 pub fn region_code(&self) -> Option<&str> {
297 if !self.is_valid() {
298 return None;
299 }
300 Some(&self.number[0..6])
301 }
302
303 pub fn constellation(&self) -> Option<&str> {
305 if !self.is_valid() {
306 return None;
307 }
308 let month = match self.month() {
309 Some(value) => value,
310 None => return None,
311 };
312 let day = match self.day() {
313 Some(value) => value,
314 None => return None,
315 };
316 constellation(month, day)
317 }
318
319 pub fn chinese_era(&self) -> Option<String> {
321 if !self.is_valid() {
322 return None;
323 }
324 let year = match self.year() {
325 Some(value) => value,
326 None => return None,
327 };
328 chinese_era(year)
329 }
330
331 pub fn chinese_zodiac(&self) -> Option<&str> {
333 if !self.is_valid() {
334 return None;
335 }
336 let year = match self.year() {
337 Some(value) => value,
338 None => return None,
339 };
340 chinese_zodiac(year)
341 }
342
343 pub fn is_valid(&self) -> bool {
345 self.valid
346 }
347
348 pub fn is_empty(&self) -> bool {
350 self.number.is_empty()
351 }
352
353 pub fn len(&self) -> usize {
355 self.number.len()
356 }
357}
358
359pub fn chinese_zodiac(year: u32) -> Option<&'static str> {
362 if year < 1000 {
363 return None;
364 }
365 let end = 3;
366 let idx = (year - end) % 12;
367 let zod = CHINESE_ZODIAC[idx as usize];
368 Some(zod)
369}
370
371pub fn chinese_era(year: u32) -> Option<String> {
374 if year < 1000 {
375 return None;
376 }
377 let i = (year - 3) % 10;
378 let j = (year - 3) % 12;
379 let era = format!(
380 "{}{}",
381 CELESTIAL_STEM[i as usize], TERRESTRIAL_BRANCH[j as usize]
382 );
383 Some(era)
384}
385
386pub fn constellation(month: u32, day: u32) -> Option<&'static str> {
388 let result = if (month == 1 && day >= 20) || (month == 2 && day <= 18) {
389 "水瓶座"
390 } else if (month == 2 && day >= 19) || (month == 3 && day <= 20) {
391 "双鱼座"
392 } else if (month == 3 && day > 20) || (month == 4 && day <= 19) {
393 "白羊座"
394 } else if (month == 4 && day >= 20) || (month == 5 && day <= 20) {
395 "金牛座"
396 } else if (month == 5 && day >= 21) || (month == 6 && day <= 21) {
397 "双子座"
398 } else if (month == 6 && day > 21) || (month == 7 && day <= 22) {
399 "巨蟹座"
400 } else if (month == 7 && day > 22) || (month == 8 && day <= 22) {
401 "狮子座"
402 } else if (month == 8 && day >= 23) || (month == 9 && day <= 22) {
403 "处女座"
404 } else if (month == 9 && day >= 23) || (month == 10 && day <= 23) {
405 "天秤座"
406 } else if (month == 10 && day > 23) || (month == 11 && day <= 22) {
407 "天蝎座"
408 } else if (month == 11 && day > 22) || (month == 12 && day <= 21) {
409 "射手座"
410 } else if (month == 12 && day > 21) || (month == 1 && day <= 19) {
411 "魔羯座"
412 } else {
413 return None;
414 };
415 Some(result)
416}
417
418pub fn upgrade(number: &str) -> Result<String, Error> {
420 let number = number.trim().to_ascii_uppercase();
421 if number.len() == ID_V1_LEN && is_digital(&number) {
422 let mut idv2 = String::new();
423 let birthday = "19".to_owned() + &number[6..12];
424 let birth_date = NaiveDate::parse_from_str(&birthday, "%Y%m%d");
425
426 let cal = match birth_date {
427 Ok(value) => value,
428 _ => return Err(Error::UpgradeError),
429 };
430
431 idv2.push_str(&number[0..6]);
432 idv2.push_str(&cal.year().to_string());
433 idv2.push_str(&number[8..]);
434
435 let iarr = match string_to_integer_array(&idv2) {
436 Ok(value) => value,
437 _ => return Err(Error::UpgradeError),
438 };
439
440 let weight = get_weights_sum(&iarr);
441 if let Some(code) = get_check_code(weight) {
442 idv2.push_str(code);
443 Ok(idv2)
444 } else {
445 return Err(Error::UpgradeError);
446 }
447 } else {
448 Err(Error::InvalidNumber)
449 }
450}
451
452pub fn validate(number: &str) -> bool {
454 let number = number.trim().to_ascii_uppercase();
455 if number.len() == ID_V1_LEN {
456 validate_v1(&number)
457 } else if number.len() == ID_V2_LEN {
458 validate_v2(&number)
459 } else {
460 false
461 }
462}
463
464fn validate_v1(number: &str) -> bool {
465 if number.len() == ID_V1_LEN && is_digital(number) {
466 let code = &number[0..2];
467 if !PROVINCE_CODE_NAME.contains_key(code) {
468 return false;
469 }
470
471 let birthday = "19".to_owned() + &number[6..12];
472 let birth_date = NaiveDate::parse_from_str(&birthday, "%Y%m%d");
473 birth_date.is_ok()
474 } else {
475 false
476 }
477}
478
479fn validate_v2(number: &str) -> bool {
480 if number.len() != ID_V2_LEN {
481 return false;
482 }
483
484 let birth_date = NaiveDate::parse_from_str(&number[6..14], "%Y%m%d");
485 if !birth_date.is_ok() {
486 return false;
487 }
488
489 let code17 = &number[0..17];
490 let code18 = &number[17..18];
491 if is_digital(code17) {
492 let iarr = match string_to_integer_array(code17) {
493 Ok(value) => value,
494 _ => return false,
495 };
496
497 let sum17 = get_weights_sum(&iarr);
498 if let Some(code) = get_check_code(sum17) {
499 if code == code18.to_uppercase() {
500 return true;
501 }
502 }
503 }
504 false
505}
506
507fn is_digital(s: &str) -> bool {
508 if s.is_empty() {
509 false
510 } else {
511 s.chars().all(char::is_numeric)
512 }
513}
514
515fn get_check_code(sum: u32) -> Option<&'static str> {
516 let code = match sum % 11 {
517 10 => "2",
518 9 => "3",
519 8 => "4",
520 7 => "5",
521 6 => "6",
522 5 => "7",
523 4 => "8",
524 3 => "9",
525 2 => "X",
526 1 => "0",
527 0 => "1",
528 _ => return None,
529 };
530 Some(code)
531}
532
533fn string_to_integer_array(s: &str) -> Result<Vec<u32>, Error> {
534 let mut v: Vec<u32> = Vec::new();
535 for ch in s.chars() {
536 match ch.to_digit(10) {
537 Some(i) => v.push(i),
538 None => return Err(Error::InvalidNumber),
539 }
540 }
541 Ok(v)
542}
543
544fn get_weights_sum(arr: &[u32]) -> u32 {
545 let weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
546 let mut sum = 0;
547 if weights.len() == arr.len() {
548 for i in 0..arr.len() {
549 for j in 0..weights.len() {
550 if i == j {
551 sum = sum + arr[i] * weights[j];
552 }
553 }
554 }
555 }
556 sum
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
564 fn test_upgrade() {
565 let id = Identity::new("632123820927051");
566 assert_eq!(id.is_valid(), true);
567 assert_eq!(id.number(), "632123198209270518");
568 let id = upgrade("310112850409522").unwrap();
569 assert_eq!(&id, "310112198504095227");
570 }
571
572 #[test]
573 fn test_validate() {
574 assert_eq!(validate("511702800222130"), true);
575 assert_eq!(validate("230127197908177456"), true);
576 }
577
578 #[test]
579 fn test_compute_age() {
580 let id = Identity::new("511702800222130");
581 assert_eq!(id.age_in_year(2020), Some(40));
582 assert_eq!(id.age_in_year(1980), Some(0));
583 assert_eq!(id.age_in_year(1900), None);
584 }
585
586 #[test]
587 fn test_utilities() {
588 assert_eq!(chinese_zodiac(1000), Some("鼠"));
589 assert_eq!(chinese_zodiac(1900), Some("鼠"));
590 assert_eq!(chinese_zodiac(2021), Some("牛"));
591 assert_eq!(chinese_era(1000), Some("庚子".to_string()));
592 assert_eq!(chinese_era(1900), Some("庚子".to_string()));
593 assert_eq!(chinese_era(2021), Some("辛丑".to_string()));
594 assert_eq!(constellation(10, 25), Some("天蝎座"));
595 assert_eq!(constellation(2, 29), Some("双鱼座"));
596 assert_eq!(constellation(0, 32), None);
597 }
598
599 #[test]
600 fn test_identity() {
601 let a = Identity::new("632123820927051");
602 let b = Identity::new("632123198209270518");
603 assert_eq!(a == b, true);
604 let a = Identity::new("21021119810503545X");
605 let b = Identity::new("21021119810503545x");
606 assert_eq!(a == b, true);
607 let a = Identity::new("330421197402080974");
608 let b = Identity::new("130133197909136078");
609 assert_eq!(a != b, true);
610 }
611}