Skip to content

Commit

Permalink
Add NumberToDialFrom
Browse files Browse the repository at this point in the history
  • Loading branch information
TrevorPilley committed Nov 25, 2024
1 parent 332140c commit ae1f423
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 1 deletion.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,23 @@ To out out of specific countries but still use any new ones added in future vers
ParseOptions.Default.Countries.Remove(CountryInfo.X);
```

## Dial helper

The library contains helper methods to determine the number that needs to be dialled for a call between two phone numbers.

```csharp
var callerNumber = PhoneNumber.Parse("+441142726444");

PhoneNumber.Parse("+441146548866").NumberToDialFrom(callerNumber); // 6548866 UK local dialling within same NDC
PhoneNumber.Parse("+447106865391").NumberToDialFrom(callerNumber); // 07106865391 UK mobile from UK landline
PhoneNumber.Parse("+33140477283").NumberToDialFrom(callerNumber); // 0033140477283 French mobile from UK landline
// Alternatively a destination number from a specific country
PhoneNumber.Parse("+441146548866").NumberToDialFrom(CountryInfo.UnitedKingdom); // 01146548866 UK landline from UK
PhoneNumber.Parse("+447106865391").NumberToDialFrom(CountryInfo.UnitedKingdom); // 07106865391 UK mobile from UK
PhoneNumber.Parse("+33140477283").NumberToDialFrom(CountryInfo.UnitedKingdom); // 0033140477283 French mobile from UK
```

## Versioning

The library adheres to [Semantic Versioning](https://semver.org) and [release notes](https://github.com/TrevorPilley/phone-number-parser/releases) are provided for every published version.
Expand Down
77 changes: 76 additions & 1 deletion src/PhoneNumbers/PhoneNumberExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,79 @@ namespace PhoneNumbers;
/// <summary>
/// A class containing extension methods for the <see cref="PhoneNumber"/> class.
/// </summary>
internal static class PhoneNumberExtensions
public static class PhoneNumberExtensions
{
/// <summary>
/// Determines the number needed to dial a <see cref="PhoneNumber"/> from another <see cref="CountryInfo"/>.
/// </summary>
/// <param name="destination">The <see cref="PhoneNumber"/> to dial.</param>
/// <param name="countryInfo">The <see cref="CountryInfo"/> to dial from.</param>
/// <exception cref="ArgumentNullException">Thrown if the specified <paramref name="destination"/> or <paramref name="countryInfo"/> is null.</exception>
/// <returns>Returns the number needed to dial a <see cref="PhoneNumber"/> from another <see cref="CountryInfo"/>.</returns>
public static string NumberToDialFrom(this PhoneNumber destination, CountryInfo countryInfo)
{
if (destination is null)
{
throw new ArgumentNullException(nameof(destination));
}

if (countryInfo is null)
{
throw new ArgumentNullException(nameof(countryInfo));
}

if (destination.Country.SharesCallingCodeWith(countryInfo))
{
return destination.ToString("U");
}

if (countryInfo.InternationalCallPrefixes.TryGetValue(destination.Country.CallingCode, out var callPrefix))
{
return $"{callPrefix}{destination.NationalSignificantNumber}";
}

if (countryInfo == CountryInfo.Italy && destination.IsSanMarinoLandline())
{
return !destination.SubscriberNumber.StartsWith("0549", StringComparison.Ordinal)
? $"0549{destination.SubscriberNumber}"
: destination.SubscriberNumber;
}

return $"{countryInfo.InternationalCallPrefix}{destination.Country.CallingCode}{destination.NationalSignificantNumber}";
}

/// <summary>
/// Determines the number needed to dial a <see cref="PhoneNumber"/> from another <see cref="PhoneNumber"/>.
/// </summary>
/// <param name="destination">The <see cref="PhoneNumber"/> to dial.</param>
/// <param name="source">The <see cref="PhoneNumber"/> to dial from.</param>
/// <exception cref="ArgumentNullException">Thrown if the specified <paramref name="source"/> or <paramref name="destination"/> is null.</exception>
/// <returns>Returns the number needed to dial a <see cref="PhoneNumber"/> from another <see cref="PhoneNumber"/>.</returns>
public static string NumberToDialFrom(this PhoneNumber destination, PhoneNumber source)
{
if (destination is null)
{
throw new ArgumentNullException(nameof(destination));
}

if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

if (source.Country.SharesCallingCodeWith(destination.Country))
{
if (source.SharesNdcWith(destination) && source.NdcIsOptional())
{
return destination.SubscriberNumber;
}

return destination.ToString("U");
}

return NumberToDialFrom(destination, source.Country);
}

/// <summary>
/// Gets a value indicating if the National Destination Code is optional for the specified <see cref="PhoneNumber"/> instance.
/// </summary>
Expand All @@ -15,4 +86,8 @@ internal static bool NdcIsOptional(this PhoneNumber phoneNumber) =>
phoneNumber.Country.AllowsLocalGeographicDialling &&
phoneNumber.Kind == PhoneNumberKind.GeographicPhoneNumber &&
!((GeographicPhoneNumber)phoneNumber).NationalDiallingOnly;

private static bool IsSanMarinoLandline(this PhoneNumber phoneNumber) =>
phoneNumber.Country == CountryInfo.SanMarino &&
(phoneNumber.SubscriberNumber[0] == '0' || phoneNumber.SubscriberNumber[0] == '7' || phoneNumber.SubscriberNumber[0] == '8');
}
77 changes: 77 additions & 0 deletions test/PhoneNumbers.Tests/PhoneNumberExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,81 @@ public void NdcIsOptional_False_For_Geographic_Number_With_Local_Dialling_Allowe
[Fact]
public void NdcIsOptional_True_For_Geographic_Number_With_Local_Dialling_Allowed_Where_NDC_Is_Not_National_Dialling_Only() =>
Assert.True(TestHelper.CreateGeographicPhoneNumber("0", "1", "123", true, PhoneNumberHint.None).NdcIsOptional());

[Theory]
[InlineData("GB", "+447106865391", "07106865391")] // UK number from UK
[InlineData("GB", "+441481717000", "01481717000")] // Guernsey number from UK (countries share calling code)
[InlineData("GB", "+33140477283", "0033140477283")] // France number from UK
[InlineData("US", "+33140477283", "01133140477283")] // France number from US
[InlineData("GB", "+12124841200", "0012124841200")] // US number from UK
[InlineData("US", "+447106865391", "011447106865391")] // UK number from US
[InlineData("KE", "+255222199760", "007222199760")] // Tanzania from Kenya (uses 007 instead of +254)
[InlineData("KE", "+256414348832", "006414348832")] // Uganda from Kenya (uses 006 instead of +256)
[InlineData("KE", "+447106865391", "000447106865391")] // United Kingdom from Kenya
[InlineData("TZ", "+25420424200", "00520424200")] // Kenya from Tanzania (uses 005 instead of +255)
[InlineData("TZ", "+256414348832", "006414348832")] // Uganda from Tanzania (uses 006 instead of +256)
[InlineData("TZ", "+447106865391", "000447106865391")] // United Kingdom from Tanzania
[InlineData("UG", "+25420424200", "00520424200")] // Kenya from Uganda (uses 005 instead of +255)
[InlineData("UG", "+255222199760", "007222199760")] // Tanzania from Uganda (uses 006 instead of +256)
[InlineData("UG", "+447106865391", "000447106865391")] // United Kingdom from Uganda
[InlineData("IT", "+3780549882555", "0549882555")] // San Marino NonGeo with Italy NDC from Italy
[InlineData("IT", "+378882555", "0549882555")] // San Marino NonGeo with Italy NDC from Italy
[InlineData("IT", "+378693247", "00378693247")] // San Marino Mobile from Italy
[InlineData("IT", "+378598765", "00378598765")] // San Marino Mobile from Italy
[InlineData("CA", "+12124841200", "2124841200")] // US number from Canada
public void NumberToDialFrom_CountryInfo(string sourceCountryCode, string destination, string expected) =>
Assert.Equal(
expected,
PhoneNumber.Parse(destination).NumberToDialFrom(ParseOptions.Default.GetCountryInfo(sourceCountryCode)));

[Fact]
public void NumberToDialFrom_CountryInfo_Throws_If_CountryInfo_Null()
{
var phoneNumber1 = TestHelper.CreateMobilePhoneNumber(null, "1", "123");
Assert.Throws<ArgumentNullException>(() => phoneNumber1.NumberToDialFrom(default(CountryInfo)));
}

[Fact]
public void NumberToDialFrom_CountryInfo_Throws_If_PhoneNumber_Null() =>
Assert.Throws<ArgumentNullException>(() => default(PhoneNumber).NumberToDialFrom(CountryInfo.UnitedKingdom));

[Fact]
public void NumberToDialFrom_PhoneNumber_Throws_If_CountryInfo_Null()
{
Assert.Throws<ArgumentNullException>(() => default(PhoneNumber).NumberToDialFrom(TestHelper.CreateMobilePhoneNumber(null, "1", "123")));
Assert.Throws<ArgumentNullException>(() => TestHelper.CreateMobilePhoneNumber(null, "1", "123").NumberToDialFrom(default(PhoneNumber)));
}

[Theory]
[InlineData("+441142726444", "+441146548866", "6548866")] // UK Geo to Geo within NDC, local dialling permitted, NDC not required
[InlineData("+441202454877", "+441202653887", "01202653887")] // UK Geo to Geo within NDC, local dialling permitted, NDC required for local dialling due to number shortages
[InlineData("+441142726444", "+441773878912", "01773878912")] // UK Geo to Geo different NDC
[InlineData("+441142726444", "+443038709123", "03038709123")] // UK Geo to NonGeo
[InlineData("+441142726444", "+447106865391", "07106865391")] // UK Geo to Mobile
[InlineData("+441142726444", "+441481717000", "01481717000")] // UK Geo to Guernsey Geo, countries share calling code
[InlineData("+447106865391", "+441142726444", "01142726444")] // UK Mobile to Geo
[InlineData("+447106865391", "+447712674523", "07712674523")] // UK Mobile to Mobile
[InlineData("+447106865391", "+443038709123", "03038709123")] // UK Mobile to NonGeo
[InlineData("+447106865391", "+33140477283", "0033140477283")] // UK Mobile to France Geo
[InlineData("+447106865391", "+33601876543", "0033601876543")] // UK Mobile to France Mobile
[InlineData("+12124841200", "+447712674523", "011447712674523")] // US to UK Mobile
[InlineData("+85229615432", "+85229616333", "29616333")] // HK to HK
[InlineData("+25420424200", "+255222199760", "007222199760")] // Kenya to Tanzania (uses 007 instead of +255)
[InlineData("+25420424200", "+256414348832", "006414348832")] // Kenya to Uganda (uses 006 instead of +256)
[InlineData("+255222199760", "+25420424200", "00520424200")] // Tanzania to Kenya (uses 005 instead of +254)
[InlineData("+255222199760", "+256414348832", "006414348832")] // Tanzania to Uganda (uses 006 instead of +256)
[InlineData("+256414348832", "+25420424200", "00520424200")] // Uganda to Kenya (uses 005 instead of +254)
[InlineData("+256414348832", "+255222199760", "007222199760")] // Uganda to Tanzania (uses 007 instead of +255)
[InlineData("+393492525255", "+3780549882555", "0549882555")] // Italy to San Marino NonGeo with Italy NDC
[InlineData("+393492525255", "+378882555", "0549882555")] // Italy to San Marino NonGeo without Italy NDC
[InlineData("+393492525255", "+378693247", "00378693247")] // Italy to San Marino Mobile
[InlineData("+393492525255", "+378598765", "00378598765")] // Italy to San Marino IP
[InlineData("+12497121234", "+12494185634", "2494185634")] // Canada within NDC, local dialling permitted, NDC required
[InlineData("+18797121234", "+18794185634", "4185634")] // Canada within NDC, local dialling permitted, NDC not required
[InlineData("+19517121234", "+19514185634", "9514185634")] // US within NDC, local dialling permitted, NDC required
[InlineData("+15597121234", "+15594185634", "4185634")] // US within NDC, local dailling permitted, NDC not required
public void NumberToDialFrom_PhoneNumber(string source, string destination, string expected) =>
Assert.Equal(
expected,
PhoneNumber.Parse(destination).NumberToDialFrom(PhoneNumber.Parse(source)));
}

0 comments on commit ae1f423

Please sign in to comment.