Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse and build timezone data into stubs #134

Merged
merged 8 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build-data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ if test ! -e lib/Number/Phone/StubCountry/KZ.pm -o \
buildtools/Number/Phone/BuildHelpers.pm -nt lib/Number/Phone/StubCountry/KZ.pm -o \
build-data.stubs -nt lib/Number/Phone/StubCountry/KZ.pm -o \
libphonenumber/resources/geocoding/en/1.txt -nt lib/Number/Phone/StubCountry/KZ.pm -o \
libphonenumber/resources/timezones/map_data.txt -nt lib/Number/Phone/StubCountry/KZ.pm -o \
libphonenumber/resources/PhoneNumberMetadata.xml -nt lib/Number/Phone/StubCountry/KZ.pm -o \
lib/Number/Phone/NANP/Data.pm -nt lib/Number/Phone/StubCountry/KZ.pm;
then
Expand All @@ -276,7 +277,7 @@ then
if test ! -e lib/Number/Phone/StubCountry/KZ.pm; then
echo " because they don't all exist"
else
ls -ltr lib/Number/Phone/StubCountry/KZ.pm buildtools/Number/Phone/BuildHelpers.pm build-data.stubs libphonenumber/resources/geocoding/en/1.txt libphonenumber/resources/PhoneNumberMetadata.xml lib/Number/Phone/NANP/Data.pm | \
ls -ltr lib/Number/Phone/StubCountry/KZ.pm buildtools/Number/Phone/BuildHelpers.pm build-data.stubs libphonenumber/resources/geocoding/en/1.txt libphonenumber/resources/timezones/map_data.txt libphonenumber/resources/PhoneNumberMetadata.xml lib/Number/Phone/NANP/Data.pm | \
sed 's/^/ /'
fi

Expand Down
56 changes: 55 additions & 1 deletion build-data.stubs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ $Data::Dumper::Indent = 2;
$Data::Dumper::Sortkeys = 1;

use lib 'lib';
use Number::Phone::Country;
use Number::Phone::NANP::Data;

use lib 'buildtools';
Expand All @@ -24,6 +25,35 @@ use XML::XPath;
my $xml = XML::XPath->new(filename => 'libphonenumber/resources/PhoneNumberMetadata.xml');
my @territories = $xml->find('/phoneNumberMetadata/territories/territory')->get_nodelist();

my %prefix_to_timezones;
open(my $timezones_fh, "<:encoding(utf8)", 'libphonenumber/resources/timezones/map_data.txt')
percivalalb marked this conversation as resolved.
Show resolved Hide resolved
or die "Opening 'map_data.txt': $!\n";
while (my $line = <$timezones_fh>) {
$line =~ s/^\s+|\s+$//g;

# Skip empty or comment lines
if (!$line || $line =~ /^#/) {
next;
}

# Assert pipe deliminated
if ($line !~ /\|/) {
die "malformatted data, expected '|''\n";
}

my ($prefix, $tz_string) = split(/\|/, $line, 2);
unless ($tz_string) {
die "missing time zones\n";
}

if ($prefix_to_timezones{$prefix}) {
die "duplicated prefix $prefix\n";
}

$prefix_to_timezones{$prefix} = $tz_string;
}
close($timezones_fh);

open(my $manifest_fh, 'MANIFEST') || die("Can't read MANIFEST\n");
my @manifest_files = grep { /./ } <$manifest_fh>;
close($manifest_fh);
Expand Down Expand Up @@ -141,12 +171,36 @@ TERRITORY: foreach my $territory (@territories) {
print $module_fh "};\n";
}

my %timezones;

while (my ($prefix, $tz_string) = each %prefix_to_timezones) {
if ($prefix !~ /^$IDD_country_code/) {
next;
}

my $national_prefix = $prefix =~ s/^$IDD_country_code//r;

# For NANP phone numbers, we only need to include timezone mappings if the prefix
# might apply to the country. E.g. Jamiaca which also use the +1 country code do
# not need to include timezone mappings for Texas area codes.
if ($IDD_country_code eq "1") {
percivalalb marked this conversation as resolved.
Show resolved Hide resolved
my ($area) = $national_prefix =~ /^1(\d{3})/; # take the next 3 digits (if they exit)
if ($area && $area !~ /^($Number::Phone::Country::NANP_areas{$ISO_country_code})$/x) {
next;
}
}

$timezones{$national_prefix} = [split(/&/, $tz_string)];
}

print $module_fh 'my '.Data::Dumper->new([\%timezones], [qw(timezones)])->Dump();

