Skip to content

Commit

Permalink
Closes #613
Browse files Browse the repository at this point in the history
Add support for Montenegro phone numbers
  • Loading branch information
TrevorPilley authored Apr 5, 2024
1 parent e0d8f44 commit 1bf2886
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ Luxembourg | LU | 352 | | | Yes
Malta | MT | 356 | | Yes | Yes | Yes | | Yes | Yes | Yes | | Yes
Moldova | MD | 373 | 0 | Yes | Yes | | | Yes | Yes | Yes | Yes |
Monaco | MC | 377 | | | Yes | | | Yes | | | | Yes
Montenegro | ME | 382 | 0 | Yes | Yes | | | Yes | Yes | Yes | Yes | Yes
Netherlands | NL | 31 | 0 | Yes | Yes | Yes | | Yes | Yes | Yes | | Yes
Norway | NO | 47 | | | Yes | | | Yes | | | | Yes
Poland | PL | 48 | | Yes | Yes | Yes | | Yes | | | |
Expand Down
14 changes: 14 additions & 0 deletions src/PhoneNumbers/CountryInfo_Europe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,20 @@ public partial class CountryInfo
NsnLengths = new ReadOnlyCollection<int>([8, 9, 12]),
};

/// <summary>
/// Gets the <see cref="CountryInfo"/> for Montenegro.
/// </summary>
public static CountryInfo Montenegro { get; } = new()
{
CallingCode = "382",
Continent = Europe,
Iso3166Code = "ME",
Name = "Montenegro",
NdcLengths = new ReadOnlyCollection<int>([2]),
NsnLengths = new ReadOnlyCollection<int>([4, 5, 8, 12]),
TrunkPrefix = "0",
};

/// <summary>
/// Gets the <see cref="CountryInfo"/> for Netherlands.
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions src/PhoneNumbers/DataFiles/ME.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# References:
# - https://www.itu.int/dms_pub/itu-t/oth/02/02/T02020000DA0002PDFE.pdf
# - https://www.ekip.me/media/documents/general/1656937726_Plan%20numeracije%20CG.pdf
G|20|Podgorica, Tuzi, Danilovgrad, Kolašin|000000-999999|
G|30|Bar, Ulcinj|000000-999999|
G|31|Herceg Novi|000000-999999|
G|32|Kotor, Tivat|000000-999999|
G|33|Budva|000000-999999|
G|40|Nikšić, Šavnik, Plužine|000000-999999|
G|41|Cetinje|000000-999999|
G|50|Bijelo Polje, Mojkovac|000000-999999|
G|51|Berane, Andrijevica, Rožaje, Plav, Petnjica, Gusinje|000000-999999|
G|52|Pljevlja, Žabljak|000000-999999|
M|60,68||00-9999999999|
M|63,66-67,69||00-9999999999|
N|70,77-78||000000-999999|
N|71||0000000000-9999999999|M
N|80||000000-999999|F
N|83||000000-999999|S
N|94-95||000000-999999|R
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
namespace PhoneNumbers.Tests.Parsers;

/// <summary>
/// Contains unit tests for the <see cref="DefaultPhoneNumberParser"/> class for Montenegro <see cref="PhoneNumber"/>s.
/// </summary>
public class DefaultPhoneNumberParserTests_ME_GeographicNumber
{
private static readonly PhoneNumberParser s_parser = DefaultPhoneNumberParser.Create(CountryInfo.Montenegro);

[Theory]
[InlineData("020000000", "20", "000000", "Podgorica, Tuzi, Danilovgrad, Kolašin")]
[InlineData("020999999", "20", "999999", "Podgorica, Tuzi, Danilovgrad, Kolašin")]
public void Parse_Known_GeographicPhoneNumber_2X_NationalDestinationCode(string value, string NationalDestinationCode, string subscriberNumber, string geographicArea)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<GeographicPhoneNumber>(phoneNumber);

var geographicPhoneNumber = (GeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, geographicPhoneNumber.Country);
Assert.Equal(geographicArea, geographicPhoneNumber.GeographicArea);
Assert.Equal(NationalDestinationCode, geographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, geographicPhoneNumber.SubscriberNumber);
}

