Skip to content

Commit

Permalink
dev/core#2638 Add Helper class for formatting
Browse files Browse the repository at this point in the history
This brings back to life various formatting
attempts such as
#20296

but consolidates the 4 main types of formatting to one helper class"
  • Loading branch information
eileenmcnaughton committed Oct 1, 2021
1 parent 078cc1f commit 2e3fcec
Show file tree
Hide file tree
Showing 7 changed files with 372 additions and 37 deletions.
35 changes: 10 additions & 25 deletions CRM/Core/Smarty/plugins/modifier.crmDate.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,15 @@
* human readable date format | invalid date message
*/
function smarty_modifier_crmDate($dateString, ?string $dateFormat = NULL, bool $onlyTime = FALSE): string {
if ($dateString) {
$configuredFormats = [
'Datetime',
'Full',
'Partial',
'Time',
'Year',
'FinancialBatch',
'shortdate',
];
if (in_array($dateFormat, $configuredFormats, TRUE)) {
$dateFormat = Civi::settings()->get('dateformat' . $dateFormat);
}
// this check needs to be type sensitive
// CRM-3689, CRM-2441
if ($dateFormat === 0) {
$dateFormat = NULL;
}
if ($onlyTime) {
$config = CRM_Core_Config::singleton();
$dateFormat = $config->dateformatTime;
}

return CRM_Utils_Date::customFormat($dateString, $dateFormat);
// this check needs to be type sensitive
// CRM-3689, CRM-2441
if ($dateFormat === 0) {
$dateFormat = NULL;
}
if ($onlyTime) {
$config = CRM_Core_Config::singleton();
$dateFormat = $config->dateformatTime;
}
return '';

return Civi::format()->date($dateString, $dateFormat);
}
14 changes: 2 additions & 12 deletions CRM/Utils/Money.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,7 @@ public static function formatUSLocaleNumericRounded($amount, int $numberOfPlaces
}
return self::formatNumericByFormat($amount, '%!.' . $numberOfPlaces . 'i');
}
$money = Money::of($amount, CRM_Core_Config::singleton()->defaultCurrency, new CustomContext($numberOfPlaces), RoundingMode::HALF_UP);
// @todo - we specify en_US here because we don't want this function to do
// currency replacement at the moment because
// formatLocaleNumericRoundedByPrecision is doing it and if it
// is done there then it is swapped back in there.. This is a short term
// fix to allow us to resolve formatLocaleNumericRoundedByPrecision
// and to make the function comments correct - but, we need to reconsider this
// in master as it is probably better to use locale than our currency separator fields.
$formatter = new \NumberFormatter('en_US', NumberFormatter::DECIMAL);
$formatter->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $numberOfPlaces);
return $money->formatWith($formatter);
return Civi::format()->machineNumber($amount, $numberOfPlaces);
}

/**
Expand Down Expand Up @@ -335,7 +325,7 @@ public static function missingIntlNotice() {
*
* @return int
*/
protected static function getDecimalPlacesForAmount(string $amount): int {
public static function getDecimalPlacesForAmount(string $amount): int {
$decimalPlaces = strlen(substr($amount, strpos($amount, '.') + 1));
return $decimalPlaces;
}
Expand Down
11 changes: 11 additions & 0 deletions Civi.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Civi\Core\Format;

/**
* Class Civi
*
Expand Down Expand Up @@ -103,6 +105,15 @@ public static function paths() {
return \Civi\Core\Container::getBootService('paths');
}

/**
* Obtain the formatting object.
*
* @return \Civi\Core\Format
*/
public static function format(): Format {
return new Civi\Core\Format();
}

/**
* Fetch a service from the container.
*
Expand Down
5 changes: 5 additions & 0 deletions Civi/Core/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ public function createContainer() {
[]
))->setPublic(TRUE);

$container->setDefinition('format', new Definition(
'\Civi\Core\Format',
[]
))->setPublic(TRUE);

$container->setDefinition('bundle.bootstrap3', new Definition('CRM_Core_Resources_Bundle', ['bootstrap3']))
->setFactory('CRM_Core_Resources_Common::createBootstrap3Bundle')->setPublic(TRUE);

Expand Down
212 changes: 212 additions & 0 deletions Civi/Core/Format.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<?php

namespace Civi\Core;

use Brick\Money\Money;
use Brick\Money\Context\CustomContext;
use Brick\Math\RoundingMode;
use Civi;
use CRM_Core_Config;
use CRM_Core_I18n;
use CRM_Utils_Constant;
use CRM_Utils_Date;
use CRM_Utils_Money;
use DateTime;
use DateTimeZone;
use NumberFormatter;

/**
* Class Paths
* @package Civi\Core
*
* This class provides standardised formatting
*/
class Format {

/**
* Get formatted money
*
* @param string $amount
* @param string|null $currency
* Currency, defaults to site currency if not provided.
* @param string|null $locale
* @param int|null $precision
*
* @return string
*
* @noinspection PhpDocMissingThrowsInspection
* @noinspection PhpUnhandledExceptionInspection
*/
public function money(string $amount, ?string $currency = NULL, ?string $locale = NULL, int $precision = NULL): string {
if (!$currency) {
$currency = Civi::settings()->get('defaultCurrency');
}
if (!isset($locale) || $locale === CRM_Core_I18n::getLocale()) {
// Legacy mode can't copy with locale or precision so we can only go 'the new way'
// if the new params are passed in.
if (!isset($precision) && $this->isUseSeparatorSettings()) {
// @todo - this legacy method is losing it's charm! However, passing to it, for now,
// means only the new functionality goes through the new, better but less tested, method.
return CRM_Utils_Money::format($amount, $currency);
}
$locale = CRM_Core_I18n::getLocale();
}

if ($precision === NULL) {
$precision = CRM_Utils_Money::getCurrencyPrecision($currency);
}
$money = Money::of($amount, $currency, new CustomContext($precision), RoundingMode::HALF_UP);
return $money->formatTo($locale);
}

/**
* Get a formatted number.
*
* @param string|int|float|Money $amount
* Amount in a machine money format.
* @param string|null $locale
* @param array $attributes
* Additional values supported by NumberFormatter
* https://www.php.net/manual/en/class.numberformatter.php
* By default this will set it to round to 8 places and not
* add any padding.
*
* @return string
*/
public function number($amount, ?string $locale = NULL, array $attributes = [
NumberFormatter::MIN_FRACTION_DIGITS => 0,
NumberFormatter::MAX_FRACTION_DIGITS => 8,
]): string {
if ($locale && $locale !== CRM_Core_I18n::getLocale()) {
$formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL);
}
else {
$locale = CRM_Core_I18n::getLocale();
$formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL);
if ($this->isUseSeparatorSettings()) {
$formatter->setAttribute('DECIMAL_SEPARATOR_SYMBOL', Civi::settings()->get('decimalSeparator'));
$formatter->setAttribute('GROUPING_SEPARATOR_SYMBOL', Civi::settings()->get('thousandSeparator'));
}
}

foreach ($attributes as $attribute => $value) {
$formatter->setAttribute($attribute, $value);
}
return $formatter->format($amount);
}