print $module_fh "
sub new {
my \$class = shift;
my \$number = shift;
\$number =~ s/(^\\+$IDD_country_code|\\D)//g;
my \$self = bless({ country_code => '$IDD_country_code', number => \$number, formatters => \$formatters, validators => \$validators, ".
my \$self = bless({ country_code => '$IDD_country_code', number => \$number, formatters => \$formatters, validators => \$validators, timezones => \$timezones, ".
(($IDD_country_code != 1 && $got_area_names) ? 'areanames => \\%areanames' : '')
."}, \$class);
";
Expand Down
23 changes: 23 additions & 0 deletions lib/Number/Phone.pm
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ sub format_for_country {
return $self->format_using('NationallyPreferredIntl');
}

sub timezones {
my $self = shift;

if (my $stub = Number::Phone::Lib->new($self->format)) {
return $stub->timezones;
}

return undef;
}

1;

=head1 NAME
Expand Down Expand Up @@ -529,6 +539,19 @@ Return the subscriber part of the number.
While the superclass implementation returns undef, this is nonsense in just
about all cases, so you should always implement this.

=item timezones

This returns a list-ref of the timezones that could be assoicated with a
geographic number or with the country for non geographic numbers. Returns
undef in the case that possible timezones are unknown.

Data is sourced from Google's libphonenumber project therefore implementation
lies in the stub-countries which return timezones e.g. Europe/London, America/New_York.
Non-stub implementations by default return their stub-country counterpart's
result.

Ordering is arbitrary though is typically alphabetical.

=item operator

Return the name of the telco assigned this number, in an appropriate
Expand Down
16 changes: 16 additions & 0 deletions lib/Number/Phone/StubCountry.pm
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,20 @@ sub format {
return '+'.$self->country_code().' '.$number;
}

sub timezones {
my $self = shift;

# If non-geographic use the country-level timezones
my $number = $self->is_geographic() ? $self->raw_number() : $self->country_code();

foreach my $i (reverse (0..length($number))) {
if (my $timezones = $self->{timezones}->{substr($number, 0, $i)}) {
my $copy = [@$timezones]; # copy the list-ref to avoid manipulation
percivalalb marked this conversation as resolved.
Show resolved Hide resolved
return $copy;
}
}

return undef;
}

1;
41 changes: 41 additions & 0 deletions t/timezones.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use strict;
use warnings;
use lib 't/inc';
use nptestutils;

use Number::Phone;
use Number::Phone::Lib;
use Number::Phone::NANP;
use Test::More;

my $all_nanp_timezones = ['America/Adak','America/Anchorage','America/Anguilla','America/Antigua','America/Barbados','America/Boise','America/Cayman','America/Chicago','America/Denver','America/Dominica','America/Edmonton','America/Fort_Nelson','America/Grand_Turk','America/Grenada','America/Halifax','America/Jamaica','America/Juneau','America/Los_Angeles','America/Lower_Princes','America/Montserrat','America/Nassau','America/New_York','America/North_Dakota/Center','America/Phoenix','America/Port_of_Spain','America/Puerto_Rico','America/Regina','America/Santo_Domingo','America/St_Johns','America/St_Kitts','America/St_Lucia','America/St_Thomas','America/St_Vincent','America/Toronto','America/Tortola','America/Vancouver','America/Winnipeg','Atlantic/Bermuda','Pacific/Guam','Pacific/Honolulu','Pacific/Pago_Pago','Pacific/Saipan'];

my %tests = (
'+442087712924' => ['Europe/London'], # Geographic UK number
'+445511000000' => ['Europe/Guernsey','Europe/Isle_of_Man','Europe/Jersey','Europe/London'], # Non-geographic UK number
'+12024181440' => ['America/New_York'], # geographic New York number
'+18765551234' => ['America/Jamaica'], # geographic Jamaican number
percivalalb marked this conversation as resolved.
Show resolved Hide resolved
'+12642920000' => ['America/Anguilla'], # geographic Anguilla number
'+12642350000' => $all_nanp_timezones, # mobile Anguilla number
'+17875551234' => ['America/Puerto_Rico'], # 1st Puerto Rico area code
'+19395551234' => ['America/Puerto_Rico'], # 2nd Puerto Rico area code
'+81335803311' => ['Asia/Tokyo'], # geographic Japanese number
'+815012345678' => ['Asia/Tokyo'], # non-geographic Japanese number
'+18885558888' => $all_nanp_timezones, # Non-geographic NANP number.
'+80012345678' => undef, # International Toll-Free.
);

note("timezones()");
while (my ($num, $expect) = each %tests) {
my $number = Number::Phone::Lib->new($num);
is_deeply($number->timezones(), $expect, "timezone of $num using libphonenumber");
}

# Non-stubs only return libnumberphone data.
is_deeply(Number::Phone->new('+18885558888')->timezones(), $all_nanp_timezones, 'non-stubs returns the same as the stub country implementation');
is_deeply(Number::Phone::NANP->new('+12024181440')->timezones(), ['America/New_York'], 'non-stubs returns the same as the stub country implementation');

# International Toll-Free numbers return unknown.
is(Number::Phone->new('+80012345678')->timezones(), undef, 'international Toll-Free numbers (+800) return unknown');

done_testing();
Loading