1pub mod bit_fields;
89pub mod human_redable;
90
91pub use chrono;
92#[cfg(feature = "serde")]
93pub use serde;
94pub use thiserror;
95
96pub mod prelude {
97 pub use crate::{validate, Gender, PeselTrait};
98 pub use chrono::NaiveDate;
99}
100
101use chrono::NaiveDate;
102use thiserror::Error;
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum Gender {
107 Male,
108 Female,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Error)]
112pub enum ValidationError {
113 #[error("Pesel is too short.")]
114 TooShort(usize),
115 #[error("Pesel is too long.")]
116 TooLong(usize),
117 #[error("Pesel has an invalid date of birth.")]
118 BirthDate,
119 #[error("Pesel has an invalid control digit.")]
120 ControlDigit,
121}
122
123const PESEL_WEIGHTS: [u8; 11] = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1];
124
125#[cfg(feature = "serde")]
126#[cfg_attr(feature = "serde", macro_export)]
127macro_rules! impl_pesel_visitor {
128 ($name:ident) => {
129 pub struct PeselVisitor;
130
131 impl<'de> serde::de::Visitor<'de> for PeselVisitor {
132 type Value = $name;
133
134 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
135 write!(formatter, "a valid PESEL as u64, &str, &'de str, or String")
136 }
137
138 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
139 where
140 E: serde::de::Error,
141 {
142 $name::try_from(v).map_err(|err| serde::de::Error::custom(err.to_string()))
143 }
144
145 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
146 where
147 E: serde::de::Error,
148 {
149 $name::try_from(v).map_err(|err| serde::de::Error::custom(err.to_string()))
150 }
151
152 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
153 where
154 E: serde::de::Error,
155 {
156 $name::try_from(v).map_err(|err| serde::de::Error::custom(err.to_string()))
157 }
158
159 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
160 where
161 E: serde::de::Error,
162 {
163 $name::try_from(v).map_err(|err| serde::de::Error::custom(err.to_string()))
164 }
165 }
166 };
167}
168#[cfg(feature = "serde")]
169#[cfg_attr(feature = "serde", macro_export)]
170macro_rules! impl_pesel_deserializer {
171 ($name:ident) => {
172 impl_pesel_visitor!($name);
173
174 impl<'de> serde::Deserialize<'de> for $name {
175 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
176 where
177 D: serde::Deserializer<'de>,
178 {
179 deserializer.deserialize_any(PeselVisitor)
180 }
181 }
182 };
183}
184
185pub const fn month_from_section(month_section: u8) -> Option<u8> {
189 if !(1 <= month_section && month_section <= 92) {
190 return None;
191 }
192
193 Some(month_section - (((month_section / 10) / 2) * 20))
194}
195
196pub const fn month_to_section(month: u8, year: u16) -> Option<u8> {
201 if !(1 <= month && month <= 12) {
202 return None;
203 }
204 if !(1800 <= year && year <= 2299) {
205 return None;
206 }
207
208 let base = ((year / 100) - 10) as u8;
210 let shift = match base {
211 8 => 80,
212 9 => 0,
213 base => (base + 1) * 20,
214 };
215
216 Some(month + shift)
217}
218
219pub const fn year_from_sections(month_section: u8, year_section: u8) -> u16 {
220 let shift = ((month_section / 10) / 2) * 2;
221
222 (match shift {
223 8 => 1800,
224 shift => 1900 + (shift as u16) * 50,
225 } + year_section as u16)
226}
227
228pub trait PeselTrait: TryFrom<u64> + Into<u64>
235where
236 u64: From<Self>,
237 for<'a> u64: From<&'a Self>,
238{
239 fn day_section(&self) -> u8;
241
242 fn month_section(&self) -> u8;
244
245 fn year_section(&self) -> u8;
247
248 fn ordinal_section(&self) -> u16;
250
251 fn control_section(&self) -> u8;
253
254 fn day(&self) -> u8 {
256 self.day_section()
257 }
258
259 fn month(&self) -> u8 {
261 match month(self) {
262 Some(month) => month,
263 None => unreachable!(),
264 }
265 }
266
267 fn year(&self) -> u16 {
269 year(self)
270 }
271
272 fn date_of_birth(&self) -> NaiveDate {
274 match date_of_birth(self) {
275 Some(date_of_birth) => date_of_birth,
276 None => unreachable!(),
277 }
278 }
279
280 fn gender(&self) -> Gender {
282 gender(self)
283 }
284}
285
286pub fn day_section(pesel: impl Into<u64>) -> u8 {
288 ((pesel.into() % 10_000_000) / 100_000) as u8
289}
290
291pub fn month_section(pesel: impl Into<u64>) -> u8 {
293 ((pesel.into() % 1_000_000_000) / 10_000_000) as u8
294}
295
296pub fn year_section(pesel: impl Into<u64>) -> u8 {
298 ((pesel.into() % 100_000_000_000) / 1_000_000_000) as u8
299}
300
301pub fn ordinal_section(pesel: impl Into<u64>) -> u16 {
303 ((pesel.into() % 100_000) / 10) as u16
304}
305
306pub fn control_section(pesel: impl Into<u64>) -> u8 {
308 (pesel.into() % 10) as u8
309}
310
311pub fn day(pesel: impl Into<u64>) -> u8 {
313 day_section(pesel)
314}
315
316pub fn month(pesel: impl Into<u64>) -> Option<u8> {
322 month_from_section(month_section(pesel))
323}
324
325pub fn year(pesel: impl Into<u64>) -> u16 {
327 let pesel = pesel.into();
328 year_from_sections(month_section(pesel), year_section(pesel))
329}
330
331pub fn date_of_birth(pesel: impl Into<u64>) -> Option<NaiveDate> {
333 let pesel = pesel.into();
334 NaiveDate::from_ymd_opt(
335 year(pesel) as i32,
336 match month(pesel) {
337 Some(month) => month as u32,
338 None => return None,
339 },
340 day(pesel) as u32,
341 )
342}
343
344pub fn gender(pesel: impl Into<u64>) -> Gender {
346 if ordinal_section(pesel) % 2 == 0 {
347 Gender::Female
348 } else {
349 Gender::Male
350 }
351}
352
353pub fn validate(pesel: impl Into<u64>) -> Result<(), ValidationError> {
355 let pesel = pesel.into();
356 let mut pesel_str = pesel.to_string();
357
358 if pesel_str.len() < 8 {
359 return Err(ValidationError::TooShort(pesel_str.len()));
360 }
361
362 if pesel_str.len() > 11 {
363 return Err(ValidationError::TooLong(pesel_str.len()));
364 }
365
366 if pesel_str.len() < 11 {
367 let mut new_value_str = "0".to_string();
368
369 for _ in (pesel_str.len() + 1)..11 {
370 new_value_str.push('0');
371 }
372
373 pesel_str = new_value_str + &pesel_str;
374 }
375
376 if date_of_birth(pesel).is_none() {
377 return Err(ValidationError::BirthDate);
378 }
379
380 let mut sum = 0;
381 for (i, digit) in pesel_str
382 .chars()
383 .take(11)
384 .map(|char| char.to_digit(10).unwrap())
385 .enumerate()
386 {
387 sum += (digit as u8) * PESEL_WEIGHTS[i];
388 }
389
390 if let Some(Some(last_digit)) = sum.to_string().chars().last().map(|char| char.to_digit(10)) {
391 if last_digit != 0 {
392 return Err(ValidationError::ControlDigit);
393 }
394 Ok(())
395 } else {
396 Err(ValidationError::ControlDigit)
397 }
398}
399
400#[derive(Debug, Clone, PartialEq, Eq, Error)]
401#[error("{0}")]
402pub enum PeselTryFromError<T> {
403 ValidationError(#[from] ValidationError),
404 Other(T),
405}
406
407#[macro_export]
408macro_rules! impl_try_from_str_for_pesel {
409 ($name:ident) => {
410 impl TryFrom<&str> for $name {
411 type Error = PeselTryFromError<std::num::ParseIntError>;
412
413 fn try_from(value: &str) -> Result<Self, Self::Error> {
414 let value = u64::from_str_radix(value, 10).map_err(PeselTryFromError::Other)?;
415 validate(value)?;
416 Self::try_from(value).map_err(PeselTryFromError::ValidationError)
417 }
418 }
419
420 impl TryFrom<&String> for $name {
421 type Error = PeselTryFromError<std::num::ParseIntError>;
422
423 fn try_from(value: &String) -> Result<Self, Self::Error> {
424 Self::try_from(value.as_str())
425 }
426 }
427
428 impl TryFrom<String> for $name {
429 type Error = PeselTryFromError<std::num::ParseIntError>;
430
431 fn try_from(value: String) -> Result<Self, Self::Error> {
432 Self::try_from(&value)
433 }
434 }
435 };
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 static PESEL1: u64 = 02290486168;
443 static PESEL2: u64 = 01302534699;
444 static PESEL3: u64 = 00010128545;
445 static PESEL4: u64 = 98250993285;
446 static PESEL5: u64 = 60032417874;
447
448 #[test]
449 fn day_section() {
450 assert_eq!(super::day_section(PESEL1), 04);
451 assert_eq!(super::day_section(PESEL2), 25);
452 assert_eq!(super::day_section(PESEL3), 01);
453 assert_eq!(super::day_section(PESEL4), 09);
454 assert_eq!(super::day_section(PESEL5), 24);
455 }
456
457 #[test]
458 fn month_section() {
459 assert_eq!(super::month_section(PESEL1), 29);
460 assert_eq!(super::month_section(PESEL2), 30);
461 assert_eq!(super::month_section(PESEL3), 01);
462 assert_eq!(super::month_section(PESEL4), 25);
463 assert_eq!(super::month_section(PESEL5), 03);
464 }
465
466 #[test]
467 fn year_section() {
468 assert_eq!(super::year_section(PESEL1), 02);
469 assert_eq!(super::year_section(PESEL2), 01);
470 assert_eq!(super::year_section(PESEL3), 00);
471 assert_eq!(super::year_section(PESEL4), 98);
472 assert_eq!(super::year_section(PESEL5), 60);
473 }
474
475 #[test]
476 fn ordinal_section() {
477 assert_eq!(super::ordinal_section(PESEL1), 8616);
478 assert_eq!(super::ordinal_section(PESEL2), 3469);
479 assert_eq!(super::ordinal_section(PESEL3), 2854);
480 assert_eq!(super::ordinal_section(PESEL4), 9328);
481 assert_eq!(super::ordinal_section(PESEL5), 1787);
482 }
483
484 #[test]
485 fn control_section() {
486 assert_eq!(super::control_section(PESEL1), 8);
487 assert_eq!(super::control_section(PESEL2), 9);
488 assert_eq!(super::control_section(PESEL3), 5);
489 assert_eq!(super::control_section(PESEL4), 5);
490 assert_eq!(super::control_section(PESEL5), 4);
491 }
492
493 #[test]
494 fn day() {
495 assert_eq!(super::day(PESEL1), 04);
496 assert_eq!(super::day(PESEL2), 25);
497 assert_eq!(super::day(PESEL3), 01);
498 assert_eq!(super::day(PESEL4), 09);
499 assert_eq!(super::day(PESEL5), 24);
500 }
501
502 #[test]
503 fn month() {
504 assert_eq!(super::month(PESEL1), Some(09));
505 assert_eq!(super::month(PESEL2), Some(10));
506 assert_eq!(super::month(PESEL3), Some(01));
507 assert_eq!(super::month(PESEL4), Some(05));
508 assert_eq!(super::month(PESEL5), Some(03));
509 }
510
511 #[test]
512 fn invalid_month() {
513 assert_eq!(super::month(02990486168u64), None);
514 assert_eq!(super::month(02970486168u64), None);
515 assert_eq!(super::month(02930486168u64), None);
516 }
517
518 #[test]
519 fn year() {
520 assert_eq!(super::year(PESEL1), 2002);
521 assert_eq!(super::year(PESEL2), 2001);
522 assert_eq!(super::year(PESEL3), 1900);
523 assert_eq!(super::year(PESEL4), 2098);
524 assert_eq!(super::year(PESEL5), 1960);
525 }
526
527 #[test]
528 fn date_of_birth() {
529 assert_eq!(
530 super::date_of_birth(PESEL1),
531 NaiveDate::from_ymd_opt(2002, 09, 04)
532 );
533 assert_eq!(
534 super::date_of_birth(PESEL2),
535 NaiveDate::from_ymd_opt(2001, 10, 25)
536 );
537 assert_eq!(
538 super::date_of_birth(PESEL3),
539 NaiveDate::from_ymd_opt(1900, 01, 01)
540 );
541 assert_eq!(
542 super::date_of_birth(PESEL4),
543 NaiveDate::from_ymd_opt(2098, 05, 09)
544 );
545 assert_eq!(
546 super::date_of_birth(PESEL5),
547 NaiveDate::from_ymd_opt(1960, 03, 24)
548 );
549 }
550
551 #[test]
552 fn gender() {
553 assert_eq!(super::gender(PESEL1), Gender::Female);
554 assert_eq!(super::gender(PESEL2), Gender::Male);
555 assert_eq!(super::gender(PESEL3), Gender::Female);
556 assert_eq!(super::gender(PESEL4), Gender::Female);
557 assert_eq!(super::gender(PESEL5), Gender::Male);
558 }
559
560 #[test]
561 fn validate() {
562 assert_eq!(super::validate(PESEL1), Ok(()));
563 assert_eq!(super::validate(PESEL2), Ok(()));
564 assert_eq!(super::validate(PESEL3), Ok(()));
565 assert_eq!(super::validate(PESEL4), Ok(()));
566 assert_eq!(super::validate(PESEL5), Ok(()));
567 }
568
569 #[test]
570 fn invalid_pesels() {
571 assert_eq!(super::validate(4355u64), Err(ValidationError::TooShort(4)));
572 assert_eq!(
573 super::validate(435585930294485u64),
574 Err(ValidationError::TooLong(15))
575 );
576 assert_eq!(
577 super::validate(99990486167u64),
578 Err(ValidationError::BirthDate)
579 );
580 assert_eq!(
581 super::validate(02290486167u64),
582 Err(ValidationError::ControlDigit)
583 );
584 }
585}