I ran into a problem today while outputting various currency amounts.

The Japanese Yen does not use a decimal point. The yen is the lowest value possible in Japanese currency. Converted amounts must be rounded up or down to the nearest whole number.

As a result the decimal count for each currency type must be predefined. Most use 2, but a handful are 0 (or maybe even something different; I didn't research all the other types.)

My idea is to add the decimal count to one of the currency arrays in currency_api.module.

Thoughts?

Comments

kbahey’s picture

Why don't we just have a centralized currency formatting function, that is also themable?

This way, you can override the value in the theme any way you like, and do away with the decimals if you want, or add the symbol if you want, ...etc.?

johnhanley’s picture

A theme function is a good idea, but it seems like we'd still need a predefined way to determine the decimal count for each currency.

Besides the decimal count issue, it would also be nice to have a way to customize the currency output. For example when displaying negative amounts, I would like to use the format -$200.00 (the way PayPal does it) instead of $-200.00. The PHP function money_format might be useful for this purpose, but I haven't played with it enough to know for sure. Perhaps sprintf is more suitable.

kbahey’s picture

That is what I am saying, a unified way to output the currency is a theme_currency($symbol, $amount) function that can be overridden.

Inside that we can use money_format() by default with no side effects since it is in PHP 4 and 5.

Any patches?

johnhanley’s picture

I haven't thought the idea through enough to create a patch. Can you think of any existing theme functions that could serve as an example?

kbahey’s picture

Example from user.module:

function theme_user_picture($account) {
  if (variable_get('user_pictures', 0)) {
    if ($account->picture && file_exists($account->picture)) {
      $picture = file_create_url($account->picture);
    }
    else if (variable_get('user_picture_default', '')) {
      $picture = variable_get('user_picture_default', '');
    }

    if (isset($picture)) {
      $alt = t("@user's picture", array('@user' => $account->name ? $account->name : variable_get('anonymous', t('Anonymous'))));
      $picture = theme('image', $picture, $alt, $alt, '', FALSE);
      if (!empty($account->uid) && user_access('access user profiles')) {
        $picture = l($picture, "user/$account->uid", array('title' => t('View user profile.')), NULL, NULL, FALSE, TRUE);
      }

      return "<div class=\"picture\">$picture</div>";
    }
  }
}

You call it like this:

$output .= theme('user_picture', $user);

So, you have to define the following function in the module.

function theme_currency_amount($symbol, $amount) {
....
}

Then call it via:

$output .= theme('currency_amount', '$', 100.26);

Then to override it, you create a function called phptemplate_currency_amount() in the template.php of your theme, and it will override what is in the module.

Read more about theming as it is explained in far more detail.

johnhanley’s picture

Right, right. I've created many theme functions and have overridden preexisting ones in template.php, but I hadn't given the idea adequate thought yet.

Your post is a good start and now has me thinking. It appears we have 4 parameters:

amount
symbol
decimal
code

The only required parameter is amount. The others could default to

<?php
function theme_currency_amount($amount, $symbol = '$', $decimal = 2, $code = 'USD') {
....
}
?>

The default output would be: $100.00 USD

Implementing money_format would require setting the category and local parameters with setlocale, which might end up being more effort than is needed.

Another option would be to have some additional admin settings. For example the way Windows Vista allows the user to control the currency display.

While all of this is very interesting it still doesn't address my original question, which is how to set the decimal count for individual currencies. Perhaps something like this would suffice:

  $currency = array(
    'USD' => array('symbol' => '$', decimal => 2),
  );

For that matter why not combine all the elements:

  $currency = array(
    'USD' => array('name' => t('U.S. Dollar (USD)'), 'symbol' => '$', 'decimal' => 2),
  );
kbahey’s picture

You are on the right track.

Think about it for some time until it firms, and then make a patch.

Also, it would be nice to use format_number() inside the the theme function.

As for the number of arguments, we should consider making it a keyed array, instead of having 4 arguments. And we can check if the argument is not an array, then it is only the amount and we act accordingly.

array(
'amount' => $amount,
'symbol' => '$',
...
);

As for the original question, the whole idea is not to clutter the module with special cases.

Now, on your site you do this in template.php:

phptemplate_currency_amount($amount, $currency, $symbol, ...) {
  switch($currency) {
    case 'JPY':
      return number_format($amount);
    default:
      return number_format($amount, 2);
  }
}

Countries using comma or period for thousands separators can use whatever they like.

johnhanley’s picture

I hear what you're saying about clutter, but each currency's characteristics is absolute and not subject to change so it makes sense to include a decimal count definition in the base module. Grant it most decimal count values will equal '2', but I believe this is more efficient than hard-coding conditions for individual currencies. You can then simply use the decimal count variable with number_format() like so

number_format($amount, $decimal);
kbahey’s picture

I am taking the "less code" approach.

Most currencies just work right now, and only a minority need this feature.

So, having those few sites that use those minority currency override things in template.php makes the module leaner for everyone else (the majority).

I am against creeping featuritis and bloat and try to avoid adding code for less used features.

Less code is better code ...

johnhanley’s picture

I don't think adding an extra element to an existing array is "extra code". Storing the decimal count property with each currency is the way most financial and e-commerce applications do it.

I suppose it depends on one's perspective. In this case less code at the module level means more code at the template level. In its purist form the template level should contain the least amount of business logic as possible.

kbahey’s picture

Adding the extra field makes the array bigger and consume more memory. It also makes it added work to maintain in the future (more elements, prone to editing).

Perhaps if we move the currency info array to a database table that is created on the module install, we can add that extra field, without being too concerned with memory usage. We can provide a default theme function that will cater for all currencies, yet will still be overridable for those want to do so.

johnhanley’s picture

I actually share your concern about bloat. In fact currency_api.module is only one of two (literally) third-party modules currently enabled for my custom application. The rest of the functionality is produced with a small suite of custom modules. No CCK, Views, OG, etc. As a result the application screams. If the concept proves successful (i.e. tons of traffic) I will need to preserve every possible cycle.

Somehow many Drupal developers have come to accept and even embrace 100's of database hits per page load. For this reason I like the idea of keeping the currency information in memory. This makes the most sense to me because the information is unlikely to change very often. Besides your module is extremely light so memory usage is negligible.

That said, it's your call.

kbahey’s picture

OK, roll a patch with the added field in the array and a theme function to display the stuff.

Then measure using devel's performance module and see the difference before and after.

If it is negligible, I will accept it.

We need the patch to go in for 6.x later.

johnhanley’s picture

StatusFileSize
new18.72 KB

Here's the patch as discussed.

Included in this patch:

function currency_api_get_currencies(), returns an array with combined attributes currency symbol, name, code, and decimal count.
function theme_currency_api_amount(), returns a formatted string using attributes of a currency, example: $200.00 USD

Note: currency_api_get_currencies() contains the latest Yahoo currency list as of 11/01/2008. The old functions currency_api_get_symbols() and currency_api_get_list() are now depreciated, but have been preserved.

The following function illustrates how to cache the currencies array and return the attributes for a currency...

/**
 * Custom function to return attributes for currency.
 *
 * @param string 3-letter currency type
 * @return array currency attributes
 */
function _transaction_get_currency($currency) {
  static $currencies = array();

  if (!isset($currencies[$currency])) {
    // cache currencies in static array variable for subsequent lookups
    $currencies = currency_api_get_currencies();
  }

  // return attributes for currency exchange rate
  return $currencies[$currency];
}

Example usage:

$amount = 153;
$currency = 'USD';

print theme('currency_api_amount', $amount, _transaction_get_currency($currency));
kbahey’s picture

Version: 5.x-1.3 » 5.x-1.x-dev
Status: Active » Needs work

The patch does not apply.

Can you please make sure that patches are created against CVS, with the tag DRUPAL-5, or the 5.x-1.x-dev tarball?

johnhanley’s picture

StatusFileSize
new18.1 KB

Against 5.x-1.x-dev tarball.

kbahey’s picture

Still needs work.

We have two functions that return currency information:

The old one is currency_api_get_list() which returns the code and name, and new one is currency_api_get_currencies() with code, description and symbols.

There is also currency_api_get_symbols() which can be folded into that too.

Having data in many places is a bad thing. Better unify them all in one place, so maintenance means just one place to change.

Also, we need to do this to 6.x-1.x-dev when 5.x is done.

johnhanley’s picture

Thanks for the coding practice tip, but did you read my previous comment?

currency_api_get_currencies() COMBINES the attributes of both currency_api_get_symbols() and currency_api_get_list() PLUS adds the currency decimal count.

The old functions are intended to be deleted, but this needs to be communicated to developers so as not to break their applications.

kbahey’s picture

I am inclined just to break the API, and make this 5.x-2.x-dev and document that the old functions do not exist. This is what Drupal core does to stay lean: break the API, and document the changes so people can migrate.

We can document the API in the README.txt file as a new section that lists the functions to be used.

Alternately, Can't we just change them into wrapper functions? Internally they call the new function and return only the needed info from the big array.

I prefer we cleanup the API and create 5.x-2.x-dev.

What do others think? Hopefully we get some comments.

johnhanley’s picture

You could create a wrapper function to pull in the various attributes and combine them into one array, but if the goal is to streamline the API and ease maintenance then the new single function defining all the attributes in "one place" is the way to go. This, along with the theme function, give me the desired flexibility.

Yes, input from other using the API module would be helpful.

johnhanley’s picture

StatusFileSize
new29.2 KB

Here's the latest (last?) patch related to the above changes.

functions removed:

currency_api_get_list()
currency_api_get_symbols()

functions added:

currency_api_get_currencies()
theme_currency_api_amount()

function modified:

currency_api_get_desc()

Now everything is defined in one place.

Use the previously described wrapper function in your custom module to return all attributes (code, name, symbol, decimal) for a given exchange rate code. The wrapper function also has the added benefit of static variable usage, which eliminates the need to redefine the array for subsequent calls.

Interest parties are welcome to further refine/enhance these modifications.

Cheers,
John

kbahey’s picture

I noticed the patch is against an older version

-//$Id: currency_api.module,v 1.4.2.18 2008/10/30 16:45:57 kbahey Exp $
+//$Id: currency_api.module,v 1.4.2.17 2008/10/20 14:33:16 kbahey Exp $

Can you please reroll against the last version?

Also, can you include in it updates to API.txt to reflect the latest API?

markus_petrux’s picture

Sweet addition. I'll be using the decimal count for the Money CCK field :)

#217842: Format currencies according to their own custom, add hints for this to the Currency API

markus_petrux’s picture

Version: 5.x-1.x-dev » 6.x-1.x-dev
Status: Needs work » Needs review
StatusFileSize
new27.45 KB

Would you mind checking the attached patch?

It implements currency_api_get_currencies() as in previous patches, but then it keeps the current APIs reusing the array that is provided by the new API, so the data is just stored in one single place and it offers backwards compatibility with existing theird party modules that depend on the currency api.

For the theme function... I would suggest taking the Format Number API into account here as well, since it provides a method to define thousand separator and decimal point symbols. Anyway, this thing could be provided on a separate patch if needs be, maybe?

The attached patch would be enough for the Money CCK field to reuse the decimal count settings per currency.

BTW, the patch here is against the -dev package for D6.

johnhanley’s picture

Good call on keeping the existing API functions as wrappers for backwards compatibility.

I wouldn't recommend creating a dependency on Format Number API, but it's a nice option for those who want to override the default theme.

markus_petrux’s picture

hmm... maybe it is not really the best place here to provide a theme function for currency amount?

For the Money CCK field I would happily use the new API with information about decimal places per currency, as an option, but not the theme function as it doesn't really fit the needs of the field formatter, and I prefer using the Format Number API for the amount.

johnhanley’s picture

Well, you can always choose not to use the default theme or it could be struck from the patch altogether. Since users needs greatly vary when it comes to formatting currency it's nearly impossible to have a one-size-fits-all theme. currency_api_get_currencies() contains all the attributes needed.

markus_petrux’s picture

Would it be possible to review the patch in #24 and discuss the theme function after that one gets in?

kbahey’s picture

@markus_petrux

I am ok with the patch in #24.

However, do we really need a separate module to implement a theme function? I think it is kind of overkill with more dependencies, more things to maintain/upgrade, ...etc.

I can commit #24. But would like to know your (and others') views on a 3rd module dependency. Rather add a theme function in currency_api, even if the other module can enrich it by overriding the function. So we have a basic function that is suitable for most people in currency_api, and those who want to be more fancy can install the other module.

Thoughts?

markus_petrux’s picture

StatusFileSize
new29.61 KB

Ok, thanks for taking a look at this. :)

Here's a new patch that includes #24 + the theme function in #21 + doxygen for functions where it was missing.

johnhanley’s picture

I second the idea of including theme_currency_api_amount() in currency_api. It provides basic output and can easily be overridden. It also serves as an example, but obviously won't satisfy everybody's need (just like any other theme.) For instance someone might want to format with sprintf.

kbahey’s picture

Status: Needs review » Fixed

Committed.

Thank you!

markus_petrux’s picture

Thanks! :-D

Any chance for a new release?

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

markus_petrux’s picture

Status: Closed (fixed) » Active

@kbahey: Could you please create a new release so that the changes introduced here are included on a official release?

Latest Money CCK field needs this, and an official release here would save a few headaches to users. Please?

Thank you :)

kbahey’s picture

markus_petrux, I granted you CVS commit access to this module, since you are a long time contributer to Drupal.

You can go ahead and create a stable release yourself. Be sure to run the cvs release notes script.

Close this issue when you are done.

markus_petrux’s picture

Khalid, thanks for the confidence. Though, I had some trouble creating the release: #455380: Request to fix or delete a failed project release. I hope it can be fixed asap.

markus_petrux’s picture

Status: Active » Closed (fixed)

Release node has been fixed! :-D