61 changes: 36 additions & 25 deletions buildtools/Number/Phone/BuildHelpers.pm
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ use warnings;

use Exporter qw(import);
our @ISA = qw(Exporter);
our @EXPORT = qw(is_dodgy_unknown_country known_non_country_codes);
our @EXPORT = qw(
get_final_class_part
is_dodgy_unknown_country
known_non_country_codes
$non_geo_IDD_codes_regex
);

our $non_geo_IDD_codes_regex = qr/^(800|808|870|878|881|882|883|888|979)$/;
# these are territories where libphonenumber has some data, but
# for "ISO" country code "001"
sub is_dodgy_unknown_country {
my($ISO, $IDD) = @_;

$ISO !~ /^..$/ # && $IDD !~ /^(800|808|870|878|883|888|979)$/;
$ISO !~ /^..$/ && $IDD !~ /$non_geo_IDD_codes_regex/
}

# see https://en.wikipedia.org/wiki/Global_Mobile_Satellite_System
Expand All @@ -27,33 +33,38 @@ sub known_non_country_codes {
808 => 'SharedCostServices',
870 => 'Inmarsat',
878 => 'UniversalPersonalTelecoms',
881 => 'GMSS', # \ Satphones
8810 => 'ICO', # |
8811 => 'ICO', # |
# 8812 is vacant (Ellipso never launched) # |
# 8813 is vacant (Ellipso never launched) # |
# 8814 is spare # |
# 8815 is spare # |
8816 => 'Iridium', # |
8817 => 'Iridium', # |
8818 => 'Globalstar', # |
8819 => 'Globalstar', # /
882 => 'InternationalNetworks', # many allocations not listed as I don't know if they're diallable, see wtng.info
88213 => 'Telespazio', # Sat-phone
88216 => 'Thuraya', # Sat-phone
88220 => 'GarudaMobile', # Sat-phone
88234 => 'AQ', # Antarctica, via Global Networks Switzerland, http://wtng.info/wtng-spe.html#Networks
883 => 'InternationalNetworks',
883120 => 'Telenor',
883130 => 'Mobistar',
883140 => 'MTTGlobalNetworks',
# NB all the sub-ranges in 881, 882 and 883 except AQ need empty sub-classes in lib/Number/Phone/StubCountry/.../
# There's also logic for them in Number::Phone->_make_stub_object()
# and for AQ in Number::Phone->_new_args()
881 => 'GMSS', # \ Satphones
8810 => 'GMSS::ICO', # |
8811 => 'GMSS::ICO', # |
# 8812 is vacant (Ellipso never launched) # |
# 8813 is vacant (Ellipso never launched) # |
# 8814 is spare # |
# 8815 is spare # |
8816 => 'GMSS::Iridium', # |
8817 => 'GMSS::Iridium', # |
8818 => 'GMSS::Globalstar', # |
8819 => 'GMSS::Globalstar', # /
882 => 'InternationalNetworks882', # many allocations not listed as I don't know if they're diallable, see wtng.info
88213 => 'InternationalNetworks882::Telespazio', # Sat-phone
88216 => 'InternationalNetworks882::Thuraya', # Sat-phone
88234 => 'AQ', # Antarctica, via Global Networks Switzerland, http://wtng.info/wtng-spe.html#Networks
883 => 'InternationalNetworks883',
883130 => 'InternationalNetworks883::Mobistar',
883140 => 'InternationalNetworks883::MTTGlobalNetworks',
888 => 'TelecomsForDisasterRelief',
# 979 is used for testing when we fail to load a module when we
# know what 'country' it is
979 => 'InternationalPremiumRate',
991 => 'ITPCS',
# 999 deliberately NYI for testing; proposed to be like 888.
)
}

sub get_final_class_part {
my($ISO_country_code, $IDD_country_code) = @_;
return length($ISO_country_code) == 2
? $ISO_country_code
: { known_non_country_codes() }->{$IDD_country_code};
}

