line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Validate::CodiceFiscale; |
2
|
3
|
|
|
3
|
|
163524
|
use v5.24; |
|
3
|
|
|
|
|
37
|
|
3
|
3
|
|
|
3
|
|
16
|
use Carp; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
183
|
|
4
|
3
|
|
|
3
|
|
643
|
use experimental qw< signatures >; |
|
3
|
|
|
|
|
3558
|
|
|
3
|
|
|
|
|
36
|
|
5
|
|
|
|
|
|
|
{ our $VERSION = '0.003002' } |
6
|
|
|
|
|
|
|
|
7
|
3
|
|
|
3
|
|
561
|
use List::Util 'sum'; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
366
|
|
8
|
3
|
|
|
3
|
|
1571
|
use Time::Local 'timegm'; |
|
3
|
|
|
|
|
6885
|
|
|
3
|
|
|
|
|
178
|
|
9
|
3
|
|
|
3
|
|
2156
|
use JSON::PP 'decode_json'; |
|
3
|
|
|
|
|
48223
|
|
|
3
|
|
|
|
|
191
|
|
10
|
3
|
|
|
3
|
|
24
|
use Exporter 'import'; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
9593
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our @EXPORT_OK = qw< assert_valid_cf decode_cf is_valid_cf validate_cf r >; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# PUBLIC interface |
15
|
|
|
|
|
|
|
|
16
|
17
|
|
|
17
|
1
|
10032
|
sub assert_valid_cf ($cf, %options) { |
|
17
|
|
|
|
|
33
|
|
|
17
|
|
|
|
|
30
|
|
|
17
|
|
|
|
|
25
|
|
17
|
17
|
100
|
|
|
|
44
|
my $errors = validate_cf($cf, all_errors => 0, %options) or return; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
defined(my $ecb = $options{on_error}) |
20
|
14
|
50
|
|
|
|
215
|
or croak join ', ', $errors->@*; |
21
|
|
|
|
|
|
|
|
22
|
0
|
|
|
|
|
0
|
my $exception = $ecb->($errors->@*); |
23
|
0
|
|
|
|
|
0
|
die $exception; # just as a fallback, $ecb might throw by itself |
24
|
|
|
|
|
|
|
} ## end sub assert_valid_cf |
25
|
|
|
|
|
|
|
|
26
|
1
|
|
|
1
|
1
|
11391
|
sub decode_cf ($cf, %options) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
3
|
|
27
|
1
|
|
|
|
|
5
|
return _decode_and_validate($cf, %options, all_errors => 1); |
28
|
|
|
|
|
|
|
} |
29
|
|
|
|
|
|
|
|
30
|
17
|
|
|
17
|
1
|
9932
|
sub is_valid_cf ($cf, %options) { |
|
17
|
|
|
|
|
31
|
|
|
17
|
|
|
|
|
32
|
|
|
17
|
|
|
|
|
21
|
|
31
|
17
|
|
|
|
|
31
|
my $error = 0; |
32
|
17
|
|
|
14
|
|
108
|
_validate_cf($cf, $options{data}, sub { $error = 1; return 0 }); |
|
14
|
|
|
|
|
582
|
|
|
14
|
|
|
|
|
95
|
|
33
|
17
|
|
|
|
|
104
|
return !$error; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
|
36
|
34
|
|
|
34
|
1
|
6001
|
sub validate_cf ($cf, %options) { |
|
34
|
|
|
|
|
59
|
|
|
34
|
|
|
|
|
59
|
|
|
34
|
|
|
|
|
72
|
|
37
|
34
|
|
|
|
|
95
|
my $r = _decode_and_validate($cf, %options); |
38
|
34
|
|
50
|
|
|
92
|
my $errors = $r->{errors} // []; |
39
|
34
|
100
|
|
|
|
191
|
return scalar($errors->@*) ? $errors : undef; |
40
|
|
|
|
|
|
|
} ## end sub validate_cf |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# The following is useful for one-lines: |
43
|
|
|
|
|
|
|
# |
44
|
|
|
|
|
|
|
# $ perl -MValidate::CodiceFiscale=r -er bcadfe88a48h501p |
45
|
|
|
|
|
|
|
# |
46
|
0
|
|
|
0
|
1
|
0
|
sub r (@args) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
47
|
0
|
0
|
|
|
|
0
|
@args = @ARGV unless @args; |
48
|
0
|
|
|
|
|
0
|
my $i = 0; |
49
|
0
|
|
|
|
|
0
|
my $n = 0; |
50
|
0
|
|
|
|
|
0
|
for my $cf (@ARGV) { |
51
|
0
|
0
|
|
|
|
0
|
if (my $errors = validate_cf($cf)) { |
52
|
0
|
|
|
|
|
0
|
say "$i not ok - " . join(', ', $errors->@*); |
53
|
0
|
|
|
|
|
0
|
++$n; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
else { |
56
|
0
|
|
|
|
|
0
|
say "$i ok - $cf"; |
57
|
|
|
|
|
|
|
} |
58
|
0
|
|
|
|
|
0
|
++$i; |
59
|
|
|
|
|
|
|
} ## end for my $cf (@ARGV) |
60
|
0
|
0
|
|
|
|
0
|
return $n ? 1 : 0; |
61
|
|
|
|
|
|
|
} ## end sub r |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
exit r(@ARGV) unless caller(); # modulino |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# PRIVATE interface |
66
|
|
|
|
|
|
|
|
67
|
35
|
|
|
35
|
|
44
|
sub _decode_and_validate ($cf, %options) { |
|
35
|
|
|
|
|
58
|
|
|
35
|
|
|
|
|
51
|
|
|
35
|
|
|
|
|
53
|
|
68
|
35
|
|
100
|
|
|
130
|
my $data = $options{data} // undef; |
69
|
|
|
|
|
|
|
|
70
|
35
|
|
100
|
|
|
102
|
my $collect_all_errors = $options{all_errors} // 1; |
71
|
35
|
|
|
|
|
57
|
my @errors; |
72
|
31
|
|
|
31
|
|
51
|
my $callback = sub ($msg) { |
|
31
|
|
|
|
|
1271
|
|
|
31
|
|
|
|
|
56
|
|
73
|
31
|
|
|
|
|
58
|
push @errors, $msg; |
74
|
31
|
|
|
|
|
123
|
return $collect_all_errors; |
75
|
35
|
|
|
|
|
151
|
}; |
76
|
|
|
|
|
|
|
|
77
|
35
|
|
|
|
|
86
|
my $r = _validate_cf($cf, $data, $callback); |
78
|
35
|
|
|
|
|
106
|
$r->{errors} = \@errors; |
79
|
35
|
|
|
|
|
176
|
return $r; |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
52
|
|
|
52
|
|
80
|
sub _validate_cf ($cf, $data, $cb) { |
|
52
|
|
|
|
|
86
|
|
|
52
|
|
|
|
|
89
|
|
|
52
|
|
|
|
|
77
|
|
|
52
|
|
|
|
|
66
|
|
83
|
52
|
|
|
|
|
101
|
state $consonant = qr{(?imxs:[BCDFGHJKLMNPQRSTVWXYZ])}; |
84
|
52
|
|
|
|
|
77
|
state $vowel = qr{(?imxs:[AEIOU])}; |
85
|
52
|
|
|
|
|
388
|
state $namish = qr{(?imxs: |
86
|
|
|
|
|
|
|
$consonant $consonant $consonant # includes CCX, CXX, XXX |
87
|
|
|
|
|
|
|
| $consonant $consonant $vowel |
88
|
|
|
|
|
|
|
| $consonant $vowel $vowel |
89
|
|
|
|
|
|
|
| $consonant $vowel X |
90
|
|
|
|
|
|
|
| $vowel $vowel $vowel |
91
|
|
|
|
|
|
|
| $vowel $vowel X |
92
|
|
|
|
|
|
|
| $vowel X X |
93
|
|
|
|
|
|
|
)}; |
94
|
52
|
|
|
|
|
101
|
state $digitish = qr{(?imxs:[0-9LMNPQRSTUV])}; |
95
|
|
|
|
|
|
|
|
96
|
52
|
100
|
|
|
|
143
|
if (length($cf) != 16) { |
97
|
3
|
|
|
|
|
10
|
$cb->('invalid length'); |
98
|
3
|
|
|
|
|
7
|
return {}; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
49
|
|
|
|
|
119
|
$cf = uc($cf); |
102
|
49
|
|
|
|
|
284
|
my %portions = ( |
103
|
|
|
|
|
|
|
surname => substr($cf, 0, 3), |
104
|
|
|
|
|
|
|
name => substr($cf, 3, 3), |
105
|
|
|
|
|
|
|
date => substr($cf, 6, 5), |
106
|
|
|
|
|
|
|
place => substr($cf, 11, 4), |
107
|
|
|
|
|
|
|
checksum => substr($cf, 15, 1), |
108
|
|
|
|
|
|
|
); |
109
|
49
|
|
|
|
|
143
|
my $retval = {portions => \%portions}; |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
return $retval |
112
|
49
|
100
|
100
|
|
|
694
|
if $portions{name} !~ m{\A$namish\z}mxs |
113
|
|
|
|
|
|
|
&& !$cb->('invalid name'); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
return $retval |
116
|
47
|
100
|
100
|
|
|
518
|
if $portions{surname} !~ m{\A$namish\z}mxs |
117
|
|
|
|
|
|
|
&& !$cb->('invalid surname'); |
118
|
|
|
|
|
|
|
|
119
|
43
|
|
|
|
|
116
|
my ($y, $m, $d, $sex) = _expand_date($portions{date}, $data); |
120
|
43
|
|
|
|
|
188
|
$retval->@{qw< year month day sex >} = ($y, $m, $d, $sex); |
121
|
43
|
100
|
100
|
|
|
103
|
return $retval |
122
|
|
|
|
|
|
|
if !_is_valid_cf_date($y, $m, $d) |
123
|
|
|
|
|
|
|
&& !$cb->('invalid birth date'); |
124
|
|
|
|
|
|
|
|
125
|
39
|
|
|
|
|
72
|
my $date; |
126
|
39
|
100
|
|
|
|
227
|
$date = $retval->{date} = sprintf('%04d-%02d-%02d', $y, $m, $d) |
127
|
|
|
|
|
|
|
if defined($y); |
128
|
|
|
|
|
|
|
|
129
|
39
|
100
|
|
|
|
105
|
if (defined(my $p = _place_name_for($portions{place}, $date))) { |
130
|
36
|
|
|
|
|
80
|
$retval->{place} = $p; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
else { |
133
|
3
|
100
|
|
|
|
9
|
return $retval unless $cb->('invalid birth place'); |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
37
|
|
|
|
|
88
|
my $checksum = _cf_checksum($cf); |
137
|
37
|
100
|
100
|
|
|
138
|
return $retval |
138
|
|
|
|
|
|
|
if $checksum ne substr($cf, -1, 1) |
139
|
|
|
|
|
|
|
&& !$cb->("invalid checksum (should be: $checksum)"); |
140
|
|
|
|
|
|
|
|
141
|
33
|
100
|
|
|
|
100
|
return $retval unless $data; |
142
|
|
|
|
|
|
|
|
143
|
20
|
100
|
|
|
|
83
|
if (defined(my $surname = $data->{surname})) { |
144
|
6
|
100
|
100
|
|
|
17
|
return $retval |
145
|
|
|
|
|
|
|
if substr($cf, 0, 3) ne _compact_surname($surname) |
146
|
|
|
|
|
|
|
&& !$cb->('surname mismatch'); |
147
|
|
|
|
|
|
|
} |
148
|
18
|
100
|
|
|
|
43
|
if (defined(my $name = $data->{name})) { |
149
|
6
|
100
|
100
|
|
|
18
|
return $retval |
150
|
|
|
|
|
|
|
if substr($cf, 3, 3) ne _compact_name($name) |
151
|
|
|
|
|
|
|
&& !$cb->('name mismatch'); |
152
|
|
|
|
|
|
|
} |
153
|
16
|
100
|
|
|
|
36
|
if (defined(my $birthdate = $data->{date})) { |
154
|
6
|
|
|
|
|
14
|
my ($male, $female) = _compact_birthdates($birthdate); |
155
|
6
|
|
|
|
|
20
|
my $got = _normalized_birthdate(substr($cf, 6, 5)); |
156
|
6
|
100
|
66
|
|
|
64
|
return $retval |
|
|
|
100
|
|
|
|
|
157
|
|
|
|
|
|
|
if ($got ne $male) |
158
|
|
|
|
|
|
|
&& ($got ne $female) |
159
|
|
|
|
|
|
|
&& !$cb->('birth date mismatch'); |
160
|
|
|
|
|
|
|
} ## end if (defined(my $birthdate...)) |
161
|
14
|
100
|
|
|
|
35
|
if (defined(my $sex = $data->{sex})) { |
162
|
8
|
|
|
|
|
29
|
my $got = _normalized_birthdate(substr($cf, 6, 5)); |
163
|
8
|
|
|
|
|
23
|
my $day = substr($got, -2, 2) + 0; |
164
|
8
|
100
|
66
|
|
|
81
|
return $retval |
|
|
|
100
|
|
|
|
|
165
|
|
|
|
|
|
|
if ((lc($sex) eq 'm' && $day > 31) |
166
|
|
|
|
|
|
|
|| (lc($sex) eq 'f' && $day < 41)) |
167
|
|
|
|
|
|
|
&& !$cb->('sex mismatch'); |
168
|
|
|
|
|
|
|
} ## end if (defined(my $sex = ...)) |
169
|
12
|
100
|
|
|
|
32
|
if (defined(my $place = $data->{place})) { |
170
|
|
|
|
|
|
|
my $got = $retval->{place} // |
171
|
6
|
|
33
|
|
|
14
|
_normalized_birthplace($portions{place}); |
172
|
6
|
100
|
100
|
|
|
26
|
return $retval |
173
|
|
|
|
|
|
|
if fc($got) ne fc($place) |
174
|
|
|
|
|
|
|
&& !$cb->('birth place mismatch'); |
175
|
|
|
|
|
|
|
} ## end if (defined(my $place ...)) |
176
|
|
|
|
|
|
|
|
177
|
10
|
|
|
|
|
36
|
return $retval; |
178
|
|
|
|
|
|
|
} ## end sub _validate_cf |
179
|
|
|
|
|
|
|
|
180
|
37
|
|
|
37
|
|
56
|
sub _cf_checksum ($cf) { |
|
37
|
|
|
|
|
48
|
|
|
37
|
|
|
|
|
59
|
|
181
|
37
|
|
|
|
|
655
|
state $odd_checksums = { |
182
|
|
|
|
|
|
|
0 => 1, |
183
|
|
|
|
|
|
|
1 => 0, |
184
|
|
|
|
|
|
|
2 => 5, |
185
|
|
|
|
|
|
|
3 => 7, |
186
|
|
|
|
|
|
|
4 => 9, |
187
|
|
|
|
|
|
|
5 => 13, |
188
|
|
|
|
|
|
|
6 => 15, |
189
|
|
|
|
|
|
|
7 => 17, |
190
|
|
|
|
|
|
|
8 => 19, |
191
|
|
|
|
|
|
|
9 => 21, |
192
|
|
|
|
|
|
|
A => 1, |
193
|
|
|
|
|
|
|
B => 0, |
194
|
|
|
|
|
|
|
C => 5, |
195
|
|
|
|
|
|
|
D => 7, |
196
|
|
|
|
|
|
|
E => 9, |
197
|
|
|
|
|
|
|
F => 13, |
198
|
|
|
|
|
|
|
G => 15, |
199
|
|
|
|
|
|
|
H => 17, |
200
|
|
|
|
|
|
|
I => 19, |
201
|
|
|
|
|
|
|
J => 21, |
202
|
|
|
|
|
|
|
K => 2, |
203
|
|
|
|
|
|
|
L => 4, |
204
|
|
|
|
|
|
|
M => 18, |
205
|
|
|
|
|
|
|
N => 20, |
206
|
|
|
|
|
|
|
O => 11, |
207
|
|
|
|
|
|
|
P => 3, |
208
|
|
|
|
|
|
|
Q => 6, |
209
|
|
|
|
|
|
|
R => 8, |
210
|
|
|
|
|
|
|
S => 12, |
211
|
|
|
|
|
|
|
T => 14, |
212
|
|
|
|
|
|
|
U => 16, |
213
|
|
|
|
|
|
|
V => 10, |
214
|
|
|
|
|
|
|
W => 22, |
215
|
|
|
|
|
|
|
X => 25, |
216
|
|
|
|
|
|
|
Y => 24, |
217
|
|
|
|
|
|
|
Z => 23, |
218
|
|
|
|
|
|
|
}, |
219
|
|
|
|
|
|
|
my $even_checksums = { |
220
|
|
|
|
|
|
|
0 => 0, |
221
|
|
|
|
|
|
|
1 => 1, |
222
|
|
|
|
|
|
|
2 => 2, |
223
|
|
|
|
|
|
|
3 => 3, |
224
|
|
|
|
|
|
|
4 => 4, |
225
|
|
|
|
|
|
|
5 => 5, |
226
|
|
|
|
|
|
|
6 => 6, |
227
|
|
|
|
|
|
|
7 => 7, |
228
|
|
|
|
|
|
|
8 => 8, |
229
|
|
|
|
|
|
|
9 => 9, |
230
|
|
|
|
|
|
|
A => 0, |
231
|
|
|
|
|
|
|
B => 1, |
232
|
|
|
|
|
|
|
C => 2, |
233
|
|
|
|
|
|
|
D => 3, |
234
|
|
|
|
|
|
|
E => 4, |
235
|
|
|
|
|
|
|
F => 5, |
236
|
|
|
|
|
|
|
G => 6, |
237
|
|
|
|
|
|
|
H => 7, |
238
|
|
|
|
|
|
|
I => 8, |
239
|
|
|
|
|
|
|
J => 9, |
240
|
|
|
|
|
|
|
K => 10, |
241
|
|
|
|
|
|
|
L => 11, |
242
|
|
|
|
|
|
|
M => 12, |
243
|
|
|
|
|
|
|
N => 13, |
244
|
|
|
|
|
|
|
O => 14, |
245
|
|
|
|
|
|
|
P => 15, |
246
|
|
|
|
|
|
|
Q => 16, |
247
|
|
|
|
|
|
|
R => 17, |
248
|
|
|
|
|
|
|
S => 18, |
249
|
|
|
|
|
|
|
T => 19, |
250
|
|
|
|
|
|
|
U => 20, |
251
|
|
|
|
|
|
|
V => 21, |
252
|
|
|
|
|
|
|
W => 22, |
253
|
|
|
|
|
|
|
X => 23, |
254
|
|
|
|
|
|
|
Y => 24, |
255
|
|
|
|
|
|
|
Z => 25, |
256
|
|
|
|
|
|
|
}; |
257
|
37
|
|
|
|
|
75
|
state $checksums_for = [$odd_checksums, $even_checksums]; |
258
|
37
|
|
|
|
|
185
|
my @chars = split m{}mxs, substr($cf, 0, 15); # no checksum |
259
|
37
|
|
|
|
|
132
|
my $sum = sum map { $checksums_for->[$_ % 2]{$chars[$_]} } 0 .. $#chars; |
|
555
|
|
|
|
|
1118
|
|
260
|
37
|
|
|
|
|
254
|
chr(ord('A') + ($sum % 26)); |
261
|
|
|
|
|
|
|
} ## end sub _cf_checksum |
262
|
|
|
|
|
|
|
|
263
|
96
|
|
|
96
|
|
133
|
sub _normalized_string ($string, @positions) { |
|
96
|
|
|
|
|
151
|
|
|
96
|
|
|
|
|
164
|
|
|
96
|
|
|
|
|
131
|
|
264
|
96
|
|
|
|
|
135
|
state $letters = [qw< L M N P Q R S T U V >]; |
265
|
96
|
|
|
|
|
147
|
state $digit_for = {map { $letters->[$_] => $_ } 0 .. $letters->$#*}; |
|
20
|
|
|
|
|
59
|
|
266
|
96
|
|
|
|
|
204
|
for my $i (@positions) { |
267
|
345
|
|
|
|
|
557
|
my $current = substr($string, $i, 1); |
268
|
|
|
|
|
|
|
substr($string, $i, 1, $digit_for->{$current}) |
269
|
345
|
50
|
|
|
|
758
|
if exists $digit_for->{$current}; |
270
|
|
|
|
|
|
|
} |
271
|
96
|
|
|
|
|
300
|
return $string; |
272
|
|
|
|
|
|
|
} ## end sub _normalized_string |
273
|
|
|
|
|
|
|
|
274
|
39
|
|
|
39
|
|
52
|
sub _normalized_birthplace ($place) { _normalized_string($place, 1 .. 3) } |
|
39
|
|
|
|
|
65
|
|
|
39
|
|
|
|
|
59
|
|
|
39
|
|
|
|
|
105
|
|
275
|
57
|
|
|
57
|
|
73
|
sub _normalized_birthdate ($date) { _normalized_string($date, 0, 1, 3, 4) } |
|
57
|
|
|
|
|
110
|
|
|
57
|
|
|
|
|
71
|
|
|
57
|
|
|
|
|
117
|
|
276
|
|
|
|
|
|
|
|
277
|
43
|
|
|
43
|
|
67
|
sub _expand_date ($date, $opts) { |
|
43
|
|
|
|
|
70
|
|
|
43
|
|
|
|
|
73
|
|
|
43
|
|
|
|
|
52
|
|
278
|
43
|
|
|
|
|
82
|
state $mlf = [split m{}mxs, 'ABCDEHLMPRST']; |
279
|
43
|
|
|
|
|
70
|
state $month_for = {map { $mlf->[$_] => $_ } 0 .. $mlf->$#*}; |
|
24
|
|
|
|
|
71
|
|
280
|
|
|
|
|
|
|
|
281
|
43
|
|
|
|
|
96
|
$date = _normalized_birthdate($date); |
282
|
43
|
100
|
|
|
|
240
|
my ($y, $mc, $d) = $date =~ m{\A(\d\d)([ABCDEHLMPRST])(\d\d)\z}mxs |
283
|
|
|
|
|
|
|
or return; |
284
|
40
|
|
|
|
|
90
|
my $m = 1 + $month_for->{$mc}; |
285
|
40
|
|
|
|
|
147
|
$_ += 0 for ($d, $y); |
286
|
40
|
100
|
|
|
|
101
|
my $sex = $d > 40 ? 'F' : 'M'; |
287
|
40
|
100
|
|
|
|
89
|
$d -= 40 if $d > 40; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# century: the initial digits of a year |
290
|
40
|
50
|
100
|
|
|
145
|
if (defined(my $years_baseline = ($opts // {})->{years_baseline})) { |
291
|
0
|
|
|
|
|
0
|
$y += $years_baseline; |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
else { # whatever in the last 100 years |
294
|
40
|
|
|
|
|
1137
|
my $this_year = 1900 + (localtime)[5]; |
295
|
40
|
|
|
|
|
188
|
$y += 100 * int($this_year / 100); |
296
|
40
|
50
|
|
|
|
131
|
$y -= 100 if $y > $this_year; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
40
|
|
|
|
|
173
|
return ($y, $m, $d, $sex); |
300
|
|
|
|
|
|
|
} ## end sub _expand_date |
301
|
|
|
|
|
|
|
|
302
|
43
|
|
|
43
|
|
67
|
sub _is_valid_cf_date ($y, $m, $d) { |
|
43
|
|
|
|
|
65
|
|
|
43
|
|
|
|
|
73
|
|
|
43
|
|
|
|
|
56
|
|
|
43
|
|
|
|
|
56
|
|
303
|
43
|
|
|
|
|
96
|
return !!(eval { timegm(30, 30, 12, $d, $m - 1, $y); 1 }); |
|
43
|
|
|
|
|
215
|
|
|
37
|
|
|
|
|
1176
|
|
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
6
|
|
|
6
|
|
8
|
sub _compact_birthdates ($birthdate) { |
|
6
|
|
|
|
|
19
|
|
|
6
|
|
|
|
|
11
|
|
307
|
6
|
|
|
|
|
45
|
state $month_letter_for = ['', split m{}mxs, 'ABCDEHLMPRST']; |
308
|
6
|
|
|
|
|
39
|
my ($y, $m, $d) = split m{\D}mxs, $birthdate; |
309
|
6
|
50
|
|
|
|
21
|
($y, $d) = ($d, $y) if $d > 31; |
310
|
6
|
|
|
|
|
11
|
$y %= 100; |
311
|
6
|
|
|
|
|
14
|
$m = $month_letter_for->[$m + 0]; |
312
|
6
|
|
|
|
|
11
|
map { sprintf '%02d%s%02d', $y, $m, $_ } ($d, $d + 40); |
|
12
|
|
|
|
|
86
|
|
313
|
|
|
|
|
|
|
} ## end sub _compact_birthdates |
314
|
|
|
|
|
|
|
|
315
|
6
|
|
|
6
|
|
13
|
sub _compact_surname ($surname) { |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
10
|
|
316
|
6
|
|
|
|
|
17
|
my ($cs, $vs) = _consonants_and_vowels($surname); |
317
|
6
|
|
|
|
|
22
|
my @retval = ($cs->@*, $vs->@*, ('X') x 3); |
318
|
6
|
|
|
|
|
41
|
return join '', @retval[0 .. 2]; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
6
|
|
|
6
|
|
8
|
sub _compact_name ($name) { |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
8
|
|
322
|
6
|
|
|
|
|
11
|
my ($cs, $vs) = _consonants_and_vowels($name); |
323
|
6
|
100
|
|
|
|
19
|
splice $cs->@*, 1, 1 if $cs->@* > 3; |
324
|
6
|
|
|
|
|
20
|
my @retval = ($cs->@*, $vs->@*, ('X') x 3); |
325
|
6
|
|
|
|
|
52
|
return join '', @retval[0 .. 2]; |
326
|
|
|
|
|
|
|
} ## end sub _compact_name |
327
|
|
|
|
|
|
|
|
328
|
12
|
|
|
12
|
|
21
|
sub _consonants_and_vowels ($string) { |
|
12
|
|
|
|
|
19
|
|
|
12
|
|
|
|
|
17
|
|
329
|
12
|
|
|
|
|
17
|
my (@consonants, @vowels); |
330
|
12
|
|
|
|
|
41
|
for my $char (grep { m{[A-Z]}mxs } split m{}mxs, uc($string)) { |
|
63
|
|
|
|
|
183
|
|
331
|
63
|
100
|
|
|
|
142
|
if ($char =~ m{[AEIOU]}mxs) { push @vowels, $char } |
|
30
|
|
|
|
|
87
|
|
332
|
33
|
|
|
|
|
73
|
else { push @consonants, $char } |
333
|
|
|
|
|
|
|
} |
334
|
12
|
|
|
|
|
40
|
return (\@consonants, \@vowels); |
335
|
|
|
|
|
|
|
} ## end sub _consonants_and_vowels |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub _places { |
338
|
2
|
|
|
2
|
|
5
|
state $retval = do { |
339
|
2
|
|
|
|
|
9
|
local $/; |
340
|
2
|
|
|
|
|
29
|
binmode DATA, ':raw'; |
341
|
2
|
|
|
|
|
22193
|
(my $json = readline(DATA)) =~ s{\n+}{}gmxs; |
342
|
2
|
|
|
|
|
27
|
decode_json($json); |
343
|
|
|
|
|
|
|
}; |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
39
|
|
|
39
|
|
61
|
sub _place_name_for ($place, $birthdate) { |
|
39
|
|
|
|
|
73
|
|
|
39
|
|
|
|
|
55
|
|
|
39
|
|
|
|
|
55
|
|
347
|
39
|
|
|
|
|
61
|
state $place_for = _places(); |
348
|
39
|
50
|
|
|
|
24941463
|
my $record = $place_for->{_normalized_birthplace($place)} or return; |
349
|
39
|
100
|
|
|
|
100
|
return "[$record->[-1]{name}]" unless defined($birthdate); |
350
|
38
|
|
|
|
|
74
|
for my $candidate ($record->@*) { |
351
|
69
|
100
|
|
|
|
165
|
next if $birthdate gt $candidate->{end}; |
352
|
35
|
50
|
|
|
|
92
|
last if $birthdate lt $candidate->{start}; |
353
|
35
|
|
|
|
|
119
|
return $candidate->{name}; |
354
|
|
|
|
|
|
|
} |
355
|
3
|
|
|
|
|
11
|
return; |
356
|
|
|
|
|
|
|
} ## end sub _place_name_for |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
1; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
__DATA__ |