Localizing Dates, Currency, and Numbers with Php-Intl

Younes Rafie
Younes Rafie
Share

The first part of this series was an introduction of the PHP Intl extension and of how to localize your application’s messages. In this part, we’re going to learn about localizing numbers, dates, calendars, and similar complex data. Let’s get started!

Globe stock illustration

Localizing Decimals

This may sound odd, but one of my main concerns when formatting numbers is working with decimal points, as they differ from place to place. Check Wikipedia for more details about different decimal mark variations.

Style
1,234,567.89
1234567.89
1234567,89
1,234,567·89
1.234.567,89
1˙234˙567,89
12,34,567.89
1’234’567.89
1’234’567,89
1.234.567’89
123,4567.89

The PHP Intl extension has a NumberFormatter which deals with number localization:

$numberFormatter = new NumberFormatter( 'de_DE', NumberFormatter::DECIMAL );
var_dump( $numberFormatter->format(123456789) );

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::DECIMAL );
var_dump( $numberFormatter->format(123456789) );

$numberFormatter = new NumberFormatter( 'ar', NumberFormatter::DECIMAL );
var_dump( $numberFormatter->format(123456789) );

$numberFormatter = new NumberFormatter( 'bn', NumberFormatter::DECIMAL );
var_dump( $numberFormatter->format(123456789) );
string(11) "123.456.789"
string(11) "123,456,789"
string(22) "١٢٣٬٤٥٦٬٧٨٩"
string(30) "১২,৩৪,৫৬,৭৮৯"

The first parameter is the locale code, and the second is the formatting style. In this case, we’re formatting decimals.

Formatting Styles

Formatting styles describe how our numbers should be formatted: decimal, currency, duration, etc. Check the list of available formatting styles in the documentation. Let’s try some examples for different styles:

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::DECIMAL );
$numberFormatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
var_dump( $numberFormatter->format(1234.56789) );

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::DECIMAL );
$numberFormatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
var_dump( $numberFormatter->format(1234) );
string(8) "1,234.57"
string(8) "1,234.00"

When specifying the fractional part attribute, the value is rounded up using the up mode. We can change that by specifying the rounding style.

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::DECIMAL );
$numberFormatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 2);
$numberFormatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_CEILING);
var_dump($numberFormatter->format(1234.5678) );

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::DECIMAL );
$numberFormatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 2);
$numberFormatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_DOWN);
var_dump($numberFormatter->format(1234.5678) );
string(8) "1,234.57"

string(8) "1,234.56"

The spellout and duration options from the first part can also be used here with the following options:

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::DURATION );
var_dump( $numberFormatter->format(123) );

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::SPELLOUT );
var_dump( $numberFormatter->format(123) );
string(4) "2:03"
string(24) "one hundred twenty-three"

We can also parse strings to get a formatted value from them:

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::DURATION );
var_dump( $numberFormatter->parse("4:03") );

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::SPELLOUT );
var_dump( $numberFormatter->parse("one hundred") );
float(243)

float(100)

Although we can totally make our application locale agnostic, we should switch between locales to test the various changes.

Localizing Currencies

Formatting numbers as currencies is not very different, we only change the formatting type and add the currency code.

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::CURRENCY );
var_dump( $numberFormatter->formatCurrency(1234.56789, "USD" ) );
string(9) "$1,234.57"

We can avoid typing the currency code for every locale by calling the getSymbol method on the NumberFormatter instance.

$numberFormatter = new NumberFormatter( 'en_US', NumberFormatter::CURRENCY );
var_dump( $numberFormatter->formatCurrency(1234.56789, $numberFormatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL)) );

$numberFormatter = new NumberFormatter( 'fr_FR', NumberFormatter::CURRENCY );
var_dump( $numberFormatter->formatCurrency(1234.56789, $numberFormatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL)) );
string(9) "$1,234.57"

string(14) "1 234,57 €"

We can use the attributes we mentioned earlier ($numberFormatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 2);) with currencies, too.

Timezones

Before using PHP Intl calendars, we need to brush up on timezones and try a quick example:

A time zone is a region that observes a uniform standard time for legal, commercial, and social purposes. Time zones tend to follow the boundaries of countries and their subdivisions because it is convenient for areas in close commercial or other communication to keep the same time.

— Wikipedia

The IntlTimeZone class is responsible for creating and managing timezones. This is no different from timezones in the DateTimeZone class:

$timezone = IntlTimeZone::createDefault();
var_dump($timezone, $timezone->getDisplayName());

$timezone = IntlTimeZone::countEquivalentIDs("GMT");
var_dump($timezone);

$timezone = IntlTimeZone::createTimeZone("GMT");
var_dump($timezone);
object(IntlTimeZone)#1 (4) {
  ["valid"]=>
  bool(true)
  ["id"]=>
  string(3) "UTC"
  ["rawOffset"]=>
  int(0)
  ["currentOffset"]=>
  int(0)
}

string(3) "GMT"

int(10)

object(IntlTimeZone)#1 (4) {
  ["valid"]=>
  bool(true)
  ["id"]=>
  string(3) "GMT"
  ["rawOffset"]=>
  int(0)
  ["currentOffset"]=>
  int(0)
}

Calendars

The PHP Intl extension has a nice, expressive API for doing calendar operations:

