Example #1
0
bool
NumberValcon::parse(const QString& text)
{
    if (text.isEmpty()) {
	_value = 0;
	return true;
    }

    UnicodeString utext = convertToICU(text);

    // Parse the number using ICU
    UErrorCode status = U_ZERO_ERROR;
    NumberFormat* fmt = NumberFormat::createInstance(status);
    if (U_SUCCESS(status)) {
	Formattable value;
	ParsePosition pos;
	fmt->parse(utext, value, pos);
	if (pos.getErrorIndex() == -1 && pos.getIndex() == utext.length()) {
#if U_ICU_VERSION_MAJOR_NUM < 3
	    _value = value.getDouble(&status);
#else
	    _value = value.getDouble(status);
#endif
	    return true;
	}
    }

    return false;
}
Example #2
0
/**
 * Sample code for the C++ API to NumberFormat.
 */
void cppapi() {
    Locale us("en", "US");
    UErrorCode status = U_ZERO_ERROR;
    
    // Create a number formatter for the US locale
    NumberFormat *fmt = NumberFormat::createInstance(us, status);
    check(status, "NumberFormat::createInstance");

    // Parse a string.  The string uses the digits '0' through '9'
    // and the decimal separator '.', standard in the US locale
    UnicodeString str("9876543210.123");
    Formattable result;
    fmt->parse(str, result, status);
    check(status, "NumberFormat::parse");

    printf("NumberFormat::parse(\""); // Display the result
    uprintf(str);
    printf("\") => ");
    uprintf(formattableToString(result));
    printf("\n");

    // Take the number parsed above, and use the formatter to
    // format it.
    str.remove(); // format() will APPEND to this string
    fmt->format(result, str, status);
    check(status, "NumberFormat::format");

    printf("NumberFormat::format("); // Display the result
    uprintf(formattableToString(result));
    printf(") => \"");
    uprintf(str);
    printf("\"\n");

    delete fmt; // Release the storage used by the formatter
    
}
/**
 * This test does round-trip testing (format -> parse -> format -> parse -> etc.) of
 * NumberFormat.
 */