[Theory]
[InlineData("030000000", "30", "000000", "Bar, Ulcinj")]
[InlineData("030999999", "30", "999999", "Bar, Ulcinj")]
[InlineData("031000000", "31", "000000", "Herceg Novi")]
[InlineData("031999999", "31", "999999", "Herceg Novi")]
[InlineData("032000000", "32", "000000", "Kotor, Tivat")]
[InlineData("032999999", "32", "999999", "Kotor, Tivat")]
[InlineData("033000000", "33", "000000", "Budva")]
[InlineData("033999999", "33", "999999", "Budva")]
public void Parse_Known_GeographicPhoneNumber_3X_NationalDestinationCode(string value, string NationalDestinationCode, string subscriberNumber, string geographicArea)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<GeographicPhoneNumber>(phoneNumber);

var geographicPhoneNumber = (GeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, geographicPhoneNumber.Country);
Assert.Equal(geographicArea, geographicPhoneNumber.GeographicArea);
Assert.Equal(NationalDestinationCode, geographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, geographicPhoneNumber.SubscriberNumber);
}

[Theory]
[InlineData("040000000", "40", "000000", "Nikšić, Šavnik, Plužine")]
[InlineData("040999999", "40", "999999", "Nikšić, Šavnik, Plužine")]
[InlineData("041000000", "41", "000000", "Cetinje")]
[InlineData("041999999", "41", "999999", "Cetinje")]
public void Parse_Known_GeographicPhoneNumber_4X_NationalDestinationCode(string value, string NationalDestinationCode, string subscriberNumber, string geographicArea)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<GeographicPhoneNumber>(phoneNumber);

var geographicPhoneNumber = (GeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, geographicPhoneNumber.Country);
Assert.Equal(geographicArea, geographicPhoneNumber.GeographicArea);
Assert.Equal(NationalDestinationCode, geographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, geographicPhoneNumber.SubscriberNumber);
}

[Theory]
[InlineData("050000000", "50", "000000", "Bijelo Polje, Mojkovac")]
[InlineData("050999999", "50", "999999", "Bijelo Polje, Mojkovac")]
[InlineData("051000000", "51", "000000", "Berane, Andrijevica, Rožaje, Plav, Petnjica, Gusinje")]
[InlineData("051999999", "51", "999999", "Berane, Andrijevica, Rožaje, Plav, Petnjica, Gusinje")]
[InlineData("052000000", "52", "000000", "Pljevlja, Žabljak")]
[InlineData("052999999", "52", "999999", "Pljevlja, Žabljak")]
public void Parse_Known_GeographicPhoneNumber_5X_NationalDestinationCode(string value, string NationalDestinationCode, string subscriberNumber, string geographicArea)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<GeographicPhoneNumber>(phoneNumber);

var geographicPhoneNumber = (GeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, geographicPhoneNumber.Country);
Assert.Equal(geographicArea, geographicPhoneNumber.GeographicArea);
Assert.Equal(NationalDestinationCode, geographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, geographicPhoneNumber.SubscriberNumber);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace PhoneNumbers.Tests.Parsers;

