(core) Add PHONE_FORMAT function using the phonenumberslight library

Summary:
Add phonenumberslite-8.12.57 to requirements
Implement PHONE_FORMAT function.

Test Plan: Added doctest test cases

Reviewers: jarek

Reviewed By: jarek

Subscribers: jarek

Differential Revision: https://phab.getgrist.com/D3665
pull/323/head
Dmitry S 2 years ago
parent 62792329c3
commit 0a8ce2178a

@ -5,8 +5,9 @@ import numbers
import re import re
import dateutil.parser import dateutil.parser
import phonenumbers
import six import six
from six import unichr from six import unichr # pylint: disable=redefined-builtin
from six.moves import xrange from six.moves import xrange
from usertypes import AltText # pylint: disable=import-error from usertypes import AltText # pylint: disable=import-error
@ -77,7 +78,7 @@ def CONCATENATE(string, *more_strings):
>>> assert CONCATENATE(2, " crème ", "brûlée") == u'2 crème brûlée' >>> assert CONCATENATE(2, " crème ", "brûlée") == u'2 crème brûlée'
""" """
return u''.join( return u''.join(
val.decode('utf8') if isinstance(val, six.binary_type) else val.decode('utf8') if isinstance(val, six.binary_type) else # pylint:disable=no-member
six.text_type(val) six.text_type(val)
for val in (string,) + more_strings for val in (string,) + more_strings
) )
@ -100,7 +101,6 @@ def CONCAT(string, *more_strings):
""" """
return CONCATENATE(string, *more_strings) return CONCATENATE(string, *more_strings)
def DOLLAR(number, decimals=2): def DOLLAR(number, decimals=2):
""" """
Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. Formats a number into a formatted dollar amount, with decimals rounded to the specified place (.
@ -267,6 +267,103 @@ def MID(text, start_num, num_chars):
return text[start_num - 1 : start_num - 1 + num_chars] return text[start_num - 1 : start_num - 1 + num_chars]
output_formats = {
"+": phonenumbers.PhoneNumberFormat.INTERNATIONAL,
"INTL": phonenumbers.PhoneNumberFormat.INTERNATIONAL,
"#": phonenumbers.PhoneNumberFormat.NATIONAL,
"NATL": phonenumbers.PhoneNumberFormat.NATIONAL,
"*": phonenumbers.PhoneNumberFormat.E164,
"E164": phonenumbers.PhoneNumberFormat.E164,
"tel": phonenumbers.PhoneNumberFormat.RFC3966,
"RFC3966": phonenumbers.PhoneNumberFormat.RFC3966,
}
def PHONE_FORMAT(value, country=None, format=None): # pylint: disable=redefined-builtin
"""
Formats a phone number.
With no optional arguments, the number must start with "+" and the international dialing prefix,
and will be formatted as an international number, e.g. `+12345678901` becomes `+1 234-567-8901`.
The `country` argument allows specifying a 2-letter country code (e.g. "US" or "GB") for
interpreting phone numbers that don't start with "+". E.g. `PHONE_FORMAT('2025555555', 'US')`
would be seen as a US number and formatted as "(202) 555-5555". Phone numbers that start with
"+" ignore `country`. E.g. `PHONE_FORMAT('+33555555555', 'US')` is a French number because '+33'
is the international prefix for France.
The `format` argument specifies the output format, according to this table:
- `"#"` or `"NATL"` (default) - use the national format, without the international dialing
prefix, when possible. E.g. `(234) 567-8901` for "US", or `02 34 56 78 90` for "FR". If
`country` is omitted, or the number does not correspond to the given country, the
international format is used instead.
- `"+"` or `"INTL"` - international format, e.g. `+1 234-567-8901` or
`+33 2 34 56 78 90`.
- `"*"` or `"E164"` - E164 format, like international but with no separators, e.g.
`+12345678901`.
- `"tel"` or `"RFC3966"` - format suitable to use as a [hyperlink](col-types.md#hyperlinks),
e.g. 'tel:+1-234-567-8901'.
When specifying the `format` argument, you may omit the `country` argument. I.e.
`PHONE_FORMAT(value, "tel")` is equivalent to `PHONE_FORMAT(value, None, "tel")`.
For more details, see the [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers)
Python library, which underlies this function.
>>> PHONE_FORMAT("+12345678901")
u'+1 234-567-8901'
>>> PHONE_FORMAT("2345678901", "US")
u'(234) 567-8901'
>>> PHONE_FORMAT("2345678901", "GB")
u'023 4567 8901'
>>> PHONE_FORMAT("2345678901", "GB", "+")
u'+44 23 4567 8901'
>>> PHONE_FORMAT("+442345678901", "GB")
u'023 4567 8901'
>>> PHONE_FORMAT("+12345678901", "GB")
u'+1 234-567-8901'
>>> PHONE_FORMAT("(234) 567-8901") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
NumberParseException: (0) Missing or invalid default region.
>>> PHONE_FORMAT("(234)567 89-01", "US", "tel")
u'tel:+1-234-567-8901'
>>> PHONE_FORMAT("2/3456/7890", "FR", '#')
u'02 34 56 78 90'
>>> PHONE_FORMAT("+33234567890", '#')
u'+33 2 34 56 78 90'
>>> PHONE_FORMAT("+33234567890", 'tel')
u'tel:+33-2-34-56-78-90'
>>> PHONE_FORMAT("tel:+1-234-567-8901", country="US", format="*")
u'+12345678901'
"""
if not value:
return value
if format is None and country in output_formats:
format = country
country = None
parsed = phonenumbers.parse(str(value), country)
out_fmt = output_formats.get(format or "#")
if out_fmt is None:
raise ValueError("Unrecognized phone format; try +, INTL, #, NATL, *, E164, tel, or RFC3966")
if out_fmt == phonenumbers.PhoneNumberFormat.NATIONAL and not country:
# With no country, we lose info in NATIONAL format (because numbers must be specified with an
# international prefix, and the output would discard it). Use INTERNATIONAL instead.
out_fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL
result = phonenumbers.format_number(parsed, out_fmt)
# If using a national format with a country, check that we don't garble numbers with a different
# international prefix. If so, use an international format. E.g. for
# PHONE_FORMAT('+12345678901', 'FR'), the output should include the US dialing prefix.
if (out_fmt == phonenumbers.PhoneNumberFormat.NATIONAL and country and
phonenumbers.parse(result, country) != parsed):
result = phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
return result
def PROPER(text): def PROPER(text):
""" """
Capitalizes each word in a specified string. It converts the first letter of each word to Capitalizes each word in a specified string. It converts the first letter of each word to
@ -541,7 +638,7 @@ def T(value):
@unimplemented @unimplemented
def TEXT(number, format_type): def TEXT(number, format_type): # pylint: disable=unused-argument
""" """
Converts a number into text according to a specified format. It is not yet implemented in Converts a number into text according to a specified format. It is not yet implemented in
Grist. Grist.

@ -7,6 +7,7 @@ chardet==4.0.0
enum34==1.1.10 enum34==1.1.10
iso8601==0.1.12 iso8601==0.1.12
lazy_object_proxy==1.6.0 lazy_object_proxy==1.6.0
phonenumberslite==8.12.57
python_dateutil==2.8.2 python_dateutil==2.8.2
roman==2.0.0 roman==2.0.0
singledispatch==3.6.2 singledispatch==3.6.2

@ -21,6 +21,7 @@ chardet==4.0.0
enum34==1.1.10 enum34==1.1.10
iso8601==0.1.12 iso8601==0.1.12
lazy_object_proxy==1.6.0 lazy_object_proxy==1.6.0
phonenumberslite==8.12.57
python_dateutil==2.8.2 python_dateutil==2.8.2
roman==2.0.0 roman==2.0.0
singledispatch==3.6.2 singledispatch==3.6.2

Loading…
Cancel
Save