File Coverage

blib/lib/Number/Phone/UK.pm
Criterion Covered Total %
statement 162 165 99.3
branch 53 58 93.1
condition 15 18 83.3
subroutine 32 32 100.0
pod 9 9 100.0
total 271 282 97.1


line stmt bran cond sub pod time code
1             package Number::Phone::UK;
2              
3 15     15   6540 use strict;
  15         45  
  15         540  
4              
5 15     15   91 use Scalar::Util 'blessed';
  15         45  
  15         721  
6 15     15   732 use Number::Phone::UK::Data;
  15         48  
  15         529  
7              
8 15     15   112 use base 'Number::Phone';
  15         34  
  15         19826  
9              
10             our $VERSION = '1.71';
11              
12             my $cache = {};
13              
14             =head1 NAME
15              
16             Number::Phone::UK - UK-specific methods for Number::Phone
17              
18             =head1 SYNOPSIS
19              
20             use Number::Phone;
21              
22             $daves_phone = Number::Phone->new('+44 1234 567890');
23              
24             =cut
25              
26             sub new {
27 338     338 1 2581 my $class = shift;
28 338         674 my $number = shift;
29              
30 338         1048 $number = '+44'._clean_number($number);
31 338 100       1088 if(is_valid($number)) {
32 301         803 $number =~ s/^0/+44/;
33 301         894 my $target_class = $class->_get_class(_clean_number($number));
34 301 100       1271 return undef if($class ne $target_class);
35 295         4337 return bless(\$number, $target_class);
36 38         218 } else { return undef; }
37             }
38              
39             =head1 DATABASE
40              
41             Number::Phone::UK uses a large database, access via L. This
42             database lives in a file, and normally only the little bits of it that you access will
43             ever get loaded into memory. This means, however, that creating Number::Phone::UK objects
44             almost always involves disk access and so is slow compared to data for some other
45             countries. There are two ways to avoid this slowness.
46              
47             First, if you don't need all the functionality you can use L.
48              
49             Second, if you can accept slow startup - eg when your server starts - then you can call
50             C<< Number::Phone::UK::Data->slurp() >> from your code, which will pull the entire database
51             into memory. This will take a few minutes, and on a 64-bit machine will consume of the
52             order of 200MB of memory.
53              
54             The database uses L. This apparently has some problems if you connect to it,
55             C, and then try to access the database from multiple processes. We attempt to
56             work around this by re-connecting to the database after forking. This is, of course,
57             not a problem if you C the database before forking.
58              
59             =head1 METHODS
60              
61             The following methods from Number::Phone are overridden:
62              
63             =over 4
64              
65             =item new
66              
67             The constructor, you should never have to call this yourself. To create an
68             object the canonical incantation is C<< Number::Phone->new('+44 ...') >>.
69              
70             =item data_source
71              
72             Returns a string telling where and when the data that drives this class was last updated, looking something like:
73              
74             "OFCOM at Wed Sep 30 10:37:39 2020 UTC"
75              
76             The current value of this is also documented in L.
77              
78             =item is_valid
79              
80             The number is valid within the national numbering scheme. It may or may
81             not yet be allocated, or it may be reserved. Any number which returns
82             true for any of the following methods will also be valid.
83              
84             =cut
85              
86             sub _get_class {
87 300     301   672 my $class = shift;
88 300         976 my $number = shift;
89 300         881 foreach my $prefix (_prefixes($number)) {
90 2835 100       5149308 if(exists(Number::Phone::UK::Data::db()->{subclass}->{$prefix})) {
91 43 50       130696 return $class if(Number::Phone::UK::Data::db()->{subclass}->{$prefix} eq '');
92              
93 43         128793 my $desired_subclass = Number::Phone::UK::Data::db()->{subclass}->{$prefix};
94 43         128250 my $subclass = "Number::Phone::UK::$desired_subclass";
95 43     4   4817 eval "use $subclass";
  4     4   44  
  4     3   11  
  4     3   53  
  4     3   42  
  4     3   23  
  4     3   62  
  3     3   31  
  3     2   8  
  3     2   34  
  3     2   33  
  3     2   8  
  3         38  
  3         31  
  3         8  
  3         39  
  3         30  
  3         10  
  3         30  
  3         31  
  3         17  
  3         32  
  3         27  
  3         12  
  3         32  
  2         18  
  2         6  
  2         22  
  2         19  
  2         7  
  2         23  
  2         18  
  2         10  
  2         16  
  2         25  
  2         8  
  2         18  
96 43         313 return $subclass;
97             }
98             }
99 257         503438 return $class;
100             }
101              
102             sub _clean_number {
103 1211     1212   2030 my $clean = shift;
104 1211         3815 $clean =~ s/[^0-9+]//g; # strip non-digits/plusses
105 1211         4719 $clean =~ s/^\+44//; # remove leading +44
106 1211         2462 $clean =~ s/^0//; # kill leading zero
107 1211         3800 return $clean;
108             }
109              
110             sub _prefixes {
111 794     794   1446 my $number = shift;
112 794         2277 map { substr($number, 0, $_) } reverse(1..length($number));
  7858         15477  
113             }
114              
115             sub is_valid {
116 365     365 1 2658 my $number = shift;
117              
118             # If called as an object method, it *must* be valid otherwise the
119             # object would never have been instantiated.
120             # If called as a subroutine, that's the constructor doing its thang.
121 365 100       1493 return 1 if(blessed($number));
122              
123             # otherwise we have to validate
124              
125             # if we've seen this number before, use cached result
126 337 100       1795 return 1 if($cache->{$number}->{is_valid});
127              
128             # assume it's OK unless proven otherwise
129 202         599 $cache->{$number}->{is_valid} = 1;
130              
131 202         412 my $cleaned_number = _clean_number($number);
132              
133 202         607 my @prefixes = _prefixes($cleaned_number);
134              
135             # quickly check length
136 202 100 100     1382 return $cache->{$number}->{is_valid} = 0 if(length($cleaned_number) < 7 || length($cleaned_number) > 10);
137              
138             # 04 and 06 are invalid, only 05[56] are valid
139 185 100       792 return $cache->{$number}->{is_valid} = 0 if($cleaned_number =~ /^(4|5[01234789]|6)/);
140              
141             # slightly more rigourous length check for some unallocated geographic numbers
142             # 07, 02x and 011x are always ten digits
143 179 100 100     1006 return $cache->{$number}->{is_valid} = 0 if($cleaned_number =~ /^([27]|11)/ && length($cleaned_number) != 10);
144              
145             my $telco_and_length_code =
146             (
147 165         343409 map { Number::Phone::UK::Data::db()->{telco_and_length}->{$_} }
148 171         395 grep { exists(Number::Phone::UK::Data::db()->{telco_and_length}->{$_}) }
  1684         3171176  
149             @prefixes
150             )[0];
151              
152 171         376211 $cache->{$number}->{is_allocated} = 0;
153 171 100 100     960 if(
    100          
154             # if we've got a telco, we've been allocated
155             $telco_and_length_code &&
156             Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{telco}
157             ) {
158 110         348582 $cache->{$number}->{is_allocated} = 1;
159 110         415 $cache->{$number}->{operator} = Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{telco};
160             $cache->{$number}->{format} = Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{format}
161 110         311752 } elsif($telco_and_length_code) {
162             # if not we might still have a format, eg for Protected numbers
163             $cache->{$number}->{format} = Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{format}
164 55         132821 }
165              
166 171 100 66     424728 if($cache->{$number}->{format} && $cache->{$number}->{format} =~ /\+/) {
167 165         10261 my($arealength, $subscriberlength) = split(/\+/, $cache->{$number}->{format});
168             # for hateful mixed thing
169 165 100       858 my @subscriberlengths = ($subscriberlength =~ m{/}) ? split(/\//, $subscriberlength) : ($subscriberlength);
170 165         1213 $subscriberlength =~ s/^(\d+).*/$1/; # for hateful mixed thing
171 165         776 $cache->{$number}->{areacode} = substr($cleaned_number, 0, $arealength);
172 165         588 $cache->{$number}->{subscriber} = substr($cleaned_number, $arealength);
173             $cache->{$number}->{areaname} = (
174             map {
175 88         163499 Number::Phone::UK::Data::db()->{areanames}->{$_}
176 165         444 } grep { Number::Phone::UK::Data::db()->{areanames}->{$_} } @prefixes
  1624         2723268  
177             )[0];
178 165 100       318004 if(!grep { length($cache->{$number}->{subscriber}) == $_ } @subscriberlengths) {
  183         1217  
179             # number wrong length!
180 6         35 $cache->{$number} = { is_valid => 0 };
181 6         34 return 0;
182             }
183             }
184              
185 165         929 return $cache->{$number}->{is_valid};
186             }
187              
188             # now define the is_* methods that we over-ride
189             sub is_fixed_line {
190 30 100   30 1 1026 return 0 if(is_mobile(@_));
191 16         65 return undef;
192             }
193              
194             sub is_drama {
195 80     80 1 331 my $self = shift;
196              
197 80         159 my $num = _clean_number(${$self});
  80         230  
198              
199 80         1271 my @drama_numbers = (
200             # Leeds, Sheffield, Nottingham, Leicester, Bristol, Reading
201             qr/^11[3-8]4960[0-9]{3}$/,
202             # Birmingham, Edinburgh, Glasgow, Liverpool, Manchester
203             qr/^1[2-6]14960[0-9]{3}$/,
204             # London
205             qr/^2079460[0-9]{3}$/,
206             # Tyneside/Durham/Sunderland
207             qr/^1914980[0-9]{3}$/,
208             # Northern Ireland
209             qr/^2896496[0-9]{3}$/,
210             # Cardiff
211             qr/^2920180[0-9]{3}$/,
212             # No area
213             qr/^1632960[0-9]{3}$/,
214             # Mobile
215             qr/^7700900[0-9]{3}$/,
216             # Freephone
217             qr/^8081570[0-9]{3}$/,
218             # Premium Rate
219             qr/^9098790[0-9]{3}$/,
220             # UK Wide
221             qr/^3069990[0-9]{3}$/,
222             );
223              
224 80         232 foreach my $d (@drama_numbers) {
225 552 100       2166 return 1 if ($num =~ $d);
226             }
227              
228 32         261 return 0;
229             }
230              
231             foreach my $is (qw(
232             geographic network_service tollfree corporate
233             personal pager mobile specialrate adult allocated ipphone
234             )) {
235 15     15   148 no strict 'refs';
  15         50  
  15         3647  
236             *{__PACKAGE__."::is_$is"} = sub {
237 540     540   70781 my $self = shift;
238 540 100       1000 if(!exists($cache->{${$self}}->{"is_$is"})) {
  540         2374  
239 288         489076 $cache->{${$self}}->{"is_$is"} =
240             grep {
241             Number::Phone::UK::Data::db()->{
242             { geographic => 'geo_prefices',
243             network_service => 'network_svc_prefices',
244             tollfree => 'free_prefices',
245             corporate => 'corporate_prefices',
246             personal => 'personal_prefices',
247             pager => 'pager_prefices',
248             mobile => 'mobile_prefices',
249             specialrate => 'special_prefices',
250             adult => 'adult_prefices',
251             ipphone => 'ip_prefices'
252             }->{$is}
253 2876         4473784 }->{$_}
254 288         595 } _prefixes(_clean_number(${$self}));
  288         732  
255             }
256 540         1517 $cache->{${$self}}->{"is_$is"};
  540         2791  
257             }
258             }
259              
260             # define the other methods
261              
262             foreach my $method (qw(operator areacode areaname subscriber)) {
263 15     15   141 no strict 'refs';
  15         919  
  15         9372  
264             *{__PACKAGE__."::$method"} = sub {
265 359     359   613 my $self = shift;
266 359         530 return $cache->{${$self}}->{$method};
  359         1875  
267             }
268             }
269              
270             =item is_allocated
271              
272             The number has been allocated to a telco for use. It may or may not yet
273             be in use or may be reserved.
274              
275             =item is_drama
276              
277             The number is intended for use in fiction. OFCOM has allocated numerous small
278             ranges for this purpose. These numbers will not be allocated to real customers.
279             See L
280             for the authoritative source.
281              
282             =item is_geographic
283              
284             The number refers to a geographic area.
285              
286             =item is_fixed_line
287              
288             The number, when in use, can only refer to a fixed line.
289              
290             (we can't tell whether a number is a fixed line, but we can tell that
291             some are *not*).
292              
293             =item is_mobile
294              
295             The number, when in use, can only refer to a mobile phone.
296              
297             =item is_pager
298              
299             The number, when in use, can only refer to a pager.
300              
301             =item is_tollfree
302              
303             Callers will not be charged for calls to this number under normal circumstances.
304              
305             =item is_specialrate
306              
307             The number, when in use, attracts special rates. For instance, national
308             dialling at local rates, or premium rates for services.
309              
310             =item is_adult
311              
312             The number, when in use, goes to a service of an adult nature, such as porn.
313              
314             =item is_personal
315              
316             The number, when in use, goes to an individual person.
317              
318             =item is_corporate
319              
320             The number, when in use, goes to a business.
321              
322             =item is_ipphone
323              
324             The number, when in use, is terminated using VoIP.
325              
326             =item is_network_service
327              
328             The number is some kind of network service such as a human operator, directory
329             enquiries, emergency services etc
330              
331             =item country_code
332              
333             Returns 44.
334              
335             =cut
336              
337 94     94 1 408 sub country_code { 44; }
338              
339             =item regulator
340              
341             Returns informational text.
342              
343             =cut
344              
345 2     2 1 16 sub regulator { 'OFCOM, http://www.ofcom.org.uk/'; }
346              
347             =item areacode
348              
349             Return the area code - if applicable - for the number. If not applicable,
350             returns undef.
351              
352             =item areaname
353              
354             Return the area name - if applicable - for the number, or undef.
355              
356             =item location
357              
358             For geographic numbers, this returns the location of the exchange to which
359             that number is assigned, if available. Otherwise returns undef.
360              
361             =cut
362              
363             sub location {
364 6     6 1 3843 my $self = shift;
365              
366 6 100       31 return undef unless($self->is_geographic());
367              
368 4         10 my $cleaned_number = _clean_number(${$self});
  4         17  
369              
370 4         17 my @prefixes = _prefixes($cleaned_number);
371              
372             # uncoverable branch true
373 4 50       34 if(!$ENV{TESTINGKILLTHEWABBIT}) {
374 0         0 eval "require Number::Phone::UK::DetailedLocations"; # uncoverable statement
375             }
376 4 100       83 require Number::Phone::UK::Exchanges if(!$Number::Phone::UK::Exchanges::db);
377              
378 4         926 foreach(@prefixes) {
379 28 100       163 if(exists($Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_})) {
380             return [
381             $Number::Phone::UK::Exchanges::db->{exchg_positions}->{$Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_}}->{lat},
382             $Number::Phone::UK::Exchanges::db->{exchg_positions}->{$Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_}}->{long}
383 4         56 ];
384             }
385             }
386             # may become coverable if I ever test the location of a number
387             # in an areacode that wasn't in the data dump I got years ago
388 0         0 return undef; # uncoverable statement
389             }
390              
391             =item subscriber
392              
393             Return the subscriber part of the number
394              
395             =item operator
396              
397             Return the name of the telco operating this number, in an appropriate
398             character set and with optional details such as their web site or phone
399             number.
400              
401             =item format
402              
403             Return a sanely formatted version of the number, complete with IDD code, eg
404             for the UK number (0208) 771-2924 it would return +44 20 8771 2924.
405              
406             =cut
407              
408             sub format {
409 63     63 1 856 my $self = shift;
410 63         164 my $r;
411              
412 63 100 66     227 if($self->areacode()) { # if there's an areacode ...
    100 66        
    50          
    0          
413 38         116 $r = '+'.country_code().' '.$self->areacode().' ';
414 38 100       135 if( length($self->subscriber()) == 7) { $r .= substr($self->subscriber(), 0, 3).' '.substr($self->subscriber(), 3) }
  2 100       10  
415 11         29 elsif(length($self->subscriber()) == 8) { $r .= substr($self->subscriber(), 0, 4).' '.substr($self->subscriber(), 4) }
416 25         147 else { $r .= $self->subscriber() }
417             } elsif($self->subscriber && $self->subscriber =~ /^7/) { # mobiles/pagers don't have areacodes but should be formatted as if they do
418 9         36 $r = '+'.country_code().
419             ' '.substr($self->subscriber(), 0, 4).
420             ' '.substr($self->subscriber(), 4);
421             } elsif(!$self->is_allocated() || !$cache->{${self}}->{format}) { # if not allocated or no format
422 16         56 $r = '+'.country_code().' '.substr(${$self}, 3)
  16         61  
423             } elsif($self->subscriber()) { # if there's a subscriber ...
424 0         0 $r = '+'.country_code().' '.$self->subscriber
425             }
426 63         456 return $r;
427             }
428              
429             =item intra_country_dial_to
430              
431             Within the UK numbering plan you can *always* dial 0xxxx xxxxxx
432             for intra-country calls. In most places the leading 0$areacode is
433             optional but in some it is required (see eg
434             L) and over time this
435             will apply to more areas.
436              
437             =cut
438              
439             sub intra_country_dial_to {
440 10     10 1 29 my $from = shift;
441 10         22 my $to = shift;
442              
443 10 100       46 die if(!$to->is_allocated());
444 9 100       42 return '0'.($to->areacode() ? $to->areacode() : '').$to->subscriber();
445             }
446              
447             =item country
448              
449             If the number is_international, return the two-letter ISO country code.
450              
451             NYI
452              
453             =back
454              
455             =head1 LIMITATIONS/BUGS/FEEDBACK
456              
457             The results are only as up-to-date as the data included from OFCOM's
458             official documentation of number range allocations.
459              
460             No attempt is made to deal with number portability.
461              
462             Please report bugs at L, including, if possible, a test case.
463              
464             I welcome feedback from users.
465              
466             =head1 LICENCE
467              
468             You may use, modify and distribute this software under the same terms as
469             perl itself.
470              
471             =head1 AUTHOR
472              
473             David Cantrell Edavid@cantrell.org.ukE
474              
475             Copyright 2023
476              
477             =cut
478              
479             1;