/// <summary>
/// Contains unit tests for the <see cref="DefaultPhoneNumberParser"/> class for Montenegro <see cref="PhoneNumber"/>s.
/// </summary>
public class DefaultPhoneNumberParserTests_ME_MobilePhoneNumber
{
private static readonly PhoneNumberParser s_parser = DefaultPhoneNumberParser.Create(CountryInfo.Montenegro);

[Theory]
[InlineData("06000", "60", "00")]
[InlineData("0609999999999", "60", "9999999999")]
[InlineData("06300", "63", "00")]
[InlineData("0639999999999", "63", "9999999999")]
[InlineData("06600", "66", "00")]
[InlineData("0669999999999", "66", "9999999999")]
[InlineData("06700", "67", "00")]
[InlineData("0679999999999", "67", "9999999999")]
[InlineData("06800", "68", "00")]
[InlineData("0689999999999", "68", "9999999999")]
[InlineData("06900", "69", "00")]
[InlineData("0699999999999", "69", "9999999999")]
public void Parse_Known_MobilePhoneNumber(string value, string NationalDestinationCode, string subscriberNumber)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<MobilePhoneNumber>(phoneNumber);

var mobilePhoneNumber = (MobilePhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, mobilePhoneNumber.Country);
Assert.False(mobilePhoneNumber.IsPager);
Assert.False(mobilePhoneNumber.IsVirtual);
Assert.Equal(NationalDestinationCode, mobilePhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, mobilePhoneNumber.SubscriberNumber);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
namespace PhoneNumbers.Tests.Parsers;

/// <summary>
/// Contains unit tests for the <see cref="DefaultPhoneNumberParser"/> class for Montenegro <see cref="PhoneNumber"/>s.
/// </summary>
public class DefaultPhoneNumberParserTests_ME_NonGeographicPhoneNumber
{
private static readonly PhoneNumberParser s_parser = DefaultPhoneNumberParser.Create(CountryInfo.Montenegro);

[Theory]
[InlineData("070000000", "70", "000000")]
[InlineData("070999999", "70", "999999")]
[InlineData("077000000", "77", "000000")]
[InlineData("077999999", "77", "999999")]
[InlineData("078000000", "78", "000000")]
[InlineData("078999999", "78", "999999")]
public void Parse_Known_NonGeographicPhoneNumber_7X_NationalDestinationCode(string value, string NationalDestinationCode, string subscriberNumber)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<NonGeographicPhoneNumber>(phoneNumber);

var nonGeographicPhoneNumber = (NonGeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, nonGeographicPhoneNumber.Country);
Assert.False(nonGeographicPhoneNumber.IsFreephone);
Assert.False(nonGeographicPhoneNumber.IsMachineToMachine);
Assert.False(nonGeographicPhoneNumber.IsPremiumRate);
Assert.False(nonGeographicPhoneNumber.IsSharedCost);
Assert.Equal(NationalDestinationCode, nonGeographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, nonGeographicPhoneNumber.SubscriberNumber);
}

[Theory]
[InlineData("080000000", "80", "000000")]
[InlineData("080999999", "80", "999999")]
public void Parse_Known_NonGeographicPhoneNumber_Freephone(string value, string NationalDestinationCode, string subscriberNumber)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<NonGeographicPhoneNumber>(phoneNumber);

var nonGeographicPhoneNumber = (NonGeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, nonGeographicPhoneNumber.Country);
Assert.True(nonGeographicPhoneNumber.IsFreephone);
Assert.False(nonGeographicPhoneNumber.IsMachineToMachine);
Assert.False(nonGeographicPhoneNumber.IsPremiumRate);
Assert.False(nonGeographicPhoneNumber.IsSharedCost);
Assert.Equal(NationalDestinationCode, nonGeographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, nonGeographicPhoneNumber.SubscriberNumber);
}

[Theory]
[InlineData("0710000000000", "71", "0000000000")]
[InlineData("0719999999999", "71", "9999999999")]
public void Parse_Known_NonGeographicPhoneNumber_MachineToMachine(string value, string NationalDestinationCode, string subscriberNumber)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<NonGeographicPhoneNumber>(phoneNumber);

var nonGeographicPhoneNumber = (NonGeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, nonGeographicPhoneNumber.Country);
Assert.False(nonGeographicPhoneNumber.IsFreephone);
Assert.True(nonGeographicPhoneNumber.IsMachineToMachine);
Assert.False(nonGeographicPhoneNumber.IsPremiumRate);
Assert.False(nonGeographicPhoneNumber.IsSharedCost);
Assert.Equal(NationalDestinationCode, nonGeographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, nonGeographicPhoneNumber.SubscriberNumber);
}

[Theory]
[InlineData("094000000", "94", "000000")]
[InlineData("094999999", "94", "999999")]
[InlineData("095000000", "95", "000000")]
[InlineData("095999999", "95", "999999")]
public void Parse_Known_NonGeographicPhoneNumber_PremiumRate(string value, string NationalDestinationCode, string subscriberNumber)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<NonGeographicPhoneNumber>(phoneNumber);

var nonGeographicPhoneNumber = (NonGeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, nonGeographicPhoneNumber.Country);
Assert.False(nonGeographicPhoneNumber.IsFreephone);
Assert.False(nonGeographicPhoneNumber.IsMachineToMachine);
Assert.True(nonGeographicPhoneNumber.IsPremiumRate);
Assert.False(nonGeographicPhoneNumber.IsSharedCost);
Assert.Equal(NationalDestinationCode, nonGeographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, nonGeographicPhoneNumber.SubscriberNumber);
}

