1#![deny(
19 missing_docs,
20 future_incompatible,
21 nonstandard_style,
22 rust_2018_idioms,
23 missing_copy_implementations,
24 trivial_casts,
25 trivial_numeric_casts,
26 unsafe_code,
27 unused_qualifications
28)]
29mod dates;
30mod error;
31
32#[cfg(feature = "chrono")]
33use chrono::naive::NaiveDate;
34use std::convert::TryFrom;
35use std::fmt;
36
37use dates::days_in_month;
38pub use error::KennitalaError;
39
40const VALIDATION_DIGITS: [u8; 8] = [3, 2, 7, 6, 5, 4, 3, 2];
41
42const DAY_MASK: u32 = 0b00000000_00000000_00000000_00011111;
43const DAY_OFFSET: u32 = 0;
44const MONTH_MASK: u32 = 0b00000000_00000000_00000001_11100000;
45const MONTH_OFFSET: u32 = DAY_OFFSET + 5;
46const YEAR_MASK: u32 = 0b00000000_00000000_11111110_00000000;
47const YEAR_OFFSET: u32 = MONTH_OFFSET + 4;
48const REST_MASK: u32 = 0b00000011_11111111_00000000_00000000;
49const REST_OFFSET: u32 = YEAR_OFFSET + 7;
50const CENTURY_MASK: u32 = 0b00000100_00000000_00000000_00000000;
51const CENTURY_OFFSET: u32 = REST_OFFSET + 10;
52
53#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
55pub struct Kennitala {
56 internal: u32,
57}
58
59impl Kennitala {
60 pub fn new(kennitala: &str) -> Result<Self, KennitalaError> {
63 let only_numbers = kennitala
64 .chars()
65 .all(|c| ((c as u32) >= 48) && ((c as u32) <= 57));
66 if !only_numbers {
67 return Err(KennitalaError::InvalidNumber);
68 }
69
70 if kennitala.len() != 10 {
71 return Err(KennitalaError::InvalidLength(kennitala.len()));
73 }
74
75 let mut kennitala_array = <[u8; 10]>::try_from(kennitala.as_bytes()).unwrap();
76 for d in &mut kennitala_array {
77 *d -= 48;
80 }
81 Kennitala::from_slice(&kennitala_array)
82 }
83
84 pub fn from_u32(kennitala_u32: u32) -> Result<Self, KennitalaError> {
87 let mut kennitala = [0; 10];
88 kt_to_array(kennitala_u32, &mut kennitala)?;
89 Kennitala::from_slice(&kennitala)
90 }
91
92 fn from_slice(kennitala: &[u8; 10]) -> Result<Self, KennitalaError> {
95 debug_assert!(kennitala.iter().all(|d| *d <= 9));
96
97 let checksum_digit = kennitala[8];
98 let calculated_checksum_digit = calculate_checksum_digit(&kennitala);
99 if checksum_digit != calculated_checksum_digit {
100 return Err(KennitalaError::InvalidChecksum);
101 }
102
103 if ((kennitala[6] * 10) + kennitala[7]) < 20 {
104 return Err(KennitalaError::InvalidRandomDigits);
105 }
106
107 let century_digit = kennitala[9] as u32;
108 if !((century_digit == 0) || (century_digit == 9)) {
109 return Err(KennitalaError::InvalidCentury);
110 }
111 let year_offset = if century_digit == 0 { 2000 } else { 1900 };
112
113 let dob_month = (kennitala[2] * 10) as u32 + kennitala[3] as u32;
114 if (dob_month > 12) || (dob_month <= 0) {
115 return Err(KennitalaError::InvalidMonth);
116 }
117
118 let dob_year = (kennitala[4] * 10) as u32 + kennitala[5] as u32;
119
120 let dob_day = (kennitala[0] * 10) as u32 + kennitala[1] as u32;
121 if (dob_day > days_in_month(dob_month, dob_year + year_offset)) || (dob_day <= 0) {
122 return Err(KennitalaError::InvalidDay);
123 }
124
125 let rest = (kennitala[6] as u32) * 100 + (kennitala[7] * 10) as u32 + kennitala[8] as u32;
126
127 let mut value = dob_day << DAY_OFFSET;
128 value += dob_month << MONTH_OFFSET;
129 value += dob_year << YEAR_OFFSET;
130 value += rest << REST_OFFSET;
131 value += ((century_digit == 0) as u32) << CENTURY_OFFSET;
132
133 Ok(Self { internal: value })
134 }
135
136 #[inline]
138 pub fn get_day(&self) -> u32 {
139 let day = (self.internal & DAY_MASK) >> DAY_OFFSET;
140 debug_assert!((day >= 1) && (day <= 31));
141 day
142 }
143
144 #[inline]
146 pub fn get_month(&self) -> u32 {
147 let month = (self.internal & MONTH_MASK) >> MONTH_OFFSET;
148 debug_assert!((month >= 1) && (month <= 12));
149 month
150 }
151
152 #[inline]
154 pub fn get_short_year(&self) -> u32 {
155 let short_year = (self.internal & YEAR_MASK) >> YEAR_OFFSET;
156 debug_assert!(short_year <= 99);
157 short_year
158 }
159
160 #[inline]
162 pub fn get_year(&self) -> u32 {
163 let offset = if self.get_century_bit() == 0 {
164 1900
165 } else {
166 2000
167 };
168 self.get_short_year() + offset
169 }
170
171 #[inline]
174 fn get_century_bit(&self) -> u32 {
175 let bit = (self.internal & CENTURY_MASK) >> CENTURY_OFFSET;
176 debug_assert!((bit == 0) || (bit == 1));
177 bit
178 }
179
180 #[inline]
182 pub fn get_short_century(&self) -> u32 {
183 if self.get_century_bit() == 0 {
184 9
185 } else {
186 0
187 }
188 }
189
190 #[inline]
193 pub fn get_randoms(&self) -> u32 {
194 let randoms = (self.internal & REST_MASK) >> REST_OFFSET;
195 debug_assert!((randoms >= 20) && (randoms <= 999));
196 randoms
197 }
198
199 #[cfg(feature = "chrono")]
201 pub fn get_birthday(&self) -> NaiveDate {
202 NaiveDate::from_ymd(self.get_year() as i32, self.get_month(), self.get_day())
203 }
204}
205
206impl fmt::Display for Kennitala {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 write!(
209 f,
210 "{:02}{:02}{:02}{:03}{}",
211 self.get_day(),
212 self.get_month(),
213 self.get_short_year(),
214 self.get_randoms(),
215 self.get_short_century()
216 )
217 }
218}
219
220fn kt_to_array(kt_integer: u32, array: &mut [u8; 10]) -> Result<(), KennitalaError> {
221 let mut n = kt_integer;
222 let mut i = 0;
223 while n > 0 {
224 let digit = n % 10;
225 debug_assert!(digit <= 9);
226 array[9 - i] = digit as u8;
227 n /= 10;
228 i += 1
229 }
230 if i < 9 {
231 Err(KennitalaError::InvalidLength(i))
232 } else {
233 Ok(())
234 }
235}
236
237fn calculate_checksum_digit(kennitala: &[u8; 10]) -> u8 {
241 let mut sum: u32 = 0;
242 for i in 0..8 {
243 sum += (kennitala[i] * VALIDATION_DIGITS[i]) as u32;
244 }
245 let sum_mod_11 = sum % 11;
246 let digit = if sum_mod_11 == 0 { 0 } else { 11 - sum_mod_11 };
247 debug_assert!(digit <= 10);
248 digit as u8
249}
250
251#[cfg(test)]
252mod tests {
253
254 use super::*;
255 use std::string::ToString;
256
257 #[test]
258 fn my_own_kennitala() {
259 let my_kennitala = Kennitala::new("3110002920").unwrap();
260 assert_eq!(my_kennitala.get_day(), 31);
261 assert_eq!(my_kennitala.get_month(), 10);
262 assert_eq!(my_kennitala.get_short_year(), 0);
263 assert_eq!(my_kennitala.get_short_century(), 0);
264 assert_eq!(my_kennitala.get_randoms(), 292);
265 assert_eq!(my_kennitala.get_year(), 2000);
266 #[cfg(feature = "chrono")]
267 {
268 let my_birthday = NaiveDate::from_ymd(2000, 10, 31);
269 assert_eq!(my_kennitala.get_birthday(), my_birthday);
270 }
271 assert_eq!(my_kennitala.to_string(), "3110002920");
272 }
273
274 #[test]
275 fn my_moms_kennitala() {
276 let my_moms_kennitala = Kennitala::new("1703715939").unwrap();
277 assert_eq!(my_moms_kennitala.get_day(), 17);
278 assert_eq!(my_moms_kennitala.get_month(), 03);
279 assert_eq!(my_moms_kennitala.get_short_year(), 71);
280 assert_eq!(my_moms_kennitala.get_short_century(), 9);
281 assert_eq!(my_moms_kennitala.get_randoms(), 593);
282 assert_eq!(my_moms_kennitala.get_year(), 1971);
283 #[cfg(feature = "chrono")]
284 {
285 let my_moms_birthday = NaiveDate::from_ymd(1971, 03, 17);
286 assert_eq!(my_moms_kennitala.get_birthday(), my_moms_birthday);
287 }
288 assert_eq!(my_moms_kennitala.to_string(), "1703715939");
289 }
290
291 #[test]
292 fn made_up_kennitala() {
293 let kt = Kennitala::new("0311203149").unwrap();
294 assert_eq!(kt.get_day(), 3);
295 assert_eq!(kt.get_month(), 11);
296 assert_eq!(kt.get_short_year(), 20);
297 assert_eq!(kt.get_short_century(), 9);
298 assert_eq!(kt.get_randoms(), 314);
299 assert_eq!(kt.get_year(), 1920);
300 #[cfg(feature = "chrono")]
301 {
302 let my_moms_birthday = NaiveDate::from_ymd(1920, 11, 3);
303 assert_eq!(kt.get_birthday(), my_moms_birthday);
304 }
305 assert_eq!(kt.to_string(), "0311203149");
306 }
307
308 #[test]
309 fn max_u32() {
310 let kt = Kennitala::new(&std::u32::MAX.to_string());
311 assert!(kt.is_err());
312 }
313
314 #[test]
315 fn failed_fuzz_1() {
316 let kt = Kennitala::new("3999999999");
317 assert!(kt.is_err());
318 }
319
320 #[test]
321 fn failed_fuzz_2() {
322 let kt = Kennitala::new("9999");
323 assert!(kt.is_err());
324 }
325
326 #[test]
327 fn failed_fuzz_3() {
328 let kt = Kennitala::new("01011413300");
329 assert!(kt.is_err());
330 }
331}