File Coverage

blib/lib/Business/IS/PIN.pm
Criterion Covered Total %
statement 33 44 75.0
branch 8 16 50.0
condition n/a
subroutine 11 13 84.6
pod 8 8 100.0
total 60 81 74.0


line stmt bran cond sub pod time code
1             package Business::IS::PIN;
2             our $VERSION = '0.06';
3 8     8   242341 use strict;
  8         21  
  8         323  
4              
5 8     8   43 use Exporter 'import';
  8         14  
  8         296  
6 8     8   58 use List::Util qw(sum);
  8         15  
  8         1726  
7              
8             our %EXPORT_TAGS = (
9             all => [ qw<
10             valid checksum
11             person company
12             year month day
13             > ],
14             );
15              
16             our @EXPORT_OK = @{ $EXPORT_TAGS{ all } };
17              
18             =encoding utf8
19              
20             =head1 NAME
21              
22             Business::IS::PIN - Validate and process Icelandic PIN numbers (Icelandic: kennitElur)
23              
24             =head1 SYNOPSIS
25              
26             # Functional interface
27             use Business::IS::PIN qw(:all);
28              
29             my $kt = '0902862349'; # Yours truly
30              
31             if (valid $kt) {
32             # Extract YYYY-MM-DD
33             my $year = year $kt;
34             my $month = month $kt
35             my $day = day $kt;
36              
37             # ...
38             }
39              
40             # OO interface that doesn't pollute your namespace
41             use Business::IS::PIN;
42              
43             my $kt = Business::IS::PIN->new('0902862349');
44              
45             if ($kt->valid and $kt->person) {
46             printf "You are a Real Boy(TM) born on %d-%d-%d\n",
47             $kt->year, $kt->month, $kt->day;
48             } elsif ($kt->valid and $kt->company) {
49             warn "Begone, you spawn of capitalism!";
50             } else {
51             die "EEEK!";
52             }
53              
54             =head1 DESCRIPTION
55              
56             This module provides an interface for validating the syntax of and
57             extracting information from Icelandic personal identification numbers
58             (Icelandic: I). These are unique 10-digit numbers assigned
59             to all Icelandic citizens, foreign citizens with permanent residence
60             and corporations (albeit with a slightly different format, L
61             below|/Format>).
62              
63             =head1 LIMITATIONS
64              
65             The National Statistical Institute of Iceland (Icelandic: I)
66             - a goverment organization - handles the assignment of these
67             numbers. This module will tell you whether the formatting of a given
68             number is valid, not whether it was actually assigned to someone. For
69             that you need to pay through the nose to the NSIoI, or cleverly leech
70             on someone who is:)
71              
72             =cut
73              
74 8     8   19210 use overload '""' => sub { ${ +shift } };
  8     0   7664  
  8         87  
  0         0  
  0         0  