[Theory]
[InlineData("083000000", "83", "000000")]
[InlineData("083999999", "83", "999999")]
public void Parse_Known_NonGeographicPhoneNumber_SharedCost(string value, string NationalDestinationCode, string subscriberNumber)
{
var parseResult = s_parser.Parse(value);
parseResult.ThrowIfFailure();

var phoneNumber = parseResult.PhoneNumber;

Assert.NotNull(phoneNumber);
Assert.IsType<NonGeographicPhoneNumber>(phoneNumber);

var nonGeographicPhoneNumber = (NonGeographicPhoneNumber)phoneNumber;
Assert.Equal(CountryInfo.Montenegro, nonGeographicPhoneNumber.Country);
Assert.False(nonGeographicPhoneNumber.IsFreephone);
Assert.False(nonGeographicPhoneNumber.IsMachineToMachine);
Assert.False(nonGeographicPhoneNumber.IsPremiumRate);
Assert.True(nonGeographicPhoneNumber.IsSharedCost);
Assert.Equal(NationalDestinationCode, nonGeographicPhoneNumber.NationalDestinationCode);
Assert.Equal(subscriberNumber, nonGeographicPhoneNumber.SubscriberNumber);
}
}
20 changes: 20 additions & 0 deletions test/PhoneNumbers.Tests/CountryInfo_Europe_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,26 @@ public void CountryInfo_Monaco()
Assert.Null(countryInfo.TrunkPrefix);
}

[Fact]
public void CountryInfo_Montenegro()
{
Assert.Same(CountryInfo.Montenegro, CountryInfo.Montenegro);

var countryInfo = CountryInfo.Montenegro;

Assert.False(countryInfo.AllowsLocalGeographicDialling);
Assert.Equal("382", countryInfo.CallingCode);
Assert.Equal(CountryInfo.Europe, countryInfo.Continent);
Assert.IsType<ComplexPhoneNumberFormatProvider>(countryInfo.FormatProvider);
Assert.False(countryInfo.IsEuropeanUnionMember);
Assert.Equal("ME", countryInfo.Iso3166Code);
Assert.Equal("Montenegro", countryInfo.Name);
Assert.Equal(new[] { 2 }, countryInfo.NdcLengths);
Assert.Equal(new[] { 4, 5, 8, 12 }, countryInfo.NsnLengths);
Assert.False(countryInfo.SharesCallingCode);
Assert.Equal("0", countryInfo.TrunkPrefix);
}

[Fact]
public void CountryInfo_Netherlands()
{
Expand Down
8 changes: 8 additions & 0 deletions test/PhoneNumbers.Tests/PhoneNumber_Parse_Europe_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@ public void Parse_Value_For_Monaco_CallingCode()
Assert.Equal(CountryInfo.Monaco, phoneNumber.Country);
}

[Fact]
public void Parse_Value_For_Montenegro_CallingCode()
{
var phoneNumber = PhoneNumber.Parse("+38220406700");
Assert.NotNull(phoneNumber);
Assert.Equal(CountryInfo.Montenegro, phoneNumber.Country);
}

[Fact]
public void Parse_Value_For_Netherlands_CallingCode()
{
Expand Down
7 changes: 7 additions & 0 deletions test/PhoneNumbers.Tests/PhoneNumber_ToString_Europe_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ public void Moldova_Numbers(string input, string format, string expected) =>
public void Monaco_Numbers(string input, string format, string expected) =>
Assert.Equal(expected, PhoneNumber.Parse(input).ToString(format));

[Theory]
[InlineData("+38220406700", "E.123", "+382 20 406 700")]
[InlineData("+38220406700", "N", "020 406 700")]
[InlineData("+38220406700", "RFC3966", "tel:+382-20-406-700")]
public void Montenegro_Numbers(string input, string format, string expected) =>
Assert.Equal(expected, PhoneNumber.Parse(input).ToString(format));

[Theory]
[InlineData("+31702140214", "E.123", "+31 7 02 14 02 14")]
[InlineData("+31702140214", "N", "07 02 14 02 14")]
Expand Down

0 comments on commit 1bf2886

Please sign in to comment.