public function moneyNumber($amount, $currency) {
$formatter = new \NumberFormatter('en_US', \NumberFormatter::CURRENCY);
$formatter->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, 'US$');
$formatter->setSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, '·');
$formatter->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, 2);

$money = Money::of($amount, $currency);
echo $money->formatWith($formatter); // US$5·000.00
}

/**
* Should we use the configured thousand & decimal separators.
*
* The goal is to phase this into being FALSE - but for now
* we are looking at how to manage an 'opt in'
*/
protected function isUseSeparatorSettings(): bool {
return !CRM_Utils_Constant::value('IGNORE_SEPARATOR_CONFIG');
}

/**
* Get a formatted date.
*
* @param ?string|\DateTime $date
* @param string|null $dateFormat
* @param string|null $timezone
*
* @return string
*
* @noinspection PhpDocMissingThrowsInspection
* @noinspection PhpUnhandledExceptionInspection
*/
public function date($date = 'now', ?string $dateFormat = NULL, ?string $timezone = NULL): string {
if ($timezone && $timezone !== CRM_Core_Config::singleton()->userSystem->getTimeZoneString()) {
if (!is_a($date, 'DateTime')) {
$date = new DateTime($date);
}
$date->setTimezone(new DateTimeZone($timezone));
}
if (is_a($date, 'DateTime')) {
$date = $date->format('Y-m-d H:i:s');
}

$configuredFormats = [
'Datetime',
'Full',
'Partial',
'Time',
'Year',
'FinancialBatch',
'shortdate',
];
if (in_array($dateFormat, $configuredFormats, TRUE)) {
$dateFormat = Civi::settings()->get('dateformat' . $dateFormat);
}
return CRM_Utils_Date::customFormat($date, $dateFormat);
}

/**
* Get the date in a machine readable format.
*
* This will be 2012-08-09 13:56:23
*
* @param string $date
*
* @return string
*/
public function machineDate(string $date = 'now'): string {
return date(strtotime($date), 'Y-m-d H:i:s');
}

/**
* Get Machine version of a number.
*
* This format is ready to use in code / the database.
*
* It looks like 12000.456 - note the lack of thousand separators
* and the decimal point.
*
* The rounding defaults to 9 - like our database.
*
*
* @param string $amount
* @param int|null $precision
*
* @return string
*/
public function machineNumber(string $amount, int $precision = 9): string {
$formatter = new NumberFormatter('en_US', NumberFormatter::DECIMAL);
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $precision);
return $formatter->format($amount);
}

/**
* Get Machine version of a number with rounding according to the currency.
*
* This format is ready to use in code / the database.
*
* It looks like 12000.45 - note the lack of thousand separators
* and the decimal point and rounding to 2 decimal places per the currency.
*
*
* @param string $amount
* @param ?string $currency
*
* @return string
*/
public function machineMoney(string $amount, ?string $currency = NULL): string {
if (!$currency) {
$currency = Civi::settings()->get('defaultCurrency');
}
return $this->machineNumber($amount, CRM_Utils_Money::getCurrencyPrecision($currency));
}

}
7 changes: 7 additions & 0 deletions tests/phpunit/CRM/Utils/MoneyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,11 @@ public function testInvalidCurrency() {
CRM_Utils_Money::format(4.00, 'NOT_A_CURRENCY');
}

/**
* Test the format object.
*/
public function testFormatter(): void {
$money = \Civi::format()->money('');
}

}
Loading

0 comments on commit 2e3fcec

Please sign in to comment.