75              
76             =head1 EXPORT
77              
78             None by default, every function in this package except for L can
79             be exported individually, B<:all> exports them all.
80              
81             =head1 METHODS & FUNCTIONS
82              
83             =head2 new
84              
85             Optional constructor which takes a valid kennitala or a fragment of
86             one as its argument. Returns an object that L to
87             whatever string is provided.
88              
89             If a fragment is provided functions in this package that need
90             information from the omitted part (such as L) will not work.
91              
92             =cut
93              
94             sub new
95             {
96 47     47 1 23349 my ( $pkg, $kt ) = @_;
97              
98 47         167 bless \$kt => $pkg;
99             }
100              
101             =head2 valid
102              
103             Takes a 9-10 character kennitala and returns true if its checksum is
104             valid, false otherwise.
105              
106             =cut
107              
108             sub checksum; # pre-declare to duck error
109             sub valid
110             {
111 0 0   0 1 0 my $kt = ref $_[0] ? ${$_[0]} : $_[0];
  0         0  
112              
113 0         0 my $summed = substr $kt, 0, 9;
114 0         0 my $unsummed = substr $kt, 0, 8;
115 0         0 my $sum = checksum $unsummed;
116              
117 0         0 $summed eq $unsummed . $sum;
118             }
119              
120             =head2 checksum
121              
122             Takes a the first 8 characters of a kennitala and returns the 9th
123             checksum digit.
124              
125             =cut
126              
127             sub checksum
128             {
129 2 50   2 1 15 my $kt = ref $_[0] ? ${$_[0]} : $_[0];
  0         0  
130 2         13 my @num = split //, $kt;
131              
132 2         28 my $sum =
133             sum
134             # Day
135             3 * $num[0],
136             2 * $num[1],
137             # Month
138             7 * $num[2],
139             6 * $num[3],
140             # Year
141             5 * $num[4],
142             4 * $num[5],
143             # Serial
144             3 * $num[6],
145             2 * $num[7];
146              
147 2         17 (11 - $sum % 11) % 11;
148             }
149              
150             =head2 person
151              
152             Returns true if the kennitala belongs to an individual, false
153             otherwise.
154              
155             =cut
156              
157             sub person
158             {
159 5 50   5 1 26 my $kt = ref $_[0] ? ${$_[0]} : $_[0];
  0         0  
160              
161 5         40 $kt =~ / ^ (?:[0-2]|3[01]) /x;
162             }
163              
164             =head2 company
165              
166             Returns true if the kennitala belongs to a company, false
167             otherwise.
168              
169             =cut
170              
171             sub company
172             {
173 4 50   4 1 15 my $kt = ref $_[0] ? ${$_[0]} : $_[0];
  0         0  
174              
175 4         25 $kt =~ / ^ (?:3[2-9]|[45]) /x
176             }
177              
178             =head2 year
179              
180             Return the four-digit year part of the kennitala. For this function to
181             work a complete 10-digit number must have been provided.
182              
183             =cut
184              
185             sub year
186             {
187 3 50   3 1 18 my $kt = ref $_[0] ? ${$_[0]} : $_[0];
  3         52  
188 3         5 my $yy = substr $kt, 4, 2;
189 3         5 my $c = substr $kt, 9, 1;
190 3 100       27 ($c == 0 ? 2000 : 1000) + ($c . $yy);
191             }
192              
193             =head2 month
194              
195             Return the two-digit month part of the kennitala.
196              
197             =cut
198              
199             sub month
200             {
201 12 50   12 1 55 my $kt = ref $_[0] ? ${$_[0]} : $_[0];
  12         67  
202              
203 12         51 substr $kt, 2, 2;
204             }
205              
206             =head2 day
207              
208             Return the two-digit day part of the kennitala.
209              
210             =cut
211              
212             sub day
213             {
214 31 50   31 1 159 my $kt = ref $_[0] ? ${$_[0]} : $_[0];
  31         93  
215              
216 31         127 substr $kt, 0, 2;
217             }
218              
219             =head1 Format
220              
221             The format of an IPIN is relatively simple:
222              
223             DDMMYY-SSDC
224              
225             Where B is a two-digit day, month and year, B is a
226             pseudo-random serial number, B is the check digit computed from
227             preceding part and B stands for the century and is not included
228             when calculating the checksum digit - 8 for 1800s, and 9 and 0 for the
229             1900s and 2000s respectively. It is customary to place a dash between
230             the first 6 and last 4 digits when formatting the number.
231              
232             To compute the check digit from a given IPIN B<0902862349> the
233             following algorithm is used:
234              
235             0 9 0 2 8 6 2 3 4 9
236             * 3 2 7 6 5 4 3 2
237             = 0 + 18 + 0 + 12 + 40 + 24 + 6 + 6 = 106
238              
239             checkdigit = (11 - 106 % 11) % 11
240              
241             I.e. each digit B<1..8> is multiplied by B<3..2>, B<7..2> respectively
242             and the result of each multiplication added together to get
243             B<106>. B<106> is then used as the divend in a modulo operation with
244             11 as the divisor to get B<7> which is then subtracted from B<11> to
245             get B<4> - in this case the check digit, if the result had been 11 a
246             second modulo operation 11 % 11 would have left us with B<0>.
247              
248             =head1 CAVEATS
249              
250             Only supports identity numbers assigned between the years
251             1800-2099. Please resurrect the author when this becomes an issue.
252              
253             =head1 BUGS
254              
255             Please report any bugs that aren't already listed at
256             L to
257             L
258              
259             =head1 SEE ALSO
260              
261             L
262              
263             =head1 AUTHOR
264              
265             Evar ArnfjErE Bjarmason
266              
267             =head1 LICENSE
268              
269             This program is free software; you can redistribute it and/or modify it
270             under the same terms as Perl itself.
271              
272             =cut
273              
274             1;