void IntlTestNumberFormatAPI::testAPI(/* char* par */)
{
    UErrorCode status = U_ZERO_ERROR;

// ======= Test constructors

    logln("Testing NumberFormat constructors");

    NumberFormat *def = NumberFormat::createInstance(status);
    if(U_FAILURE(status)) {
        errln("ERROR: Could not create NumberFormat (default)");
    }

    status = U_ZERO_ERROR;
    NumberFormat *fr = NumberFormat::createInstance(Locale::getFrench(), status);
    if(U_FAILURE(status)) {
        errln("ERROR: Could not create NumberFormat (French)");
    }

    NumberFormat *cur = NumberFormat::createCurrencyInstance(status);
    if(U_FAILURE(status)) {
        errln("ERROR: Could not create NumberFormat (currency, default)");
    }

    status = U_ZERO_ERROR;
    NumberFormat *cur_fr = NumberFormat::createCurrencyInstance(Locale::getFrench(), status);
    if(U_FAILURE(status)) {
        errln("ERROR: Could not create NumberFormat (currency, French)");
    }

    NumberFormat *per = NumberFormat::createPercentInstance(status);
    if(U_FAILURE(status)) {
        errln("ERROR: Could not create NumberFormat (percent, default)");
    }

    status = U_ZERO_ERROR;
    NumberFormat *per_fr = NumberFormat::createPercentInstance(Locale::getFrench(), status);
    if(U_FAILURE(status)) {
        errln("ERROR: Could not create NumberFormat (percent, French)");
    }

// ======= Test equality
    if (per_fr != NULL && cur_fr != NULL)
    {
        logln("Testing equality operator");

        if( *per_fr == *cur_fr || ! ( *per_fr != *cur_fr) ) {
            errln("ERROR: == failed");
        }
    }

// ======= Test various format() methods
    if (cur_fr != NULL)
    {
        logln("Testing various format() methods");

        double d = -10456.0037;
        int32_t l = 100000000;
        Formattable fD(d);
        Formattable fL(l);

        UnicodeString res1, res2, res3, res4, res5, res6;
        FieldPosition pos1(0), pos2(0), pos3(0), pos4(0);

        res1 = cur_fr->format(d, res1);
        logln( (UnicodeString) "" + (int32_t) d + " formatted to " + res1);

        res2 = cur_fr->format(l, res2);
        logln((UnicodeString) "" + (int32_t) l + " formatted to " + res2);

        res3 = cur_fr->format(d, res3, pos1);
        logln( (UnicodeString) "" + (int32_t) d + " formatted to " + res3);

        res4 = cur_fr->format(l, res4, pos2);
        logln((UnicodeString) "" + (int32_t) l + " formatted to " + res4);

        status = U_ZERO_ERROR;
        res5 = cur_fr->format(fD, res5, pos3, status);
        if(U_FAILURE(status)) {
            errln("ERROR: format(Formattable [double]) failed");
        }
        logln((UnicodeString) "" + (int32_t) fD.getDouble() + " formatted to " + res5);

        status = U_ZERO_ERROR;
        res6 = cur_fr->format(fL, res6, pos4, status);
        if(U_FAILURE(status)) {
            errln("ERROR: format(Formattable [long]) failed");
        }
        logln((UnicodeString) "" + fL.getLong() + " formatted to " + res6);
    }

// ======= Test parse()
    if (fr != NULL)
    {
        logln("Testing parse()");

        double d = -10456.0037;
        UnicodeString text("-10,456.0037");
        Formattable result1, result2, result3;
        ParsePosition pos(0), pos01(0);
        fr->parseObject(text, result1, pos);
        if(result1.getType() != Formattable::kDouble && result1.getDouble() != d) {
            errln("ERROR: Roundtrip failed (via parse()) for " + text);
        }
        logln(text + " parsed into " + (int32_t) result1.getDouble());

        fr->parse(text, result2, pos01);
        if(result2.getType() != Formattable::kDouble && result2.getDouble() != d) {
            errln("ERROR: Roundtrip failed (via parse()) for " + text);
        }
        logln(text + " parsed into " + (int32_t) result2.getDouble());

        status = U_ZERO_ERROR;
        fr->parse(text, result3, status);
        if(U_FAILURE(status)) {
            errln("ERROR: parse() failed");
        }
        if(result3.getType() != Formattable::kDouble && result3.getDouble() != d) {
            errln("ERROR: Roundtrip failed (via parse()) for " + text);
        }
        logln(text + " parsed into " + (int32_t) result3.getDouble());
    }

// ======= Test getters and setters
    if (fr != NULL && def != NULL)
    {
        logln("Testing getters and setters");

        int32_t count = 0;
        const Locale *locales = NumberFormat::getAvailableLocales(count);
        logln((UnicodeString) "Got " + count + " locales" );
        for(int32_t i = 0; i < count; i++) {
            UnicodeString name(locales[i].getName(),"");
            logln(name);
        }

        fr->setParseIntegerOnly( def->isParseIntegerOnly() );
        if(fr->isParseIntegerOnly() != def->isParseIntegerOnly() ) {
            errln("ERROR: setParseIntegerOnly() failed");
        }

        fr->setGroupingUsed( def->isGroupingUsed() );
        if(fr->isGroupingUsed() != def->isGroupingUsed() ) {
            errln("ERROR: setGroupingUsed() failed");
        }

        fr->setMaximumIntegerDigits( def->getMaximumIntegerDigits() );
        if(fr->getMaximumIntegerDigits() != def->getMaximumIntegerDigits() ) {
            errln("ERROR: setMaximumIntegerDigits() failed");
        }

        fr->setMinimumIntegerDigits( def->getMinimumIntegerDigits() );
        if(fr->getMinimumIntegerDigits() != def->getMinimumIntegerDigits() ) {
            errln("ERROR: setMinimumIntegerDigits() failed");
        }

        fr->setMaximumFractionDigits( def->getMaximumFractionDigits() );
        if(fr->getMaximumFractionDigits() != def->getMaximumFractionDigits() ) {
            errln("ERROR: setMaximumFractionDigits() failed");
        }

        fr->setMinimumFractionDigits( def->getMinimumFractionDigits() );
        if(fr->getMinimumFractionDigits() != def->getMinimumFractionDigits() ) {
            errln("ERROR: setMinimumFractionDigits() failed");
        }
    }

// ======= Test getStaticClassID()

    logln("Testing getStaticClassID()");

    status = U_ZERO_ERROR;
    NumberFormat *test = new DecimalFormat(status);
    if(U_FAILURE(status)) {
        errln("ERROR: Couldn't create a NumberFormat");
    }

    if(test->getDynamicClassID() != DecimalFormat::getStaticClassID()) {
        errln("ERROR: getDynamicClassID() didn't return the expected value");
    }

    delete test;
    delete def;
    delete fr;
    delete cur;
    delete cur_fr;
    delete per;
    delete per_fr;
}
Example #4
0
UBool
TimeZone::parseCustomID(const UnicodeString& id, int32_t& sign,
                        int32_t& hour, int32_t& min, int32_t& sec) {
    static const int32_t         kParseFailed = -99999;

    NumberFormat* numberFormat = 0;
    UnicodeString idUppercase = id;
    idUppercase.toUpper();

    if (id.length() > GMT_ID_LENGTH &&
        idUppercase.startsWith(GMT_ID))
    {
        ParsePosition pos(GMT_ID_LENGTH);
        sign = 1;
        hour = 0;
        min = 0;
        sec = 0;

        if (id[pos.getIndex()] == MINUS /*'-'*/) {
            sign = -1;
        } else if (id[pos.getIndex()] != PLUS /*'+'*/) {
            return FALSE;
        }
        pos.setIndex(pos.getIndex() + 1);

        UErrorCode success = U_ZERO_ERROR;
        numberFormat = NumberFormat::createInstance(success);
        if(U_FAILURE(success)){
            return FALSE;
        }
        numberFormat->setParseIntegerOnly(TRUE);

        // Look for either hh:mm, hhmm, or hh
        int32_t start = pos.getIndex();
        Formattable n(kParseFailed);
        numberFormat->parse(id, n, pos);
        if (pos.getIndex() == start) {
            delete numberFormat;
            return FALSE;
        }
        hour = n.getLong();

        if (pos.getIndex() < id.length()) {
            if (pos.getIndex() - start > 2
                || id[pos.getIndex()] != COLON) {
                delete numberFormat;
                return FALSE;
            }
            // hh:mm
            pos.setIndex(pos.getIndex() + 1);
            int32_t oldPos = pos.getIndex();
            n.setLong(kParseFailed);
            numberFormat->parse(id, n, pos);
            if ((pos.getIndex() - oldPos) != 2) {
                // must be 2 digits
                delete numberFormat;
                return FALSE;
            }
            min = n.getLong();
            if (pos.getIndex() < id.length()) {
                if (id[pos.getIndex()] != COLON) {
                    delete numberFormat;
                    return FALSE;
                }
                // [:ss]
                pos.setIndex(pos.getIndex() + 1);
                oldPos = pos.getIndex();
                n.setLong(kParseFailed);
                numberFormat->parse(id, n, pos);
                if (pos.getIndex() != id.length()
                        || (pos.getIndex() - oldPos) != 2) {
                    delete numberFormat;
                    return FALSE;
                }
                sec = n.getLong();
            }
        } else {
            // Supported formats are below -
            //
            // HHmmss
            // Hmmss
            // HHmm
            // Hmm
            // HH
            // H

            int32_t length = pos.getIndex() - start;
            if (length <= 0 || 6 < length) {
                // invalid length
                delete numberFormat;
                return FALSE;
            }
            switch (length) {
                case 1:
                case 2:
                    // already set to hour
                    break;
                case 3:
                case 4:
                    min = hour % 100;
                    hour /= 100;
                    break;
                case 5:
                case 6:
                    sec = hour % 100;
                    min = (hour/100) % 100;
                    hour /= 10000;
                    break;
            }
        }

        delete numberFormat;

        if (hour > kMAX_CUSTOM_HOUR || min > kMAX_CUSTOM_MIN || sec > kMAX_CUSTOM_SEC) {
            return FALSE;
        }
        return TRUE;
    }
    return FALSE;
}
// test RBNF extensions to message format
void TestMessageFormat::TestRBNF(void) {
    // WARNING: this depends on the RBNF formats for en_US
    Locale locale("en", "US", "");

    UErrorCode ec = U_ZERO_ERROR;

    UnicodeString values[] = {
        // decimal values do not format completely for ordinal or duration, and 
        // do not always parse, so do not include them
        "0", "1", "12", "100", "123", "1001", "123,456", "-17",
    };
    int32_t values_count = sizeof(values)/sizeof(values[0]);

    UnicodeString formats[] = {
        "There are {0,spellout} files to search.",
        "There are {0,spellout,%simplified} files to search.",
        "The bogus spellout {0,spellout,%BOGUS} files behaves like the default.",
        "This is the {0,ordinal} file to search.", // TODO fix bug, ordinal does not parse
        "Searching this file will take {0,duration} to complete.",
        "Searching this file will take {0,duration,%with-words} to complete.",
    };
    int32_t formats_count = sizeof(formats)/sizeof(formats[0]);

    Formattable args[1];

    NumberFormat* numFmt = NumberFormat::createInstance(locale, ec);
    if (U_FAILURE(ec)) {
        dataerrln("Error calling NumberFormat::createInstance()");
        return;
    }

    for (int i = 0; i < formats_count; ++i) {
        MessageFormat* fmt = new MessageFormat(formats[i], locale, ec);
        logln((UnicodeString)"Testing format pattern: '" + formats[i] + "'");

        for (int j = 0; j < values_count; ++j) {
            ec = U_ZERO_ERROR;
            numFmt->parse(values[j], args[0], ec);
            if (U_FAILURE(ec)) {
                errln((UnicodeString)"Failed to parse test argument " + values[j]);
            } else {
                FieldPosition fp(0);
                UnicodeString result;
                fmt->format(args, 1, result, fp, ec);
                logln((UnicodeString)"value: " + toString(args[0]) + " --> " + result + UnicodeString(" ec: ") + u_errorName(ec));
               
                if (i != 3) { // TODO: fix this, for now skip ordinal parsing (format string at index 3)
                    int32_t count = 0;
                    Formattable* parseResult = fmt->parse(result, count, ec);
                    if (count != 1) {
                        errln((UnicodeString)"parse returned " + count + " args");
                    } else if (parseResult[0] != args[0]) {
                        errln((UnicodeString)"parsed argument " + toString(parseResult[0]) + " != " + toString(args[0]));
                    }
                    delete []parseResult;
                }
            }
        }
        delete fmt;
    }
    delete numFmt;
}
/**
 * Parses a string using the rule set or DecimalFormat belonging
 * to this substitution.  If there's a match, a mathematical
 * operation (the inverse of the one used in formatting) is
 * performed on the result of the parse and the value passed in
 * and returned as the result.  The parse position is updated to
 * point to the first unmatched character in the string.
 * @param text The string to parse
 * @param parsePosition On entry, ignored, but assumed to be 0.
 * On exit, this is updated to point to the first unmatched
 * character (or 0 if the substitution didn't match)
 * @param baseValue A partial parse result that should be
 * combined with the result of this parse
 * @param upperBound When searching the rule set for a rule
 * matching the string passed in, only rules with base values
 * lower than this are considered
 * @param lenientParse If true and matching against rules fails,
 * the substitution will also try matching the text against
 * numerals using a default-costructed NumberFormat.  If false,
 * no extra work is done.  (This value is false whenever the
 * formatter isn't in lenient-parse mode, but is also false
 * under some conditions even when the formatter _is_ in
 * lenient-parse mode.)
 * @return If there's a match, this is the result of composing
 * baseValue with whatever was returned from matching the
 * characters.  This will be either a Long or a Double.  If there's
 * no match this is new Long(0) (not null), and parsePosition
 * is left unchanged.
 */
UBool
NFSubstitution::doParse(const UnicodeString& text,
                        ParsePosition& parsePosition,
                        double baseValue,
                        double upperBound,
                        UBool lenientParse,
                        Formattable& result) const
{
#ifdef RBNF_DEBUG
    fprintf(stderr, "<nfsubs> %x bv: %g ub: %g\n", this, baseValue, upperBound);
#endif
    // figure out the highest base value a rule can have and match
    // the text being parsed (this varies according to the type of
    // substitutions: multiplier, modulus, and numerator substitutions
    // restrict the search to rules with base values lower than their
    // own; same-value substitutions leave the upper bound wherever
    // it was, and the others allow any rule to match
    upperBound = calcUpperBound(upperBound);

    // use our rule set to parse the text.  If that fails and
    // lenient parsing is enabled (this is always false if the
    // formatter's lenient-parsing mode is off, but it may also
    // be false even when the formatter's lenient-parse mode is
    // on), then also try parsing the text using a default-
    // constructed NumberFormat
    if (ruleSet != NULL) {
        ruleSet->parse(text, parsePosition, upperBound, result);
        if (lenientParse && !ruleSet->isFractionRuleSet() && parsePosition.getIndex() == 0) {
            UErrorCode status = U_ZERO_ERROR;
            NumberFormat* fmt = NumberFormat::createInstance(status);
            if (U_SUCCESS(status)) {
                fmt->parse(text, result, parsePosition);
            }
            delete fmt;
        }

        // ...or use our DecimalFormat to parse the text
    } else if (numberFormat != NULL) {
        numberFormat->parse(text, result, parsePosition);
    }

    // if the parse was successful, we've already advanced the caller's
    // parse position (this is the one function that doesn't have one
    // of its own).  Derive a parse result and return it as a Long,
    // if possible, or a Double
    if (parsePosition.getIndex() != 0) {
        UErrorCode status = U_ZERO_ERROR;
        double tempResult = result.getDouble(status);

        // composeRuleValue() produces a full parse result from
        // the partial parse result passed to this function from
        // the caller (this is either the owning rule's base value
        // or the partial result obtained from composing the
        // owning rule's base value with its other substitution's
        // parse result) and the partial parse result obtained by
        // matching the substitution (which will be the same value
        // the caller would get by parsing just this part of the
        // text with RuleBasedNumberFormat.parse() ).  How the two
        // values are used to derive the full parse result depends
        // on the types of substitutions: For a regular rule, the
        // ultimate result is its multiplier substitution's result
        // times the rule's divisor (or the rule's base value) plus
        // the modulus substitution's result (which will actually
        // supersede part of the rule's base value).  For a negative-
        // number rule, the result is the negative of its substitution's
        // result.  For a fraction rule, it's the sum of its two
        // substitution results.  For a rule in a fraction rule set,
        // it's the numerator substitution's result divided by
        // the rule's base value.  Results from same-value substitutions
        // propagate back upard, and null substitutions don't affect
        // the result.
        tempResult = composeRuleValue(tempResult, baseValue);
        result.setDouble(tempResult);
        return TRUE;
        // if the parse was UNsuccessful, return 0
    } else {
        result.setLong(0);
        return FALSE;
    }
}
UBool
FractionalPartSubstitution::doParse(const UnicodeString& text,
                ParsePosition& parsePosition,
                double baseValue,
                double /*upperBound*/,
                UBool lenientParse,
                Formattable& resVal) const
{
    // if we're not in byDigits mode, we can just use the inherited
    // doParse()
    if (!byDigits) {
        return NFSubstitution::doParse(text, parsePosition, baseValue, 0, lenientParse, resVal);

        // if we ARE in byDigits mode, parse the text one digit at a time
        // using this substitution's owning rule set (we do this by setting
        // upperBound to 10 when calling doParse() ) until we reach
        // nonmatching text
    } else {
        UnicodeString workText(text);
        ParsePosition workPos(1);
        double result = 0;
        int32_t digit;
//          double p10 = 0.1;

        DigitList dl;
        NumberFormat* fmt = NULL;
        while (workText.length() > 0 && workPos.getIndex() != 0) {
            workPos.setIndex(0);
            Formattable temp;
            getRuleSet()->parse(workText, workPos, 10, temp);
            UErrorCode status = U_ZERO_ERROR;
            digit = temp.getLong(status);
//            digit = temp.getType() == Formattable::kLong ?
//               temp.getLong() :
//            (int32_t)temp.getDouble();

            if (lenientParse && workPos.getIndex() == 0) {
                if (!fmt) {
                    status = U_ZERO_ERROR;
                    fmt = NumberFormat::createInstance(status);
                    if (U_FAILURE(status)) {
                        delete fmt;
                        fmt = NULL;
                    }
                }
                if (fmt) {
                    fmt->parse(workText, temp, workPos);
                    digit = temp.getLong(status);
                }
            }

            if (workPos.getIndex() != 0) {
                dl.append((char)('0' + digit));
//                  result += digit * p10;
//                  p10 /= 10;
                parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex());
                workText.removeBetween(0, workPos.getIndex());
                while (workText.length() > 0 && workText.charAt(0) == gSpace) {
                    workText.removeBetween(0, 1);
                    parsePosition.setIndex(parsePosition.getIndex() + 1);
                }
            }
        }
        delete fmt;

        result = dl.fCount == 0 ? 0 : dl.getDouble();
        result = composeRuleValue(result, baseValue);
        resVal.setDouble(result);
        return TRUE;
    }
}
std::string GlobalizationNDK::stringToNumber(const std::string& args)
{
    if (args.empty()) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: no arguments provided!");
        return errorInJson(PARSING_ERROR, "No arguments provided!");
    }

    Json::Reader reader;
    Json::Value root;
    bool parse = reader.parse(args, root);

    if (!parse) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: invalid json data: %s",
                args.c_str());
        return errorInJson(PARSING_ERROR, "Invalid json data!");
    }

    Json::Value sv = root["numberString"];
    if (sv.isNull()) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: no numberString provided!");
        return errorInJson(FORMATTING_ERROR, "No numberString provided!");
    }

    if (!sv.isString()) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: invalid numberString type: %d!",
                sv.type());
        return errorInJson(FORMATTING_ERROR, "Invalid numberString type!");
    }

    std::string str = sv.asString();
    if (str.empty()) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: empty numberString!");
        return errorInJson(FORMATTING_ERROR, "Empty numberString!");
    }

    // This is the default value when no options provided.
    ENumberType type = kNumberDecimal;

    Json::Value options = root["options"];
    std::string error;
    if (!handleNumberOptions(options, type, error))
        return errorInJson(PARSING_ERROR, error);

    UErrorCode status = U_ZERO_ERROR;
    NumberFormat* nf;
    switch (type) {
    case kNumberDecimal:
    default:
        nf = NumberFormat::createInstance(status);
        break;
    case kNumberCurrency:
        nf = NumberFormat::createCurrencyInstance(status);
        break;
    case kNumberPercent:
        nf = NumberFormat::createPercentInstance(status);
        break;
    }

    if (!nf) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: failed to create NumberFormat instance for type %d: %d",
                status, type);
        return errorInJson(UNKNOWN_ERROR, "Failed to create NumberFormat instance!");
    }
    std::auto_ptr<NumberFormat> deleter(nf);

    UnicodeString uStr = UnicodeString::fromUTF8(str);
    Formattable value;

    if (type == kNumberCurrency) {
         ParsePosition pos;
         CurrencyAmount* ca = nf->parseCurrency(uStr, pos);
         if (ca)
             value = ca->getNumber();
         else
             nf->parse(uStr, value, status);
    } else
        nf->parse(uStr, value, status);

    if (status != U_ZERO_ERROR && status != U_ERROR_WARNING_START) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: failed (%d) to parse string: %s",
                status, str.c_str());
        return errorInJson(PARSING_ERROR, "Failed to parse string!");
    }

    if (!value.isNumeric()) {
        slog2f(0, ID_G11N, SLOG2_ERROR, "GlobalizationNDK::stringToNumber: string is not numeric: %s",
                str.c_str());
        return errorInJson(FORMATTING_ERROR, "String is not numeric!");
    }

    return resultInJson(value.getDouble());
}
Example #9
0
/**
 * Parse a custom time zone identifier and return a corresponding zone.
 * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
 * GMT[+-]hh.
 * @return a newly created SimpleTimeZone with the given offset and
 * no Daylight Savings Time, or null if the id cannot be parsed.
*/
TimeZone*
TimeZone::createCustomTimeZone(const UnicodeString& id)
{
    static const int32_t         kParseFailed = -99999;

    NumberFormat* numberFormat = 0;
    
    UnicodeString idUppercase = id;
    idUppercase.toUpper();

    if (id.length() > GMT_ID_LENGTH &&
        idUppercase.startsWith(GMT_ID))
    {
        ParsePosition pos(GMT_ID_LENGTH);
        UBool negative = FALSE;
        int32_t offset;

        if (id[pos.getIndex()] == 0x002D /*'-'*/)
            negative = TRUE;
        else if (id[pos.getIndex()] != 0x002B /*'+'*/)
            return 0;
        pos.setIndex(pos.getIndex() + 1);

        UErrorCode success = U_ZERO_ERROR;
        numberFormat = NumberFormat::createInstance(success);
        numberFormat->setParseIntegerOnly(TRUE);

    
        // Look for either hh:mm, hhmm, or hh
        int32_t start = pos.getIndex();
        
        Formattable n(kParseFailed);

        numberFormat->parse(id, n, pos);
        if (pos.getIndex() == start) {
            delete numberFormat;
            return 0;
        }
        offset = n.getLong();

        if (pos.getIndex() < id.length() &&
            id[pos.getIndex()] == 0x003A /*':'*/)
        {
            // hh:mm
            offset *= 60;
            pos.setIndex(pos.getIndex() + 1);
            int32_t oldPos = pos.getIndex();
            n.setLong(kParseFailed);
            numberFormat->parse(id, n, pos);
            if (pos.getIndex() == oldPos) {
                delete numberFormat;
                return 0;
            }
            offset += n.getLong();
        }
        else 
        {
            // hhmm or hh

            // Be strict about interpreting something as hh; it must be
            // an offset < 30, and it must be one or two digits. Thus
            // 0010 is interpreted as 00:10, but 10 is interpreted as
            // 10:00.
            if (offset < 30 && (pos.getIndex() - start) <= 2)
                offset *= 60; // hh, from 00 to 29; 30 is 00:30
            else
                offset = offset % 100 + offset / 100 * 60; // hhmm
        }

        if(negative)
            offset = -offset;

        delete numberFormat;
        return new SimpleTimeZone(offset * 60000, CUSTOM_ID);
    }
    return 0;
}