Number formatting in Reports (Ignition 8.1.2)

Hi everyone,

I'm facing an issue with the "Reports" module in Ignition Perspective 8.1.2. I need to create a report that uses the German number format for billing purposes --> "2.500,00 €" (German standard) instead of "2,500.00 €" (US standard).

To set the number format for a particular TextShape to German, I've been using the Properties tab and selecting Number Format as "#,##0.00;(#,##0.00)" (as highlighted in blue in the attached image).

However, when the report is created, it reverts back to the US format. This is because our Gateway is set to German but the server is set to English, and the report is being sent to the server, causing it to switch back to the US format. Unfortunately, the server language must remain in English.

This is the correct Format in Preview (Designer):
image

This is the Format when executed on the Gateway:
image

Does anyone have any ideas on how to bypass this issue? I'm considering using the "0" format and treating the input as a string, then converting it to the German format using code like in Bindings. However, I'm not sure how to add a binding to a TextShape.

If anyone has experienced a similar issue or has a more convenient way to ensure the format doesn't change when the report is created, I would greatly appreciate your insight.

Thanks in advance!

Unfortunately, the number formatting in reports is locked to the locale of the gateway overall, and there's (currently) no way to override it.

I think the "best" overall approach for a workaround would be to use a scripting datasource to rewrite any numeric columns you want to localize and perform the number -> string conversion using the appropriate locale.

Sorry, but could you please provide a more detailed explanation, or provide a online resource, of your suggested approach using the script data source? I am having difficulty understanding and implementing it...

Here's an entirely self contained example, though I'd recommend making some changes for "production" use (detailed below):
report_localization.zip (12.9 KB)

In this case I'm just using the "static CSV" input type for simplicity. Everything that matters happens in the script data source:
First, I'm defining this function. This is slightly modified from my example posted here; all it does is update an entire column of a dataset in one operation, calling the provided function parameter on each value in the column. The modification was giving it the ability to override the column type, since you're going from numbers to strings. In production, you should move this into the project library so you can call it from anywhere in your project.

def mapColumn(function, dataset, column, columnType=None):
	if isinstance(column, basestring):
		column = dataset.getColumnIndex(column)

	columnData = map(function, dataset.getColumnAsList(column))
	columnsToKeep = range(dataset.columnCount)
	columnsToKeep.pop(column)
	dsWithoutColumn = system.dataset.filterColumns(dataset, columnsToKeep)
	
	return system.dataset.addColumn(
		dsWithoutColumn, 
		column, 
		columnData, 
		dataset.getColumnName(column), 
		columnType or dataset.getColumnType(column)
	)

Then I'm defining a function to actually do the localization of a number into currency format in the provided locale. I'm leaning on Java's built in number formatting for this. Again, this should be defined in the project library, but for simplicity I put it all in one place. The locale you pass in should be a language tag string, like "de_DE" for German (Germany) or "en_US" for English (United States), etc.

from java.text import NumberFormat
from com.inductiveautomation.ignition.common.i18n import LocaleUtils

def localizeNumber(number, locale):
	locale = LocaleUtils.parseLocale(locale).orElseThrow()
	nf = NumberFormat.getCurrencyInstance(locale)
	return nf.format(number)

Finally, the piece that actually does anything with the report.
The function definition is just used to provide a default value to the general purpose localizeNumber function. You could also use functools.partial or a lambda expression for this. The key is to take a function that accepts two arguments and return a function that accepts one argument, because that's what mapColumn is expecting.

Then, that new function is applied to the two numeric columns in the static_data dataset, turning them into strings. Then that new dataset is output as a new data key. The overall concept of what we're doing is covered here.

def localizeToGerman(number):
	return localizeNumber(number, "de_DE")

ds = data["static_data"]
ds = mapColumn(localizeToGerman, ds, "Column2", columnType = str)
ds = mapColumn(localizeToGerman, ds, "Column3", columnType = str)

data["localized"] = ds

The end result is something like this, where the top table is the original data, and the bottom table is the localized presentation:
image

2 Likes

Paul, you're the man! Thank you tons for providing such a detailed description!
I'll give it a go and keep you posted!

Again, thanks a lot!

By mere chance and playing around a bit I found a solution for my problem...

By simply adding/adjusting a Binding to the reports paramerters, with the following code, did the trick:

def transform(self, value, quality, timestamp):
   	'''
   	The code replaces the comma and the period in multiple steps because replacing both 
   	simultaneously could lead to confusion of the characters.
   	'''
	# The value is converted to a floating-point number to ensure that mathematical operations can be performed.
	number = float(value)
    
	# Format the number with comma as thousand separator and dot as decimal separator
	formatted_number = '{:,.2f}'.format(number)
	
	# The comma is replaced by a temporary character. This is done to "store" the 
	# comma so that it won't be confused with the period later.
	formatted_number = formatted_number.replace(',', 'TEMP')
	
	# Replace the dot with a comma
	formatted_number = formatted_number.replace('.', ',')
	
	# Replace the temporary marker with a dot
	formatted_number = formatted_number.replace('TEMP', '.')
	
	# Return the formatted number
	return formatted_number

This is how it looks like after Report has been created.
Instead of 51,105.00 € (US Format) it shows 51.105,00 € (GER Format):

Hi, Olli. I took out the "Perspective" in your title and the tag. The Reporting module is completely separate from the Perspective module so they were not relevant.

My mistake, thanks for correcting!

1 Like

If you're in Perspective and dealing with a single number to string operation at a time, you can lean on Java's tools to do it simpler and faster:

from java.text import NumberFormat
from java.util import Locale

nf = NumberFormat.getCurrencyInstance(Locale.GERMANY)

print nf.format(123.45)
print nf.format(51106.01)
>>> 
123,45 €
51.106,01 €

You can wrap that up in a project library script to save the cost of importing and I can guarantee it'll be much faster than the repetitive string replacement operations (not that it's really going to matter in this case) - but I would argue it's much, much nicer to read.

2 Likes

True - your approach is much more professional, and most importantly it works perfectly! :slight_smile: