#![deny(
missing_docs,
future_incompatible,
nonstandard_style,
rust_2018_idioms,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unused_qualifications
)]
mod dates;
mod error;
#[cfg(feature = "chrono")]
use chrono::naive::NaiveDate;
use std::fmt;
use dates::days_in_month;
pub use error::KennitalaError;
const VALIDATION_DIGITS: [u32; 8] = [3, 2, 7, 6, 5, 4, 3, 2];
const DAY_MASK: u32 = 0b00000000_00000000_00000000_00011111;
const DAY_OFFSET: u32 = 0;
const MONTH_MASK: u32 = 0b00000000_00000000_00000001_11100000;
const MONTH_OFFSET: u32 = DAY_OFFSET + 5;
const YEAR_MASK: u32 = 0b00000000_00000000_11111110_00000000;
const YEAR_OFFSET: u32 = MONTH_OFFSET + 4;
const REST_MASK: u32 = 0b00000011_11111111_00000000_00000000;
const REST_OFFSET: u32 = YEAR_OFFSET + 7;
const CENTURY_MASK: u32 = 0b00000100_00000000_00000000_00000000;
const CENTURY_OFFSET: u32 = REST_OFFSET + 10;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Kennitala {
internal: u32,
}
impl Kennitala {
pub fn new(kennitala: &str) -> Result<Self, KennitalaError> {
let only_numbers = kennitala.chars().all(char::is_numeric);
if !only_numbers {
return Err(KennitalaError::InvalidNumber);
}
if kennitala.len() != 10 {
return Err(KennitalaError::InvalidLength(kennitala.len()));
}
let kennitala_u32 = match kennitala.parse::<u32>() {
Ok(n) => n,
Err(_) => {
return Err(KennitalaError::InvalidNumber);
}
};
Kennitala::from_u32(kennitala_u32)
}
pub fn from_u32(kennitala: u32) -> Result<Self, KennitalaError> {
let mut kt_array = [0; 10];
kt_to_array(kennitala, &mut kt_array)?;
let checksum_digit = kt_array[8];
let calculated_checksum_digit = calculate_checksum_digit(&kt_array);
if checksum_digit != calculated_checksum_digit {
return Err(KennitalaError::InvalidChecksum);
}
if ((kt_array[6] * 10) + kt_array[7]) < 20 {
return Err(KennitalaError::InvalidRandomDigits);
}
let century_digit = kt_array[9];
if !((century_digit == 0) || (century_digit == 9)) {
return Err(KennitalaError::InvalidCentury);
}
let year_offset = if century_digit == 0 { 2000 } else { 1900 };
let dob_month = kt_array[2] * 10 + kt_array[3];
if (dob_month > 12) || (dob_month <= 0) {
return Err(KennitalaError::InvalidMonth);
}
let dob_year = (kt_array[4] * 10) + kt_array[5];
let dob_day = kt_array[0] * 10 + kt_array[1];
if (dob_day > days_in_month(dob_month, dob_year + year_offset)) || (dob_day <= 0) {
return Err(KennitalaError::InvalidDay);
}
let rest = (kt_array[6] * 100) + (kt_array[7] * 10) + kt_array[8];
let mut value = dob_day << DAY_OFFSET;
value += dob_month << MONTH_OFFSET;
value += dob_year << YEAR_OFFSET;
value += rest << REST_OFFSET;
value += ((century_digit == 0) as u32) << CENTURY_OFFSET;
Ok(Self { internal: value })
}
#[inline]
pub fn get_day(&self) -> u32 {
let day = (self.internal & DAY_MASK) >> DAY_OFFSET;
debug_assert!((day >= 1) && (day <= 31));
day
}
#[inline]
pub fn get_month(&self) -> u32 {
let month = (self.internal & MONTH_MASK) >> MONTH_OFFSET;
debug_assert!((month >= 1) && (month <= 12));
month
}
#[inline]
pub fn get_short_year(&self) -> u32 {
let short_year = (self.internal & YEAR_MASK) >> YEAR_OFFSET;
debug_assert!(short_year <= 99);
short_year
}
#[inline]
pub fn get_year(&self) -> u32 {
let offset = if self.get_century_bit() == 0 {
1900
} else {
2000
};
self.get_short_year() + offset
}
#[inline]
fn get_century_bit(&self) -> u32 {
let bit = (self.internal & CENTURY_MASK) >> CENTURY_OFFSET;
debug_assert!((bit == 0) || (bit == 1));
bit
}
#[inline]
pub fn get_short_century(&self) -> u32 {
if self.get_century_bit() == 0 {
9
} else {
0
}
}
#[inline]
pub fn get_randoms(&self) -> u32 {
let randoms = (self.internal & REST_MASK) >> REST_OFFSET;
debug_assert!((randoms >= 20) && (randoms <= 999));
randoms
}
#[cfg(feature = "chrono")]
pub fn get_birthday(&self) -> NaiveDate {
NaiveDate::from_ymd(self.get_year() as i32, self.get_month(), self.get_day())
}
}
impl fmt::Display for Kennitala {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:02}{:02}{:02}{:03}{}",
self.get_day(),
self.get_month(),
self.get_short_year(),
self.get_randoms(),
self.get_short_century()
)
}
}
fn kt_to_array(kt_integer: u32, array: &mut [u32; 10]) -> Result<(), KennitalaError> {
let mut n = kt_integer;
let mut i = 0;
while n > 0 {
let digit = n % 10;
debug_assert!(digit <= 9);
array[9 - i] = digit;
n /= 10;
i += 1
}
if i < 9 {
Err(KennitalaError::InvalidLength(i))
} else {
Ok(())
}
}
fn calculate_checksum_digit(kt_array: &[u32; 10]) -> u32 {
let mut sum = 0;
for i in 0..8 {
sum += kt_array[i] * VALIDATION_DIGITS[i];
}
let sum_mod_11 = sum % 11;
let digit = if sum_mod_11 == 0 { 0 } else { 11 - sum_mod_11 };
debug_assert!(digit <= 10);
digit
}
#[cfg(test)]
mod tests {
use super::*;
use std::string::ToString;
#[test]
fn my_own_kennitala() {
let my_kennitala = Kennitala::new("3110002920").unwrap();
assert_eq!(my_kennitala.get_day(), 31);
assert_eq!(my_kennitala.get_month(), 10);
assert_eq!(my_kennitala.get_short_year(), 0);
assert_eq!(my_kennitala.get_short_century(), 0);
assert_eq!(my_kennitala.get_randoms(), 292);
assert_eq!(my_kennitala.get_year(), 2000);
#[cfg(feature = "chrono")]
{
let my_birthday = NaiveDate::from_ymd(2000, 10, 31);
assert_eq!(my_kennitala.get_birthday(), my_birthday);
}
assert_eq!(my_kennitala.to_string(), "3110002920");
}
#[test]
fn my_moms_kennitala() {
let my_moms_kennitala = Kennitala::new("1703715939").unwrap();
assert_eq!(my_moms_kennitala.get_day(), 17);
assert_eq!(my_moms_kennitala.get_month(), 03);
assert_eq!(my_moms_kennitala.get_short_year(), 71);
assert_eq!(my_moms_kennitala.get_short_century(), 9);
assert_eq!(my_moms_kennitala.get_randoms(), 593);
assert_eq!(my_moms_kennitala.get_year(), 1971);
#[cfg(feature = "chrono")]
{
let my_moms_birthday = NaiveDate::from_ymd(1971, 03, 17);
assert_eq!(my_moms_kennitala.get_birthday(), my_moms_birthday);
}
assert_eq!(my_moms_kennitala.to_string(), "1703715939");
}
#[test]
fn made_up_kennitala() {
let kt = Kennitala::new("0311203149").unwrap();
assert_eq!(kt.get_day(), 3);
assert_eq!(kt.get_month(), 11);
assert_eq!(kt.get_short_year(), 20);
assert_eq!(kt.get_short_century(), 9);
assert_eq!(kt.get_randoms(), 314);
assert_eq!(kt.get_year(), 1920);
#[cfg(feature = "chrono")]
{
let my_moms_birthday = NaiveDate::from_ymd(1920, 11, 3);
assert_eq!(kt.get_birthday(), my_moms_birthday);
}
assert_eq!(kt.to_string(), "0311203149");
}
#[test]
fn max_u32() {
let kt = Kennitala::new(&std::u32::MAX.to_string());
assert!(kt.is_err());
}
#[test]
fn failed_fuzz_1() {
let kt = Kennitala::new("3999999999");
assert!(kt.is_err());
}
#[test]
fn failed_fuzz_2() {
let kt = Kennitala::new("9999");
assert!(kt.is_err());
}
#[test]
fn failed_fuzz_3() {
let kt = Kennitala::new("01011413300");
assert!(kt.is_err());
}
}