Formatted Numeric Data

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Revision as of 23:16, 18 May 2006 by Rackle (Talk | contribs)

Jump to: navigation, search

International Components for Unicode (ICU) is IBM's toolkit to internationalise applications. Initially developped for Java it has been ported to C/C++. Now it has been partially ported to CEGUI. Please use this thread for discussion.

IBM's ICU

Useful Links

Getting started

Download these files from IBM

  • Source: icu-3.4.1.zip OR icu-3.4.1.tgz
  • Documentation: icu-3.4-docs.zip
  • User Guide: icu-3.4-userguide.zip

Compile these solutions:

  • icu/source/allinone/allinone.sln
  • icu/source/samples/all/all.sln

Fix the following errors:

  • For projects strsrch and coll
    • Move:
        if (pOpt->name == 0)
        {
            fprintf(stderr, "Unrecognized option \"%s\"\n", pArgName);
            return FALSE;
        }
    • Upward just after the line:
        for (OptSpec *pOpt = opts;  pOpt->name != 0; pOpt ++) {
  • For project legacy
    • remove dependencies to:
      • ../../../../icu-1-8-1/lib/icuucd.lib
      • ../../../../icu-1-8-1/lib/icuind.lib
  • For project GDIFontInstance
    • cast 6th parameter: (LPCWSTR) &ttGlyphs[dyStart]

Please note that samples will NOT run in their compilation directories. In order to run any of the samples you must place the executable within the icu/bin directory, where the required DLLs are situated.

Incorporating ICU within an existing project

Additional include directories:

  • $(ICU)\include

Additional library directories:

  • $(ICU)\lib

Link with:

  • debug: icuucd.lib icuind.lib
  • release: icuuc.lib icuin.lib

Make certain these DLLs are present with your executable:

  • debug: icuuc34d.dll icuin34d.dll icudt34.dll
  • release: icuuc34.dll icuin34.dll icudt34.dll

CEGUI's ICU

IBM's ICU provides many features to support internationalisation. The class I've created encapsultes some (not all) of these features.

Locale

A Locale describes the rules in effect within a country for a specific language. These rules specify the formatting of currencies, numeric values, dates, and others. Unfortunately these rules do not take into consideration the modifications that may have been applied within the Regional Settings of Window's Control Panel. However most functions accept a formatting mask. This allows applications to recreate some of the features of the control panel, allowing users to specify their desired formats. The setLocale() and setCurrency() functions will configure the class to adopt the rules specified by the specified language, country, and currency.

formatNumber

The formatNumber() function formats a numeric value given the specified mask. Three versions are available. The versions accepting a CEGUI::String has been customised to allow large numbers to be formatted: numbers having up to 18 integers and 7 decimals. The current implementation of this function is limited to accepting a single format for positive numbers. If the numeric value to be formatted is negative then a negative sign will precede the formatted value. However it is impossible to format a negative value within parentheses: formatting -1234.56 to (1,234.56).

numberToText

The numberToText() function converts a numeric value into a textual representation. For example the numeric value 123 is converted into "one hundred twenty-three" in english and "cent vingt-trois" in french.

numberToOrdinal

The numberToOrdinal() function converts a numeric value into an ordinal representation. Form example the numeric value 1 is converted into "1st" in english. Support for other languages is either buggy or lacking (or I have improperly coded this feature).

formatText

The formatText() function will parse a numeric value and sprinkle digits into the slots specified by the formatting mask. A North American telephone number of "12223334444" can be formatted into "1 (222) 333-4444" with the mask "0 (000) 000-0000". This non-localised function accepts three format specifier. A "0" represents a forced digit. If the numerical value possesses a digit at that position then the digit will be displayed, otherwise the place holder character(s) will be used. A "#" represents a potential digit. If the numerical value possesses a digit at that position the the digit will be displayed, otherwise nothing is displayed. Finally the apostrophe "'" allows the mask to specify the characters "0", "#", or "'", rather than using them as format specifiers.

formatCurrency

The formatCurrency() function will format a numerical value according to the currently configured locale and currency. It will NOT convert the monetary value of one currency to another.

formatDateTime

The formatDateTime() function will format a date, time, or date/time given the specified mask. The UDate variable type is a double. According to IBM's documentation "A UDate value is stored as UTC time in milliseconds, which means it is calendar and time zone independent. UDate is the most compact and portable way to store and transmit a date and time." However I have found that attempting to specify a date AND time within the UDate data type results in imprecisions of up to 1 minute 47 seconds. My solution is to use two UDate data variables, one to hold a date and another to hold a time.

localToGmt and gmtToLocal

The localToGmt() and the gmtToLocal() functions convert a date and a time between a local and a GMT value.

CeguiStringToDateTime

The CeguiStringToDateTime() function will parse a string specifying a date or a time into it's corresponding UDate value. Although it is possible to parse a string containing both a date and a time the resulting UDate value will be inaccurate, varying from its intended value by up to 1 minute 47 seconds. A better approach is to keep the date and time separated.

Source Code

icu.h

#ifndef _ICU_h_
#define _ICU_h_

/* Useful links
	ISO Language Codes: http://www.loc.gov/standards/iso639-2/
	ISO Country Codes:  http://www.iso.ch/iso/en/prods-services/iso3166ma/index.html
	ISO Currency Codes: http://en.wikipedia.org/wiki/ISO_4217
	ICU Decimal Format Syntax: http://icu.sourceforge.net/apiref/icu4c/classDecimalFormat.html#_details
	ICU Date/Time Format Syntax: http://icu.sourceforge.net/userguide/formatDateTime.html
*/

#include "unicode/utypes.h"
#include "unicode/unistr.h"
#include "unicode/numfmt.h"
#include "unicode/dcfmtsym.h"
#include "unicode/decimfmt.h"
#include "unicode/locid.h"
#include "unicode/uclean.h"
#include "unicode/calendar.h"
#include "unicode/datefmt.h"
#include "unicode/smpdtfmt.h"
#include "unicode/rbnf.h"
#include "CEGUI.h"

class ICU
{
public:
	bool setLocale(const CEGUI::String& language, const CEGUI::String& country);
	const Locale& getLocale();
	bool setCurrency(const char *currency);

	bool formatNumber(const CEGUI::String& rawValue, const CEGUI::String& mask, CEGUI::String& formattedValue);
	bool formatNumber(const double rawValue, const CEGUI::String& mask, CEGUI::String& formattedValue);
	bool formatNumber(const int32_t rawValue, const CEGUI::String& mask, CEGUI::String& formattedValue);

	bool numberToText(const double rawValue, CEGUI::String& formattedValue);
	bool numberToText(const int32_t rawValue, CEGUI::String& formattedValue);

	bool numberToOrdinal(const double rawValue, CEGUI::String& formattedValue);
	bool numberToOrdinal(const int32_t rawValue, CEGUI::String& formattedValue);

	bool formatText(const CEGUI::String& rawValue, const CEGUI::String& mask, const CEGUI::String& zeroPlaceHolder, CEGUI::String& formattedValue);

	bool formatCurrency(const double& currency, CEGUI::String& formattedValue);

	bool formatDateTime(UDate rawDateTime, const CEGUI::String& mask, CEGUI::String& formattedDateTime);
	bool localToGmt(const UDate& localDate, const UDate& localTime, UDate& gmtDate, UDate& gmtTime);
	bool gmtToLocal(const UDate& gmtDate, const UDate& gmtTime, UDate& localDate, UDate& localTime);

	bool CeguiStringToDateTime(const CEGUI::String& stringDateTime, const CEGUI::String& mask, UDate& unicodeDateTime);
	bool CeguiStringToInt32(const CEGUI::String& stringValue, int32_t& int32Value);
	void UnicodeToCeguiString(const UnicodeString& unicodeString, CEGUI::String& ceguiString);
private:
	void _splitIntegerDecimal(const CEGUI::String& combined, CEGUI::String& integerPart, CEGUI::String& decimalPart);
	bool _convertLocalAndGmt(const UDate& localDate, const UDate& localTime, UDate& gmtDate, UDate& gmtTime, bool fromLocalToGMT);
	bool _ruleBasedNumberFormat(const double rawValue, URBNFRuleSetTag tag, CEGUI::String& formattedValue);
	bool _ruleBasedNumberFormat(const int32_t rawValue, URBNFRuleSetTag tag, CEGUI::String& formattedValue);
	Locale m_locale; // Current locale of the computer
	UChar m_currency[4]; // Currency code
};


#endif // _ICU_h_

icu.cpp

#include "ICU.h"
#include <vector>

bool ICU::setLocale(const CEGUI::String& language, const CEGUI::String& country)
{
	// Set the locale
	Locale tempLocale = Locale::createFromName((language + "_" + country).c_str());
	if(tempLocale.isBogus())
		return false;

	m_locale = tempLocale;
	return true;
}

const Locale& ICU::getLocale()
{
	// Retrive the locale
	return m_locale;
}

bool ICU::setCurrency(const char *currency)
{
	// Set the currency
    if(currency==NULL || strlen(currency)!=3)
        return false;

    // Invariant-character conversion to UChars
    u_charsToUChars(currency, m_currency, 4);
	return true;
}

bool ICU::formatNumber(const CEGUI::String& rawValue, const CEGUI::String& mask, CEGUI::String& formattedValue)
{
	// Format a unformatted number according to the rules specified within the mask
	// An unformatted number only contains numeric digits and a period as the decimal point
	// Note that a decimal value is broken down into two numbers, an
	//   integer and a decimal number for formatting and then reassembled
	//   into a single string.  This makes it possible to handle numbers
	//   as large as 999,999,999,999,999,999.9999999 (18 integers and 7 decimals).
	//   However it makes it impossible to specify a positive mask as well as a
	//   negative mask; only one mask is supported.  It is also impossible to
	//   format a negative value with a mask similar to "(#,##0.00)"
    UErrorCode status = U_ZERO_ERROR;
	formattedValue.clear();
	CEGUI::String formattedInteger, formattedDecimal;
	CEGUI::String::size_type idx;

	// Separate the integer and the decimal parts from the value
	CEGUI::String maskInteger, maskDecimal, integerValue, decimalValue;
	idx = rawValue.find(".");
	if(idx == CEGUI::String::npos)
	{
		// If the value is empty then force the integer to 0, otherwise use the value
		// Since there is no decimal point force a decimal value of .0
		integerValue = rawValue.empty() ? "0" : rawValue;
		decimalValue = ".0";
	}
	else
	{
		// If there is no integer portion then force an integer value of 0
		integerValue = idx == 0 ? "0" : rawValue.substr(0, idx);
		decimalValue = rawValue.substr(idx);
	}

	// Separate the integer and the decimal parts from the mask
	_splitIntegerDecimal(mask, maskInteger, maskDecimal);

	// Prepare the numeric formatter
	DecimalFormatSymbols* decimalFormatSymbols = new DecimalFormatSymbols(m_locale, status);
	if( U_FAILURE(status) )
		return false;

	if(!maskInteger.empty())
	{
		// Prepare the integer parser
		UnicodeString patternInteger(maskInteger.c_str());
		DecimalFormat* fmt = new DecimalFormat(patternInteger, *decimalFormatSymbols, status);
		if( U_FAILURE(status) )
		{
			delete decimalFormatSymbols;
			return false;
		}

		// Parse the string value into an integer value
		UnicodeString uIntegerValue(integerValue.c_str());
		Formattable fIntegerValue;
		fmt->parse(uIntegerValue, fIntegerValue, status);
		if( U_FAILURE(status) )
		{
			status = U_ZERO_ERROR;
			fIntegerValue.setInt64(0);
		}

		// Format the integer value
		uIntegerValue = "";
		((NumberFormat*)fmt)->format(fIntegerValue.getInt64(), uIntegerValue);
		delete fmt;

		UnicodeToCeguiString(uIntegerValue, formattedInteger);
	}

	if(!maskDecimal.empty())
	{
		// Prepare the decimal parser
		DecimalFormat* fmtParse = new DecimalFormat(status);
		if( U_FAILURE(status) )
		{
 			delete decimalFormatSymbols;
			return false;
		}
		fmtParse->applyLocalizedPattern("0.0000003", status);

		// Parse the string value into a decimal value
		UnicodeString uDecimalValue(decimalValue.c_str());
		Formattable fDecimalValue;
		fmtParse->parse(uDecimalValue, fDecimalValue, status);
		if( U_FAILURE(status) )
		{
			status = U_ZERO_ERROR;
			fDecimalValue.setDouble(0.0);
		}

		// Prepare the decimal formatter
		UnicodeString patternDecimal(maskDecimal.c_str());
		DecimalFormat* fmt = new DecimalFormat(patternDecimal, *decimalFormatSymbols, status);
		if( U_FAILURE(status) )
		{
 			delete decimalFormatSymbols;
			return false;
		}

		// Format the decimal value
		uDecimalValue = "";
		fmt->format(fDecimalValue.getDouble(), uDecimalValue);
		delete fmt;

		UnicodeToCeguiString(uDecimalValue, formattedDecimal);
	}

	delete decimalFormatSymbols;

	formattedValue = formattedInteger + formattedDecimal;
	return true;
}

bool ICU::formatNumber(const double rawValue, const CEGUI::String& mask, CEGUI::String& formattedValue)
{
	// Format a number using the appropriate locale rules
	// Note that the use of a double limits the effective range
    UErrorCode status = U_ZERO_ERROR;
	formattedValue.clear();

	// Prepare the numeric formatter
	DecimalFormatSymbols* symbols = new DecimalFormatSymbols(m_locale, status);
	if( U_FAILURE(status) )
		return false;

    // Create a number formatter for the current locale
	UnicodeString pattern(mask.c_str());
	DecimalFormat* fmt = new DecimalFormat(pattern, *symbols, status);
	if( U_FAILURE(status) )
	{
		delete symbols;
		return false;
	}

	// Format the number
	UnicodeString uValue;
	fmt->format(rawValue, uValue, status);
	delete symbols;
	delete fmt;
	if( U_FAILURE(status) )
		return false;

	UnicodeToCeguiString(uValue, formattedValue);
	return true;
}

bool ICU::formatNumber(const int32_t rawValue, const CEGUI::String& mask, CEGUI::String& formattedValue)
{
	// Format a number using the appropriate locale rules
	// Note that the use of a double limits the effective range
    UErrorCode status = U_ZERO_ERROR;
	formattedValue.clear();

	// Prepare the numeric formatter
	DecimalFormatSymbols* symbols = new DecimalFormatSymbols(m_locale, status);
	if( U_FAILURE(status) )
		return false;

    // Create a number formatter for the current locale
	UnicodeString pattern(mask.c_str());
	DecimalFormat* fmt = new DecimalFormat(pattern, *symbols, status);
	if( U_FAILURE(status) )
	{
		delete symbols;
		return false;
	}

	// Format the number
	UnicodeString uValue;
	fmt->format(rawValue, uValue, status);
	delete symbols;
	delete fmt;
	if( U_FAILURE(status) )
		return false;

	UnicodeToCeguiString(uValue, formattedValue);
	return true;
}

bool ICU::numberToText(const double rawValue, CEGUI::String& formattedValue)
{
	// Convert a number to a text
	// "1.2" to "one point two"
	return _ruleBasedNumberFormat(rawValue, URBNF_SPELLOUT, formattedValue);
}

bool ICU::numberToText(const int32_t rawValue, CEGUI::String& formattedValue)
{
	// Convert a number to a text
	// "12" to "twelve"
	return _ruleBasedNumberFormat(rawValue, URBNF_SPELLOUT, formattedValue);
}

bool ICU::numberToOrdinal(const double rawValue, CEGUI::String& formattedValue)
{
	// Convert a number to ordinal text
	// "1" to "1st"
	return _ruleBasedNumberFormat(rawValue, URBNF_ORDINAL, formattedValue);
}

bool ICU::numberToOrdinal(const int32_t rawValue, CEGUI::String& formattedValue)
{
	// Convert a number to ordinal text
	// "1" to "1st"
	return _ruleBasedNumberFormat(rawValue, URBNF_ORDINAL, formattedValue);
}

bool ICU::formatText(const CEGUI::String& rawValue, const CEGUI::String& mask, const CEGUI::String& zeroPlaceHolder, CEGUI::String& formattedValue)
{
	/* Format a value according to a pattern
	 * Format specifiers:
	 *		0 forced digit: will be replaced by a digit or if there is no digit, by 'zeroPlaceHolder'
	 *		# potential digit: will be replaced by a digit if there is one otherwise nothing
	 *		' literal character: the following character is to be treated as a character
	 *			rather than a format specifier
	 *			ex the value "1" with the format "'00" will result in the string "01"
	 * Note that if the value is larger than the pattern then it is truncated to the left
	 *		ex the value "1234" with the format "#0" results in the string "34"
	 */
	formattedValue.clear();

	if(!rawValue.length() || !mask.length())
		return false;

	// Parse the mask
	std::vector<CEGUI::String> maskList;
	CEGUI::String maskDigit;
	for(CEGUI::String::size_type idxMask = 0; idxMask < mask.length(); ++idxMask)
	{
		if(!mask.compare(idxMask, 1, "0"))
		{
			maskDigit = "ForcedDigit";
			maskList.push_back(maskDigit);
		}
		else if(!mask.compare(idxMask, 1, "#"))
		{
			maskDigit = "PotentialDigit";
			maskList.push_back(maskDigit);
		}
		else
		{
			if(!mask.compare(idxMask, 1, "'") && idxMask < mask.length() - 1)
				++idxMask; // Literal specifier followed by a mask digit, so skip over the apostrophe
			maskDigit = mask.at(idxMask);
			maskList.push_back(maskDigit);
		}
	}

	// Format the numeric value
	CEGUI::String::size_type idxValue;
	idxValue = rawValue.length();
	bool literalCharacter;
	std::vector<CEGUI::String>::reverse_iterator itMask;
	for(itMask = maskList.rbegin(); itMask != maskList.rend(); ++itMask)
	{
		if(!(*itMask).compare("ForcedDigit"))
		{
			if(idxValue)
			{
				// We have a digit remaining
				--idxValue;
				formattedValue = rawValue.at(idxValue) + formattedValue;
			}
			else
			{
				// We're out of digits
				formattedValue = zeroPlaceHolder + formattedValue;
			}
		}
		else if(!(*itMask).compare("PotentialDigit"))
		{
			if(idxValue)
			{
				// We have a digit remaining
				--idxValue;
				formattedValue = rawValue.at(idxValue) + formattedValue;
			}
		}
		else
		{
			// Literal character
			formattedValue = (*itMask) + formattedValue;
		}
	}
	return true;
}

bool ICU::formatCurrency(const double& currency, CEGUI::String& formattedValue)
{
	// Format a number using the appropriate locale rules
	// Note that the use of a double limits the effective range
    UErrorCode status = U_ZERO_ERROR;
	formattedValue.clear();

    // Create a number formatter for the current locale
    NumberFormat *fmt = NumberFormat::createCurrencyInstance(m_locale, status);
	if( U_FAILURE(status) )
		return false;

	// Format the currency
	UnicodeString uValue;
	fmt->format(currency, uValue);
    delete fmt;

	UnicodeToCeguiString(uValue, formattedValue);
	return true;
}

bool ICU::formatDateTime(UDate rawDateTime, const CEGUI::String& mask, CEGUI::String& formattedDateTime)
{
	// Format a date/time given the specified mask
	// Note that although a UDate can support both date and time in
	//   realite the resolution is insufficient.  In order to precisely
	//   represent a date/time value they should be processed separately
    UErrorCode status = U_ZERO_ERROR;
	formattedDateTime.clear();

	// Create a Date/Time formatter
	DateFormat* fmt = DateFormat::createDateTimeInstance(DateFormat::kDefault, DateFormat::kDefault, m_locale); // kDefault is not really used

	// Activate our pattern (overrides DateFormat::kDefault)
    UnicodeString pattern(mask.c_str());
	((SimpleDateFormat*) fmt)->applyPattern(pattern);

	// Format the date/time
    UnicodeString uValue;
	fmt->format(rawDateTime, uValue, status);
	delete fmt;
	if( U_FAILURE(status) )
		return false;

	UnicodeToCeguiString(uValue, formattedDateTime);
	return true;
}

bool ICU::localToGmt(const UDate& localDate, const UDate& localTime, UDate& gmtDate, UDate& gmtTime)
{
	// Convert a local date/time into a GMT date/time
	return _convertLocalAndGmt(localDate, localTime, gmtDate, gmtTime, true);
}

bool ICU::gmtToLocal(const UDate& gmtDate, const UDate& gmtTime, UDate& localDate, UDate& localTime)
{
	// Convert a GMT date/time into a local date/time string
	return _convertLocalAndGmt(gmtDate, gmtTime, localDate, localTime, false);
}

bool ICU::CeguiStringToDateTime(const CEGUI::String& stringDateTime, const CEGUI::String& mask, UDate& unicodeDateTime)
{
	// Convert a string date/time into a UDate
    UErrorCode status = U_ZERO_ERROR;
	unicodeDateTime = 0.0;

	// Create a Date/Time formatter
	DateFormat* fmt = DateFormat::createDateTimeInstance(DateFormat::kDefault, DateFormat::kDefault, m_locale);

	// Activate our pattern
    UnicodeString pattern(mask.c_str());
	((SimpleDateFormat*) fmt)->applyPattern(pattern);

	// Parse the string into a date/time
	UnicodeString uValue(stringDateTime.c_str());
	unicodeDateTime = fmt->parse(uValue, status);
	delete fmt;
	if( U_FAILURE(status) )
		return false;

	return true;
}

bool ICU::CeguiStringToInt32(const CEGUI::String& stringValue, int32_t& int32Value)
{
	// Convert a string into an int32_t
    UErrorCode status = U_ZERO_ERROR;
	int32Value = 0;

	// Create an integer formatter
	NumberFormat *fmt = NumberFormat::createInstance(Locale::getUS(), status);
	if( U_FAILURE(status) )
		return false;

	// Activate our pattern
	UnicodeString pattern("#,##0");
	((DecimalFormat*) fmt)->applyPattern(pattern, status);
	if( U_FAILURE(status) )
	{
		delete fmt;
		return false;
	}

	// Parse the string into a double
	UnicodeString uValue(stringValue.c_str());
    Formattable result;
    fmt->parse(uValue, result, status);
	delete fmt;
	if( U_FAILURE(status) )
		return false;

	int32Value = result.getLong();

	return true;
}

void ICU::UnicodeToCeguiString(const UnicodeString& unicodeString, CEGUI::String& ceguiString)
{
	// Convert a unicode string to a CEGUI string
	ceguiString.clear();

	CEGUI::String digit;
	for(int32_t i = 0; i < unicodeString.length(); ++i)
	{
		digit = unicodeString.charAt(i);
		ceguiString.append(digit);
	}
}

void ICU::_splitIntegerDecimal(const CEGUI::String& combined, CEGUI::String& integerPart, CEGUI::String& decimalPart)
{
	// Split a decimal value into its constituent integer and decimal parts
	CEGUI::String::size_type idx = combined.find(".");
	if(idx == CEGUI::String::npos)
	{
		integerPart = combined;
		decimalPart.clear();
	}
	else
	{
		integerPart = combined.substr(0, idx);
		decimalPart = combined.substr(idx);
	}
}

bool ICU::_convertLocalAndGmt(const UDate& localDate, const UDate& localTime, UDate& gmtDate, UDate& gmtTime, bool fromLocalToGMT)
{
	// Helper function to convert between a local and a GMT date/time
	UErrorCode status = U_ZERO_ERROR;
	gmtDate = gmtTime = 0.0;

	// Create a calendar for the local date
	Calendar* calLocalDate = Calendar::createInstance(m_locale, status);
	if( U_FAILURE(status) )
		return false;
	calLocalDate->clear();
	calLocalDate->setTime(localDate, status);
	if( U_FAILURE(status) )
	{
		delete calLocalDate;
		return false;
	}

	// Create a calendar for the local time
	Calendar* calLocalTime = Calendar::createInstance(m_locale, status);
	if( U_FAILURE(status) )
		return false;
	calLocalTime->clear();
	calLocalTime->setTime(localTime, status);
	if( U_FAILURE(status) )
	{
		delete calLocalDate;
		delete calLocalTime;
		return false;
	}

	// Retrieve the offset between this time zone and the GMT time zone
	// The Daylight Saving Time offset is zero when DST is not in effect
	// Note that adding the local date and time together introduces an
	//   inaccuracy of up to 1 minute 47 seconds.  However the only impact
	//   of this inaccuracy is to advance/delay the activation or deactivation
	//   of daylight savings
	int32_t rawOffset, dstOffset, gmtOffset;
	calLocalDate->getTimeZone().getOffset(localDate + localTime, true, rawOffset, dstOffset, status);
	gmtOffset = (rawOffset + dstOffset) / 1000 / 60 / 60; // Convert from milliseconds to hours

	// Converting from local to GMT requires "reversing" the time zone
	// EST is GMT-5 so it requires adding 5 hours to local time to obtain GMT
	// Converting from GMT to local requires the opposite
	int32_t hour = calLocalTime->get(UCAL_HOUR_OF_DAY, status);
	if(fromLocalToGMT)
		hour -= gmtOffset;
	else
		hour += gmtOffset;

	// Adjust the time and date if necessary
	if(hour >= 24)
	{
		// We've moved to the next day
		hour -= 24;
		calLocalDate->add(UCAL_DAY_OF_MONTH, 1, status);
		if( U_FAILURE(status) )
			return false;
	}
	else if(hour < 0)
	{
		// We've moved to the previous day
		hour += 24;
		calLocalDate->add(UCAL_DAY_OF_MONTH, -1, status);
		if( U_FAILURE(status) )
			return false;
	}
	calLocalTime->set(UCAL_HOUR_OF_DAY, hour);

	// Retrieve the converted date and time
	gmtDate = calLocalDate->getTime(status);
	gmtTime = calLocalTime->getTime(status);

	delete calLocalDate;
	delete calLocalTime;
	if( U_FAILURE(status) )
		return false;

	return true;
}

bool ICU::_ruleBasedNumberFormat(const double rawValue, URBNFRuleSetTag tag, CEGUI::String& formattedValue)
{
    UErrorCode status = U_ZERO_ERROR;
	formattedValue.clear();

	// Create a rule based number formatter
	RuleBasedNumberFormat* fmt = new RuleBasedNumberFormat(tag, m_locale, status);
	if( U_FAILURE(status) )
		return false;

    UnicodeString uValue;
    fmt->format(rawValue, uValue);
	delete fmt;

	formattedValue.clear();
	UnicodeToCeguiString(uValue, formattedValue);
	return true;
}

bool ICU::_ruleBasedNumberFormat(const int32_t rawValue, URBNFRuleSetTag tag, CEGUI::String& formattedValue)
{
    UErrorCode status = U_ZERO_ERROR;
	formattedValue.clear();

	// Create a rule based number formatter
	RuleBasedNumberFormat* fmt = new RuleBasedNumberFormat(tag, m_locale, status);
	if( U_FAILURE(status) )
		return false;

    UnicodeString uValue;
	Formattable fValue(rawValue);
	fmt->format(fValue, uValue, status);
	delete fmt;
	if( U_FAILURE(status) )
		return false;

	formattedValue.clear();
	UnicodeToCeguiString(uValue, formattedValue);
	return true;
}

FormattedNumericData.h

#ifndef _FormattedNumericData_h_
#define _FormattedNumericData_h_

#include "CEGuiSample.h"
#include "CEGUI.h"

#include "ICU.h"

class DemoSample : public CEGuiSample
{
public:
    bool initialiseSample()
	{
		using namespace CEGUI;
		try
		{
			// Retrieve the window manager
			WindowManager& winMgr = WindowManager::getSingleton();

			// Load the TaharezLook scheme and set up the default mouse cursor and font
			SchemeManager::getSingleton().loadScheme("../datafiles/schemes/TaharezLookSkin.scheme");
			System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");
			FontManager::getSingleton().createFont("../datafiles/fonts/Commonwealth-10.font");

			// Set the GUI Sheet
			Window* sheet = winMgr.createWindow("DefaultWindow", "root_wnd");
			System::getSingleton().setGUISheet(sheet);

			// Load a layout
			Window* guiLayout = winMgr.loadWindowLayout("../datafiles/layouts/FormattedNumericData.layout");
			sheet->addChildWindow(guiLayout);


			/******** ICU Stuff ********/


			// Display one ISO country in a combo box
			Combobox* countries = static_cast<Combobox*>(winMgr.getWindow("Countries"));
			countries->setReadOnly(true);
			ListboxTextItem* listboxTextItem;
			listboxTextItem = new ListboxTextItem( Locale::getDefault().getCountry() );
			listboxTextItem->setSelectionBrushImage("TaharezLook", "MultiListSelectionBrush");
			countries->addItem(listboxTextItem);
			countries->setText(	Locale::getDefault().getCountry() );
			countries->subscribeEvent(Combobox::EventListSelectionAccepted, Event::Subscriber(&DemoSample::onLocaleChanged, this));

			// Display two ISO languages in a combo box
			Combobox* languages = static_cast<Combobox*>(winMgr.getWindow("Languages"));
			languages->subscribeEvent(Combobox::EventListSelectionAccepted, Event::Subscriber(&DemoSample::onLocaleChanged, this));
			languages->setReadOnly(true);
			listboxTextItem = new ListboxTextItem("en");
			listboxTextItem->setSelectionBrushImage("TaharezLook", "MultiListSelectionBrush");
			languages->addItem(listboxTextItem);
			listboxTextItem = new ListboxTextItem("fr");
			listboxTextItem->setSelectionBrushImage("TaharezLook", "MultiListSelectionBrush");
			languages->addItem(listboxTextItem);
			languages->setText("en");
			languages->subscribeEvent(Combobox::EventListSelectionAccepted, Event::Subscriber(&DemoSample::onLocaleChanged, this));
			PushButton* everyISO = static_cast<PushButton*>(winMgr.getWindow("EveryISO"));
			everyISO->subscribeEvent(PushButton::EventClicked, Event::Subscriber(&DemoSample::onLoadEveryISO, this));

			// Configure the currency widgets
			Editbox* currencyValue = static_cast<Editbox*>(winMgr.getWindow("CurrencyValue"));
			currencyValue->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onCurrencyChanged, this));
			RadioButton* radio;
			radio = static_cast<RadioButton*>(winMgr.getWindow("RadioCAD"));
			radio->subscribeEvent(RadioButton::EventSelectStateChanged, Event::Subscriber(&DemoSample::onCurrencySelected, this));
			radio->setID(1);
			radio->setSelected(true);
			radio = static_cast<RadioButton*>(winMgr.getWindow("RadioUSD"));
			radio->subscribeEvent(RadioButton::EventSelectStateChanged, Event::Subscriber(&DemoSample::onCurrencySelected, this));
			radio->setID(2);
			radio = static_cast<RadioButton*>(winMgr.getWindow("RadioEUR"));
			radio->subscribeEvent(RadioButton::EventSelectStateChanged, Event::Subscriber(&DemoSample::onCurrencySelected, this));
			radio->setID(3);
			radio = static_cast<RadioButton*>(winMgr.getWindow("RadioMRO"));
			radio->subscribeEvent(RadioButton::EventSelectStateChanged, Event::Subscriber(&DemoSample::onCurrencySelected, this));
			radio->setID(4);

			// Configure the numeric widgets
			Editbox* numericValue = static_cast<Editbox*>(winMgr.getWindow("NumericValue"));
			numericValue->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onNumericChanged, this));
			Editbox* numericFormat = static_cast<Editbox*>(winMgr.getWindow("NumericFormat"));
			numericFormat->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onNumericChanged, this));
			PushButton* numericFormatButton = static_cast<PushButton*>(winMgr.getWindow("NumericFormatButton"));
			numericFormatButton->subscribeEvent(PushButton::EventClicked, Event::Subscriber(&DemoSample::onNumericFormatClicked, this));

			// Configure the date/time widgets
			Editbox* dateValue = static_cast<Editbox*>(winMgr.getWindow("DateValue"));
			dateValue->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onDateTimeChanged, this));
			Editbox* timeValue = static_cast<Editbox*>(winMgr.getWindow("TimeValue"));
			timeValue->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onDateTimeChanged, this));
			Editbox* dateFormat = static_cast<Editbox*>(winMgr.getWindow("DateFormat"));
			dateFormat->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onDateTimeChanged, this));
			Editbox* timeFormat = static_cast<Editbox*>(winMgr.getWindow("TimeFormat"));
			timeFormat->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onDateTimeChanged, this));
			PushButton* dateTimeFormatButton = static_cast<PushButton*>(winMgr.getWindow("DateTimeFormatButton"));
			dateTimeFormatButton->subscribeEvent(PushButton::EventClicked, Event::Subscriber(&DemoSample::onDateTimeFormatClicked, this));

			// Configure the spinner
			Spinner* spinner = static_cast<Spinner*>(winMgr.getWindow("Spinner"));
			spinner->subscribeEvent(Spinner::EventValueChanged, Event::Subscriber(&DemoSample::onSpinnerValueChanged, this));
			static_cast<Editbox*>(winMgr.getWindow(spinner->getName() + "__auto_editbox__"))->setReadOnly(true); // We cannot handle manually specified values yet
			spinner->setTextInputMode(Spinner::FloatingPoint); // FloatingPoint, Integer, Hexadecimal, Octal
			spinner->setMinimumValue(-10.0f);
			spinner->setMaximumValue(10.0f);
			spinner->setStepSize(0.02f);
			spinner->setCurrentValue(5.2f);

			// Configure the character widgets
			Editbox* characterValue = static_cast<Editbox*>(winMgr.getWindow("CharacterValue"));
			characterValue->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onCharacterChanged, this));
			Editbox* characterFormat = static_cast<Editbox*>(winMgr.getWindow("CharacterFormat"));
			characterFormat->subscribeEvent(Editbox::EventTextChanged, Event::Subscriber(&DemoSample::onCharacterChanged, this));
			PushButton* characterFormatButton = static_cast<PushButton*>(winMgr.getWindow("CharacterFormatButton"));
			characterFormatButton->subscribeEvent(PushButton::EventClicked, Event::Subscriber(&DemoSample::onCharacterFormatClicked, this));
			EventArgs e;
			onCharacterFormatClicked(e);

			// Initialize the localise values
			RefreshLocalisedValues();
		}
		catch(Exception &e)
		{
			#if defined( __WIN32__ ) || defined( _WIN32 )
				MessageBox(NULL, e.getMessage().c_str(), "Error initializing the demo", MB_OK | MB_ICONERROR | MB_TASKMODAL);
			#else
				std::cerr << "Error initializing the demo:" << e.getMessage().c_str() << "\n";
			#endif
		}

		return true;
	}

    void cleanupSample(void)
	{
	}
private:
	ICU icu; // International Components for Unicode

	void RefreshLocalisedValues()
	{
		// Trigger changes in localised widgets to refresh their values accordingly
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		EventArgs eventArgs;
		static_cast<RadioButton*>(winMgr.getWindow("RadioCAD"))->fireEvent(RadioButton::EventSelectStateChanged, eventArgs);
		static_cast<PushButton*>(winMgr.getWindow("NumericFormatButton"))->fireEvent(PushButton::EventClicked, eventArgs);
		static_cast<PushButton*>(winMgr.getWindow("DateTimeFormatButton"))->fireEvent(PushButton::EventClicked, eventArgs);
		static_cast<Spinner*>(winMgr.getWindow("Spinner"))->fireEvent(Spinner::EventValueChanged, eventArgs);
	}

	bool onLocaleChanged(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		String country = static_cast<Combobox*>(winMgr.getWindow("Countries"))->getText();
		String language = static_cast<Combobox*>(winMgr.getWindow("Languages"))->getText();
		if(icu.setLocale(language.c_str(), country.c_str()))
			RefreshLocalisedValues();
		else
			MessageBox(NULL,
						("Error with setLocale(" + language + ", " + country + ")").c_str(), 
						"Error",
						MB_OK | MB_ICONERROR | MB_TASKMODAL);

		return true;
	}

	bool onLoadEveryISO(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		ListboxTextItem* listboxTextItem;
		int32_t count;

		// Change the label of the button
		PushButton* everyISO = static_cast<PushButton*>(winMgr.getWindow("EveryISO"));
		everyISO->setText("This will take a few seconds...");

		// Display every ISO countries in a combobox
		Combobox* countries = static_cast<Combobox*>(winMgr.getWindow("Countries"));
		const char * const * listCountries = Locale::getISOCountries();
		for(count = 0; listCountries[count]; count++)
		{
			listboxTextItem = new ListboxTextItem(listCountries[count]);
			listboxTextItem->setSelectionBrushImage("TaharezLook", "MultiListSelectionBrush");
			countries->addItem(listboxTextItem);
		}
		countries->setText(	Locale::getDefault().getCountry() );

		// Display every ISO languages in a combobox
		Combobox* languages = static_cast<Combobox*>(winMgr.getWindow("Languages"));
		const char * const * listLanguages = Locale::getISOLanguages();
		for(count = 0; listLanguages[count]; count++)
		{
			listboxTextItem = new ListboxTextItem(listLanguages[count]);
			listboxTextItem->setSelectionBrushImage("TaharezLook", "MultiListSelectionBrush");
			languages->addItem(listboxTextItem);
		}
		languages->setText(	Locale::getDefault().getLanguage() );

		// Remove this button since it is no longer useful
		everyISO->setEnabled(false);
		everyISO->setVisible(false);

		return true;
	}

	bool onCurrencyChanged(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		EventArgs eventArgs;
		static_cast<RadioButton*>(winMgr.getWindow("RadioCAD"))->fireEvent(RadioButton::EventSelectStateChanged, eventArgs);
		return true;
	}

	bool onCurrencySelected(const CEGUI::EventArgs& e)
	{
		// Reformat the numeric value into a properly formatted currency
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();

		// Set the currency
		CEGUI::uint id = static_cast<RadioButton*>(winMgr.getWindow("RadioCAD"))->getSelectedButtonInGroup()->getID();
		switch(id)
		{
		case 1:
			icu.setCurrency("CAD");
			break;
		case 2:
			icu.setCurrency("USD");
			break;
		case 3:
			icu.setCurrency("EUR");
			break;
		case 4:
			// Mauritania does not use a decimal division of units
			// and yet ICU still displays decimal units??
			icu.setCurrency("MRO");
			break;
		}

		CEGUI::String rawValue = static_cast<Editbox*>(winMgr.getWindow("CurrencyValue"))->getText();
		double currency = atof(rawValue.c_str());
		CEGUI::String formattedValue;
		if( icu.formatCurrency(currency, formattedValue) )
			static_cast<Editbox*>(winMgr.getWindow("CurrencyFormattedValue"))->setText(formattedValue);
		return true;
	}

	bool onNumericChanged(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		EventArgs eventArgs;
		static_cast<PushButton*>(winMgr.getWindow("NumericFormatButton"))->fireEvent(PushButton::EventClicked, eventArgs);
		return true;
	}

	bool onNumericFormatClicked(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		String formattedText;
		if( icu.formatNumber(static_cast<Editbox*>(winMgr.getWindow("NumericValue"))->getText(),
							static_cast<Editbox*>(winMgr.getWindow("NumericFormat"))->getText(),
							formattedText) )
			static_cast<Editbox*>(winMgr.getWindow("NumericFormattedValue"))->setText(formattedText);

		// Ordinal representation of an integer value
		double doubleValue = atof(static_cast<Editbox*>(winMgr.getWindow("NumericValue"))->getText().c_str());
		if( icu.numberToOrdinal((int) doubleValue, formattedText) )
			static_cast<Editbox*>(winMgr.getWindow("NumericFormattedOrdinal"))->setText(formattedText);

		// Textual representation of the numeric value
		// Note that this value must be raw, containing only digits and a decimal point
		// Since atof() is used to convert from a string to a double this numeric value is not localised
		if( icu.numberToText(doubleValue, formattedText) )
			static_cast<Editbox*>(winMgr.getWindow("NumericFormattedText"))->setText(formattedText);

		return true;
	}

	bool onDateTimeChanged(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		EventArgs eventArgs;
		static_cast<PushButton*>(winMgr.getWindow("DateTimeFormatButton"))->fireEvent(PushButton::EventClicked, eventArgs);
		return true;
	}

	bool onDateTimeFormatClicked(const CEGUI::EventArgs& e)
	{
		// Format the date and time according to their specified values and formats
		// In theory a format can include both the date and the time:  yyyy/MM/dd HH:mm:ss
		// However in practice the resulting time is not accurate (within 1 minute 47 seconds)
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		UDate localDate, localTime, gmtDate, gmtTime, gmtDateTime;
		if(icu.CeguiStringToDateTime(	static_cast<Editbox*>(winMgr.getWindow("DateValue"))->getText(),
										"yyyy/MM/dd", // Internal format
										localDate)
			&& icu.CeguiStringToDateTime(	static_cast<Editbox*>(winMgr.getWindow("TimeValue"))->getText(),
											"HH:mm:ss", // Internal format
											localTime) )
		{
			String formattedDate, formattedTime;
			if(icu.formatDateTime(	localDate,
									static_cast<Editbox*>(winMgr.getWindow("DateFormat"))->getText(),
									formattedDate)
				&& icu.formatDateTime(	localTime,
										static_cast<Editbox*>(winMgr.getWindow("TimeFormat"))->getText(),
										formattedTime)
				)
				static_cast<Editbox*>(winMgr.getWindow("LocalDateTimeFormattedValue"))->setText(formattedDate + " " + formattedTime);

			// GMT time
			if( icu.CeguiStringToDateTime(static_cast<Editbox*>(winMgr.getWindow("LocalDateTimeFormattedValue"))->getText(),
											"yyyy/MM/dd HH:mm:ss",
											gmtDateTime)
				&& icu.localToGmt(localDate, localTime, gmtDate, gmtTime)
				&& icu.formatDateTime(gmtDate, "yyyy/MM/dd", formattedDate)
				&& icu.formatDateTime(gmtTime, "HH:mm:ss", formattedTime)
				)
				static_cast<Editbox*>(winMgr.getWindow("GmtDateTimeFormattedValue"))->setText(formattedDate + " " + formattedTime);
		}

		return true;
	}

	bool onSpinnerValueChanged(const CEGUI::EventArgs& e)
	{
		// Reformat the displayed value into a locale appropriate format
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		Spinner* spinner = static_cast<Spinner*>(winMgr.getWindow("Spinner"));

		String formattedValue;
		if( icu.formatNumber(spinner->getCurrentValue(), "#0.00", formattedValue) )
		{
			// Disable the Editbox events, especially the EventTextChanged that
			// triggers Spinner::handleEditTextChange, which attempts to convert
			// the textual value into a non-localised numeric value
			Editbox* editbox = static_cast<Editbox*>(winMgr.getWindow(spinner->getName() + "__auto_editbox__"));
			bool muted = editbox->isMuted();
			editbox->setMutedState(true);
			spinner->setText(formattedValue);
			editbox->setMutedState(muted);
		}
		return true;
	}

	bool onCharacterChanged(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		EventArgs eventArgs;
		static_cast<PushButton*>(winMgr.getWindow("CharacterFormatButton"))->fireEvent(PushButton::EventClicked, eventArgs);
		return true;
	}

	bool onCharacterFormatClicked(const CEGUI::EventArgs& e)
	{
		using namespace CEGUI;
		WindowManager& winMgr = WindowManager::getSingleton();
		String formattedText;
		if( icu.formatText(static_cast<Editbox*>(winMgr.getWindow("CharacterValue"))->getText(),
							static_cast<Editbox*>(winMgr.getWindow("CharacterFormat"))->getText(),
							"*",
							formattedText) )
			static_cast<Editbox*>(winMgr.getWindow("CharacterFormattedValue"))->setText(formattedText);

		return true;
	}

};

#endif // _FormattedNumericData_h_


main.cpp

#if defined( __WIN32__ ) || defined( _WIN32 )
	#define WIN32_LEAN_AND_MEAN
	#define NOMINMAX
	#include "windows.h"
#endif

#include "FormattedNumericData.h"

#if defined( __WIN32__ ) || defined( _WIN32 )
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)
#else
int main(int argc, char *argv[])
#endif
{
    DemoSample app;
    return app.run();
}