1;
44 changes: 34 additions & 10 deletions lib/Number/Phone.pm
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use Devel::Deprecations::Environmental
OldPerl => { unsupported_from => '2023-01-08', older_than => '5.12.0' };

# MUST be in format N.NNNN, see https://github.com/DrHyde/perl-modules-Number-Phone/issues/58
our $VERSION = '3.8007';
our $VERSION = '3.8099_02';

my $NOSTUBS = 0;
sub import {
Expand Down Expand Up @@ -263,15 +263,28 @@ sub _new_args {
die("Number::Phone->new(): too many params\n") if(exists($_[2]));

my $original_country;
$original_country = $country if($country =~ /::/);

if(!defined($number)) { # one arg
$number = $country;
} elsif($country =~ /[a-z]/i) { # eg 'UK', '12345'
$original_country = uc($country);
$number = '+'.
Number::Phone::Country::country_code($country).
$number
unless(index($number, '+'.Number::Phone::Country::country_code($country)) == 0);
} elsif($country =~ /[a-z]/i) {
# eg ('UK', '12345')
# ('MOCK', ...)
# ('InternationalNetworks882', ...)
# we accept lower-case ISO codes
$original_country = uc($country) if($country =~ /^[a-z]{2}$/i);
if($country =~ /^GMSS::[a-zA-Z]+$/) {
if($number !~ /^\+881/) {
$number = "+881$number";
}
} elsif($country =~ /^InternationalNetworks(88[23])::[a-zA-Z]+$/) {
my $idd = $1;
if($number !~ /^\+$idd/) {
$number = "+$idd$number";
}
} elsif(index($number, '+'.Number::Phone::Country::country_code($country)) != 0) {
$number = '+'.Number::Phone::Country::country_code($country).$number;
}
} else { # (+)NNN
$number = join('', grep { defined } ($country, $number));
}
Expand All @@ -280,6 +293,9 @@ sub _new_args {
$number = "+$number" unless($number =~ /^\+/);

$country = Number::Phone::Country::phone2country($number) or return;
if($country eq 'AQ' && $number =~ /^\+882/) {
$original_country = 'InternationalNetworks882';
}

# special cases where you can legitimately ask for a containing country (eg
# GB) and get back a sub-country (eg GG, which squats upon parts of the GB
Expand Down Expand Up @@ -323,17 +339,25 @@ sub new {
sub _make_stub_object {
my ($class, $number, $country_name) = @_;
die("no module available for $country_name, and nostubs turned on\n") if($NOSTUBS);

my $stub_class = "Number::Phone::StubCountry::$country_name";
eval "use $stub_class";
# die("Can't find $stub_class: $@\n") if($@);
if($@) {
if($@ && $number =~ /^\+881/) {
$stub_class = "Number::Phone::StubCountry::GMSS::$country_name";
eval "use $stub_class";
} elsif($@ && $number =~ /^\+882/ && $country_name ne 'AQ') {
$stub_class = "Number::Phone::StubCountry::InternationalNetworks882::$country_name";
eval "use $stub_class";
} elsif($@ && $number =~ /^\+883/) {
$stub_class = "Number::Phone::StubCountry::InternationalNetworks883::$country_name";
eval "use $stub_class";
} elsif($@) {
my (undef, $country_idd) = Number::Phone::Country::phone2country_and_idd($number);
# an instance of this class is the ultimate fallback
(my $local_number = $number) =~ s/(^\+$country_idd|\D)//;
if($local_number eq '') { return undef; }
return bless({
country_code => $country_idd,
country => $country_name,
is_valid => undef,
number => $local_number,
}, 'Number::Phone::StubCountry');
Expand Down
5 changes: 3 additions & 2 deletions lib/Number/Phone/Country.pm
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ sub phone2country_and_idd {
}
$country = @$country[0];
}

$country =~ s/.*:://;
return ($country, $idd);
}
}
Expand All @@ -205,7 +205,8 @@ sub phone2country_and_idd {
}

sub country_code {
my $country = uc shift;
my $country = shift;
$country = uc($country) if($country =~ /^[a-z]{2}$/i);

my $data = $prefix_codes{$country} or return;

Expand Down
17 changes: 7 additions & 10 deletions lib/Number/Phone/StubCountry.pm
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,15 @@ Number::Phone::StubCountry - Base class for auto-generated country files

sub country_code {
my $self = shift;

return exists($self->{country_code})
? $self->{country_code}
: Number::Phone::Country::country_code($self->country());

return $self->{country_code};
}

sub country {
my $self = shift;
if(exists($self->{country})) { return $self->{country}; }
ref($self)=~ /::(\w\w(\w\w)?)$/; # extra \w\w is for MOCK during testing
return $1;
ref($self)=~ /::(\w+?)$/;
return $self->{country} = $1;
}

sub raw_number {
Expand All @@ -42,9 +40,9 @@ sub is_valid {
return $self->{is_valid};
}
foreach (map { "is_$_" } qw(specialrate geographic mobile pager tollfree personal ipphone)) {
return 1 if($self->$_());
return $self->{is_valid} = 1 if($self->$_());
}
return 0;
return $self->{is_valid} = 0;
}

sub is_geographic { shift()->_validator('geographic'); }
Expand Down Expand Up @@ -74,7 +72,6 @@ sub areaname {
}
}
my $number = $self->raw_number();
return unless $self->{areanames};
LANGUAGE: foreach my $language (@languages) {
next LANGUAGE unless(exists($self->{areanames}->{$language}));
my %map = %{$self->{areanames}->{$language}};
Expand All @@ -95,7 +92,7 @@ sub format {
return join(' ', '+'.$self->country_code(), @bits);
}
}
return '+'.$self->country_code().' '.$number;
# return '+'.$self->country_code().' '.$number;
}

1;
3 changes: 3 additions & 0 deletions lib/Number/Phone/StubCountry/GMSS/Globalstar.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Number::Phone::StubCountry::GMSS::Globalstar;
use base qw(Number::Phone::StubCountry::GMSS);
1;
3 changes: 3 additions & 0 deletions lib/Number/Phone/StubCountry/GMSS/ICO.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Number::Phone::StubCountry::GMSS::ICO;
use base qw(Number::Phone::StubCountry::GMSS);
1;
3 changes: 3 additions & 0 deletions lib/Number/Phone/StubCountry/GMSS/Iridium.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Number::Phone::StubCountry::GMSS::Iridium;
use base qw(Number::Phone::StubCountry::GMSS);
1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Number::Phone::StubCountry::InternationalNetworks882::Telespazio;
use base qw(Number::Phone::StubCountry::InternationalNetworks882);
1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Number::Phone::StubCountry::InternationalNetworks882::Thuraya;
use base qw(Number::Phone::StubCountry::InternationalNetworks882);
1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Number::Phone::StubCountry::InternationalNetworks883::MTTGlobalNetworks;
use base qw(Number::Phone::StubCountry::InternationalNetworks883);
1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package Number::Phone::StubCountry::InternationalNetworks883::Mobistar;
use base qw(Number::Phone::StubCountry::InternationalNetworks883);
1;
4 changes: 2 additions & 2 deletions t/coverage.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
cover -delete
HARNESS_PERL_SWITCHES=-MDevel::Cover make test
cover
# HARNESS_PERL_SWITCHES=-MDevel::Cover make test
AUTOMATED_TESTING=1 cover -ignore_re ^buildtools/ -test
47 changes: 34 additions & 13 deletions t/inc/common-stub_and_libphonenumber_tests.pl
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@

note("Common tests for Number::Phone::StubCountry::* and Number::Phone::Lib");

my $inmarsat870 = $CLASS->new("+870123456");
note("Inmarsat");
my $inmarsat870 = $CLASS->new("+870761234567");
isa_ok($inmarsat870, "Number::Phone::StubCountry");
isa_ok($inmarsat870, "Number::Phone::StubCountry::Inmarsat");
is($inmarsat870->country_code(), '870', 'Inmarsat number has right country_code');
is($inmarsat870->country(), 'Inmarsat', "$CLASS->new('+870123456')->country()");
is($inmarsat870->format(), '+870 123456', "$CLASS->new('+870123456')->format()");
is($inmarsat870->is_valid(), undef, "$CLASS->new('+870123456')->is_valid()");
is($inmarsat870->is_mobile(), undef, "$CLASS->new('+870123456')->is_mobile()");
is($inmarsat870->is_geographic(), undef, "$CLASS->new('+870123456')->is_geographic()");
is($inmarsat870->is_fixed_line(), undef, "$CLASS->new('+870123456')->is_fixed_line()");
is($inmarsat870->country(), 'Inmarsat', "$CLASS->new('+870761234567')->country()");
is($inmarsat870->format(), '+870 761 234 567', "$CLASS->new('+870761234567')->format()");
is($inmarsat870->is_valid(), 1, "$CLASS->new('+870761234567')->is_valid()");
is($inmarsat870->is_mobile(), 1, "$CLASS->new('+870761234567')->is_mobile()");
is($inmarsat870->is_geographic(), undef, "$CLASS->new('+870761234567')->is_geographic()");
is($inmarsat870->is_fixed_line(), undef, "$CLASS->new('+870761234567')->is_fixed_line()");

# my $inmarsat871 = $CLASS->new("+8719744591");
# is($inmarsat871->country_code(), '871', 'Inmarsat number has right country_code');
Expand All @@ -45,13 +48,31 @@
# is($inmarsat871->is_geographic(), undef, "$CLASS->new('+8719744591')->is_geographic()");
# is($inmarsat871->is_fixed_line(), undef, "$CLASS->new('+8719744591')->is_fixed_line()");

my $international883 = $CLASS->new("+88300000000");
note("InternationalNetworks882");
my $international882 = $CLASS->new("+88249123456");
isa_ok($international882, "Number::Phone::StubCountry");
isa_ok($international882, "Number::Phone::StubCountry::InternationalNetworks882");
is($international882->country(), 'InternationalNetworks882', '$CLASS->new("+88249123456")->country()');

note("Thuraya (in InternationalNetworks882)");
my $international882_thuraya = $CLASS->new("+88216123456");
isa_ok($international882_thuraya, "Number::Phone::StubCountry");
isa_ok($international882_thuraya, "Number::Phone::StubCountry::InternationalNetworks882");
isa_ok($international882_thuraya, "Number::Phone::StubCountry::InternationalNetworks882::Thuraya");
is($international882_thuraya->country(), 'Thuraya', '$CLASS->new("+8821612345")->country()');

note("InternationalNetworks883");
my $international883 = $CLASS->new("+883510012345");
isa_ok($international883, "Number::Phone::StubCountry");
is($international883->country(), 'InternationalNetworks', '$CLASS->new("+88300000000")->country()');

my $international883120 = $CLASS->new("+88312000000");
isa_ok($international883120, "Number::Phone::StubCountry");
is($international883120->country(), 'Telenor', '$CLASS->new("+88312000000")->country()');
isa_ok($international883, "Number::Phone::StubCountry::InternationalNetworks883");
is($international883->country(), 'InternationalNetworks883', '$CLASS->new("+883510012345")->country()');

note("MTTGlobalNetworks (in InternationalNetworks883)");
my $international883_mtt = $CLASS->new("+883140 00000");
isa_ok($international883_mtt, "Number::Phone::StubCountry");
isa_ok($international883_mtt, "Number::Phone::StubCountry::InternationalNetworks883");
isa_ok($international883_mtt, "Number::Phone::StubCountry::InternationalNetworks883::MTTGlobalNetworks");
is($international883_mtt->country(), 'MTTGlobalNetworks', '$CLASS->new("+88314000000")->country()');

my $fo = $CLASS->new('+298 303030'); # Faroes Telecom
is($fo->country_code(), 298, "$CLASS->new('+298 303030')->country_code()");
Expand Down
14 changes: 14 additions & 0 deletions t/inc/uk_tests.pl
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,26 @@
# next check due 2023-06-01 (semi-annually)
subtest "0800 716 range has the wrong length, OFCOM says 10 digits but 0800 716 598 is diallable" => sub {
$number = Number::Phone->new('+44800716598'); # used by Barclays
isa_ok(
$number,
(is_mocked_uk()
? 'Number::Phone::StubCountry::MOCK'
: 'Number::Phone::UK'
)
);
ok($number->is_tollfree(), "valid 9 digit number in a range supposedly for 10 digit numbers");

$number = Number::Phone->new(
(is_mocked_uk() ? 'MOCK' : 'UK'),
'0800716598'
);
isa_ok(
$number,
(is_mocked_uk()
? 'Number::Phone::StubCountry::MOCK'
: 'Number::Phone::UK'
)
);
ok($number->is_tollfree(), "valid 9 digit number (national format) in a range supposedly for 10 digit numbers");

# this is invalid, it's a digit appended to the above, but this may
Expand Down
6 changes: 4 additions & 2 deletions t/number-phone-country.t
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ is(phone2country('+67210123'), 'AQ', '+67210 is AQ');
is(phone2country('+67211123'), 'AQ', '+67211 is AQ');
is(phone2country('+67212123'), 'AQ', '+67212 is AQ');
is(phone2country('+67213123'), 'AQ', '+67213 is AQ');
is(phone2country('+882345'), 'AQ', '+67213 is AQ'); # Global Networks Switzerland
is(phone2country('+882345'), 'AQ', '+882345 is AQ'); # Global Networks Switzerland
is(phone2country('+6723123'), 'NF', '+6723 is NF');
is(phone2country('+673123'), 'BN', '+673 is BN');
is(phone2country('+674123'), 'NR', '+674 is NR');
Expand Down Expand Up @@ -299,7 +299,9 @@ is(phone2country('+8816123'), 'Iridium', '+8816 is Iridium');
is(phone2country('+8817123'), 'Iridium', '+8817 is Iridium');
is(phone2country('+8818123'), 'Globalstar', '+8818 is Globalstar');
is(phone2country('+8819123'), 'Globalstar', '+8819 is Globalstar');
is(phone2country('+882123'), 'InternationalNetworks', '+882 is InternationalNetworks');
is(phone2country('+882123'), 'InternationalNetworks882', '+882 is InternationalNetworks882');
is(phone2country('+882130'), 'Telespazio', '+88213 is Telespazio');
is(phone2country('+883123'), 'InternationalNetworks883', '+883 is InternationalNetworks883');
is(phone2country('+886123'), 'TW', '+886 is TW');
is(phone2country('+90123'), 'TR', '+90 is TR');
is(phone2country('+91123'), 'IN', '+91 is IN');
Expand Down
2 changes: 1 addition & 1 deletion t/pod-coverage.t
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use nptestutils;
use Test::More;
eval "use Test::Pod::Coverage 1.08";
plan skip_all => "Test::Pod::Coverage 1.08 required for testing POD coverage" if $@;
foreach my $module (grep { $_ !~ m{\b(UK::Exchanges|Data|StubCountry(::..)?)$} } all_modules()) {
foreach my $module (grep { $_ !~ m{\b(UK::Exchanges|Data|StubCountry(::.*)?)$} } all_modules()) {
pod_coverage_ok($module);
}
done_testing();