$calendar = IntlCalendar::createInstance();
var_dump($calendar->getTimeZone()->getId());
string(3) "UTC"

The createInstance method accepts a timezone (from the previous section), and a locale code. We can also create a calendar instance from a DateTime instance.

$calendar = IntlCalendar::fromDateTime( (new DateTime) );

We can do all sorts of comparisons between calendar dates.

$calendar1 = IntlCalendar::fromDateTime( DateTime::createFromFormat('j-M-Y', '11-Apr-2016') );
$calendar2 = IntlCalendar::createInstance();
$durationFormatter = new NumberFormatter( 'en_US', NumberFormatter::DURATION );

$diff = $calendar1->fieldDifference($calendar2->getTime(), IntlCalendar::FIELD_MILLISECOND);

var_dump(
    $calendar1->equals($calendar2), 
    $diff,
    $durationFormatter->format( $diff )
);
bool(true)
int(595)
string(4) "9:55"

Important note: Our calendar1 variable is advanced by the diff value, so the calendars are equal after the fieldDifference method call. Keep this mutability in mind when using these classes!

The IntlCalendar::FIELD_MILLISECOND constant defines the type of comparison, year, month, week, etc. Check the documentation for the full list.

If you’ve used the briannesbitt/carbon package before, you may have liked the expressive syntax for navigating dates. We can do the same using the IntlCalendar class.

$calendar1 = IntlCalendar::createInstance();
var_dump(IntlDateFormatter::formatObject($calendar1));

$calendar1->add(IntlCalendar::FIELD_MONTH, 1);
var_dump(IntlDateFormatter::formatObject($calendar1));

$calendar1->add(IntlCalendar::FIELD_DAY_OF_WEEK, 1);
var_dump(IntlDateFormatter::formatObject($calendar1));

$calendar1->add(IntlCalendar::FIELD_WEEK_OF_YEAR, 1);
var_dump(IntlDateFormatter::formatObject($calendar1));
string(25) "Apr 12, 2016, 12:06:22 AM"
string(25) "May 12, 2016, 12:06:22 AM"
string(25) "May 15, 2016, 12:06:22 AM"
string(25) "May 22, 2016, 12:06:22 AM"

Check the documentation for more details and examples.

Conclusion

In this two part series, we discovered the PHP Intl extension and the ICU library. The extension still has some other parts like Collators, Spoofchecker, UConverter, etc. We’ll focus on those in subsequent posts, but in the meanwhile, if you have any questions or comments, please leave them below!

Frequently Asked Questions (FAQs) about Localizing Dates, Currency, and Numbers with PHP Intl

What is the PHP Intl extension and why is it important?

The PHP Intl extension is a wrapper for the ICU library (International Components for Unicode), enabling PHP programmers to perform various locale-aware operations including formatting, parsing, and encoding conversion. It’s crucial for creating applications that support multiple languages and locales, as it allows for the localization of dates, numbers, and currencies, ensuring that data is presented in a format that’s familiar and understandable to the user.

How can I install and enable the PHP Intl extension?

The PHP Intl extension is not enabled by default. To install it, you’ll need to recompile PHP with the –enable-intl option, or you can install it as a PECL extension. Once installed, you can enable it by adding extension=intl to your php.ini file and restarting your web server.

How can I use the PHP Intl extension to format dates?

The IntlDateFormatter class in the PHP Intl extension allows you to format dates according to a specific locale. You can create a new IntlDateFormatter object, passing the locale and the desired date and time formats as parameters, and then use the format method to format a date.

How can I use the PHP Intl extension to format numbers?

The NumberFormatter class in the PHP Intl extension allows you to format numbers according to a specific locale. You can create a new NumberFormatter object, passing the locale and the desired number format as parameters, and then use the format method to format a number.

How can I use the PHP Intl extension to format currencies?

The NumberFormatter class in the PHP Intl extension also allows you to format currencies. You can create a new NumberFormatter object, passing the locale and the NumberFormatter::CURRENCY constant as parameters, and then use the formatCurrency method to format a currency value.

What are the different date and time formats that I can use with the IntlDateFormatter class?

The IntlDateFormatter class supports a variety of date and time formats, including short, medium, long, and full. These formats correspond to the ICU date and time format styles, and they control how the date and time are presented.

What are the different number formats that I can use with the NumberFormatter class?

The NumberFormatter class supports several number formats, including decimal, percent, scientific, and spellout. These formats correspond to the ICU number format styles, and they control how the number is presented.

Can I use the PHP Intl extension to parse dates and numbers?

Yes, the PHP Intl extension also provides functionality for parsing dates and numbers. The IntlDateFormatter and NumberFormatter classes both have parse methods that you can use to parse a string into a date or number, respectively.

What should I do if I encounter an error when using the PHP Intl extension?

If you encounter an error when using the PHP Intl extension, you can use the getErrorMessage and getErrorCode methods of the IntlDateFormatter and NumberFormatter classes to get more information about the error. You should also check your PHP error log for any related messages.

Can I use the PHP Intl extension with other PHP extensions?

Yes, the PHP Intl extension can be used in conjunction with other PHP extensions. For example, you might use it with the PDO extension to retrieve data from a database and then format that data for display in a specific locale.