File Coverage

blib/lib/Person/ID/CZ/RC/Generator.pm
Criterion Covered Total %
statement 80 80 100.0
branch 32 32 100.0
condition 3 3 100.0
subroutine 14 14 100.0
pod 2 2 100.0
total 131 131 100.0


line stmt bran cond sub pod time code
1             package Person::ID::CZ::RC::Generator;
2              
3 4     4   147554 use strict;
  4         7  
  4         166  
4 4     4   20 use warnings;
  4         6  
  4         203  
5              
6 4     4   2201 use Class::Utils qw(set_params);
  4         46239  
  4         93  
7 4     4   4282 use DateTime;
  4         2251316  
  4         223  
8 4     4   47 use English qw(-no_match_vars);
  4         8  
  4         30  
9 4     4   1720 use Error::Pure qw(err);
  4         17  
  4         264  
10 4     4   22 use List::Util 1.33 qw(none);
  4         133  
  4         268  
11 4     4   2455 use Random::Day;
  4         269377  
  4         139  
12 4     4   31 use Readonly;
  4         7  
  4         3276  
13              
14             # Constants.
15             Readonly::Scalar our $EMPTY_STR => q{};
16             Readonly::Scalar our $YEAR_FROM => 1855;
17             Readonly::Scalar our $YEAR_TO => 2054;
18              
19             our $VERSION = 0.06;
20              
21             # Constructor.
22             sub new {
23 16     16 1 795932 my ($class, @params) = @_;
24              
25             # Create object.
26 16         45 my $self = bless {}, $class;
27              
28             # Alternate flag.
29 16         56 $self->{'alternate'} = undef;
30              
31             # Day.
32 16         38 $self->{'day'} = undef;
33              
34             # Month.
35 16         35 $self->{'month'} = undef;
36              
37             # RC number separator.
38 16         41 $self->{'rc_sep'} = $EMPTY_STR;
39              
40             # Serial.
41 16         35 $self->{'serial'} = undef;
42              
43             # Sex.
44 16         44 $self->{'sex'} = undef;
45              
46             # Year.
47 16         29 $self->{'year'} = undef;
48              
49             # Process parameters.
50 16         74 set_params($self, @params);
51              
52             # Check RC separator.
53 14 100   15   302 if (none { $self->{'rc_sep'} eq $_ } ('', '/')) {
  15         63  
54 1         5 err "Parameter 'rc_sep' has bad value.";
55             }
56              
57             # Check serial part of RC.
58 13 100       66 if (defined $self->{'serial'}) {
59 5 100       53 if ($self->{'serial'} !~ m/^\d+$/ms) {
    100          
    100          
60 1         6 err "Parameter 'serial' isn't number.";
61             } elsif ($self->{'serial'} < 1) {
62 1         6 err "Parameter 'serial' is lesser than 1.";
63             } elsif ($self->{'serial'} > 999) {
64 1         5 err "Parameter 'serial' is greater than 999.";
65             }
66             }
67              
68             # Check sex.
69 10 100 100     44 if (defined $self->{'sex'}
70 5     5   18 && none { $self->{'sex'} eq $_ } qw(male female)) {
71              
72 1         5 err "Parameter 'sex' has bad value.";
73             }
74              
75             # Check year.
76 9 100       28 if (defined $self->{'year'}) {
77 3 100       16 if ($self->{'year'} < $YEAR_FROM) {
    100          
78 1         9 err "Parameter 'year' is lesser than $YEAR_FROM.";
79             } elsif ($self->{'year'} > $YEAR_TO) {
80 1         7 err "Parameter 'year' is greater than $YEAR_TO.";
81             }
82             }
83              
84             # Object.
85 7         32 return $self;
86             }
87              
88             # Get rc.
89             sub rc {
90 12     12 1 1820 my $self = shift;
91              
92             # Construct date.
93             my $date = Random::Day->new(
94             'day' => $self->{'day'},
95             'dt_from' => DateTime->new(
96             'day' => 1,
97             'month' => 1,
98             'year' => $YEAR_FROM,
99             ),
100             'dt_to' => DateTime->new(
101             'day' => 31,
102             'month' => 12,
103             'year' => $YEAR_TO,
104             ),
105             'month' => $self->{'month'},
106 12         57 'year' => $self->{'year'},
107             )->get;
108              
109             # Sex.
110 12         96034 my $sex = $self->{'sex'};
111 12 100       36 if (! defined $sex) {
112 7 100       28 $sex = int(rand(2)) ? 'male' : 'female';
113             }
114              
115             # Get month part.
116 12         33 my $month = $date->month;
117 12 100       64 if ($sex eq 'female') {
118 8         17 $month += 50;
119             }
120              
121             # Alternate number.
122 12 100       29 if ($self->{'alternate'}) {
123 5         7 $month += 20;
124             }
125              
126             # Construct date part.
127 12         29 my $date_part = (sprintf '%02d%02d%02d', (substr $date->year, 2), $month, $date->day);
128              
129             # Add serial.
130 12         184 my $serial = $self->{'serial'};
131 12 100       29 if (! defined $serial) {
132 11         21 $serial = int(rand(1000)) + 1;
133             }
134 12         25 my $serial_part = sprintf '%03d', $serial;
135              
136             # Add checksum.
137 12 100       22 if ($date->year > 1954) {
138 7         73 $serial_part = $self->_checksum($date_part, $serial_part);
139             }
140              
141             # Construct rc.
142 12         44 my $rc = $date_part.$self->{'rc_sep'}.$serial_part;
143              
144             # Return $rc.
145 12         86 return $rc;
146             }
147              
148             # Compute checksum.
149             sub _checksum {
150 7     7   20 my ($self, $date_part, $serial_part) = @_;
151 7         12 my $num = $date_part.$serial_part;
152 7         14 my $num_11 = $num % 11;
153 7         9 my $checksum;
154 7 100       15 if ($num_11 == 10) {
155 1         3 $checksum = 0;
156             } else {
157 6         8 $checksum = $num_11;
158             }
159 7         22 return $serial_part.$checksum;
160             }
161              
162             1;
163              
164             __END__
165              
166             =pod
167              
168             =encoding utf8
169              
170             =head1 NAME
171              
172             Person::ID::CZ::RC::Generator - Perl class for Czech RC identification generation.
173              
174             =head1 SYNOPSIS
175              
176             use Person::ID::CZ::RC::Generator;
177              
178             my $obj = Person::ID::CZ::RC::Generator->new(%params);
179             my $rc = $obj->rc;
180              
181             =head1 METHODS
182              
183             =head2 C<new>
184              
185             my $obj = Person::ID::CZ::RC::Generator->new(%params);
186              
187             Constructor.
188              
189             =over 8
190              
191             =item * C<alternate>
192              
193             Alternate flag.
194              
195             Default value is undef.
196              
197             =item * C<day>
198              
199             Day.
200              
201             Default value is undef.
202              
203             =item * C<month>
204              
205             Month.
206              
207             Default value is undef.
208              
209             =item * C<rc_sep>
210              
211             RC number separator.
212              
213             Possible values are:
214              
215             =over
216              
217             =item * empty string
218              
219             =item * /
220              
221             =back
222              
223             Default value is empty string.
224              
225             =item * C<serial>
226              
227             Serial number from 1 to 999.
228              
229             Default value is undef.
230              
231             =item * C<sex>
232              
233             Sex.
234              
235             Possible values are:
236              
237             =over
238              
239             =item * male
240              
241             =item * female
242              
243             =back
244              
245             Default value is undef.
246              
247             =item * C<year>
248              
249             Year.
250              
251             Possible values are between 1946 and 2054.
252              
253             Default value is undef.
254              
255             =back
256              
257             Returns instance of object.
258              
259             =head2 C<rc>
260              
261             my $rc = $obj->rc;
262              
263             Get rc identification.
264              
265             Returns string.
266              
267             =head1 ERRORS
268              
269             new():
270             Parameter 'rc_sep' has bad value.
271             Parameter 'serial' is greater than 999.
272             Parameter 'serial' is lesser than 1.
273             Parameter 'serial' isn't number.
274             Parameter 'sex' has bad value.
275             Parameter 'year' is greater than 2054.
276             Parameter 'year' is lesser than 1855.
277             From Class::Utils::set_params():
278             Unknown parameter '%s'.
279              
280             =head1 EXAMPLE1
281              
282             =for comment filename=gen_rc_with_checksum.pl
283              
284             use strict;
285             use warnings;
286              
287             use Person::ID::CZ::RC::Generator;
288              
289             # Object.
290             my $obj = Person::ID::CZ::RC::Generator->new(
291             'day' => 1,
292             'month' => 5,
293             'rc_sep' => '/',
294             'serial' => 133,
295             'sex' => 'male',
296             'year' => 1984,
297             );
298              
299             # Print out.
300             print "Personal number: ".$obj->rc."\n";
301              
302             # Output:
303             # Personal number: 840501/1330
304              
305             =head1 EXAMPLE2
306              
307             =for comment filename=gen_rc.pl
308              
309             use strict;
310             use warnings;
311              
312             use Person::ID::CZ::RC::Generator;
313              
314             # Object.
315             my $obj = Person::ID::CZ::RC::Generator->new(
316             'day' => 1,
317             'month' => 5,
318             'rc_sep' => '/',
319             'serial' => 133,
320             'sex' => 'male',
321             'year' => 1952,
322             );
323              
324             # Print out.
325             print "Personal number: ".$obj->rc."\n";
326              
327             # Output:
328             # Personal number: 520501/133
329              
330             =head1 EXAMPLE3
331              
332             =for comment filename=gen_rc_default.pl
333              
334             use strict;
335             use warnings;
336              
337             use Person::ID::CZ::RC::Generator;
338              
339             # Object.
340             my $obj = Person::ID::CZ::RC::Generator->new(
341             'rc_sep' => '/',
342             );
343              
344             # Print out.
345             print "Personal number: ".$obj->rc."\n";
346              
347             # Output like:
348             # Personal number: qr{\d\d\d\d\d\d\/\d\d\d\d?}
349              
350             =head1 DEPENDENCIES
351              
352             L<Class::Utils>,
353             L<DateTime>,
354             L<English>,
355             L<Error::Pure>,
356             L<List::Util>,
357             L<Random::Day>,
358             L<Readonly>.
359              
360             =head1 SEE ALSO
361              
362             =over
363              
364             =item L<Business::DK::CPR>
365              
366             Danish CPR (SSN) number generator/validator
367              
368             =item L<No::PersonNr>
369              
370             Check Norwegian Social security numbers
371              
372             =item L<Person::ID::CZ::RC>
373              
374             Perl class for Czech RC identification.
375              
376             =item L<Se::PersonNr>
377              
378             Module for validating and generating a Swedish personnummer.
379              
380             =back
381              
382             =head1 REPOSITORY
383              
384             L<https://github.com/michal-josef-spacek/Person::ID::CZ::RC::Generator>
385              
386             =head1 AUTHOR
387              
388             Michal Josef Špaček L<mailto:skim@cpan.org>
389              
390             L<http://skim.cz>
391              
392             =head1 LICENSE AND COPYRIGHT
393              
394             © Michal Josef Špaček 2013-2023
395              
396             BSD 2-Clause License
397              
398             =head1 VERSION
399              
400             0.06
401              
402             =cut