summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/units_router.cpp
blob: 03c9b4d1d7c416ce694d34ab3ec13086d92daa69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING

#include "charstr.h"
#include "cmemory.h"
#include "cstring.h"
#include "measunit_impl.h"
#include "number_decimalquantity.h"
#include "number_roundingutils.h"
#include "resource.h"
#include "unicode/measure.h"
#include "units_data.h"
#include "units_router.h"
#include <cmath>

U_NAMESPACE_BEGIN
namespace units {

using number::Precision;
using number::impl::parseIncrementOption;

Precision UnitsRouter::parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
                                                UErrorCode &status) {
    if (U_FAILURE(status)) {
        // As a member of UsagePrefsHandler, which is a friend of Precision, we
        // get access to the default constructor.
        return {};
    }
    constexpr int32_t kSkelPrefixLen = 20;
    if (!precisionSkeleton.startsWith(UNICODE_STRING_SIMPLE("precision-increment/"))) {
        status = U_INVALID_FORMAT_ERROR;
        return {};
    }
    U_ASSERT(precisionSkeleton[kSkelPrefixLen - 1] == u'/');
    StringSegment segment(precisionSkeleton, false);
    segment.adjustOffset(kSkelPrefixLen);
    Precision result;
    parseIncrementOption(segment, result, status);
    return result;
}

UnitsRouter::UnitsRouter(StringPiece inputUnitIdentifier, const Locale &locale, StringPiece usage,
                         UErrorCode &status) {
    this->init(MeasureUnit::forIdentifier(inputUnitIdentifier, status), locale, usage, status);
}

UnitsRouter::UnitsRouter(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
                         UErrorCode &status) {
    this->init(std::move(inputUnit), locale, usage, status);
}

void UnitsRouter::init(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
                       UErrorCode &status) {

    if (U_FAILURE(status)) {
        return;
    }

    // TODO: do we want to pass in ConversionRates and UnitPreferences instead
    // of loading in each UnitsRouter instance? (Or make global?)
    ConversionRates conversionRates(status);
    UnitPreferences prefs(status);

    MeasureUnitImpl inputUnitImpl = MeasureUnitImpl::forMeasureUnitMaybeCopy(inputUnit, status);
    MeasureUnitImpl baseUnitImpl =
        (extractCompoundBaseUnit(inputUnitImpl, conversionRates, status));
    CharString category = getUnitQuantity(baseUnitImpl, status);
    if (U_FAILURE(status)) {
        return;
    }

    const MaybeStackVector<UnitPreference> unitPrefs =
        prefs.getPreferencesFor(category.toStringPiece(), usage, locale, status);
    for (int32_t i = 0, n = unitPrefs.length(); i < n; ++i) {
        U_ASSERT(unitPrefs[i] != nullptr);
        const auto preference = unitPrefs[i];

        MeasureUnitImpl complexTargetUnitImpl =
            MeasureUnitImpl::forIdentifier(preference->unit.data(), status);
        if (U_FAILURE(status)) {
            return;
        }

        UnicodeString precision = preference->skeleton;

        // For now, we only have "precision-increment" in Units Preferences skeleton.
        // Therefore, we check if the skeleton starts with "precision-increment" and force the program to
        // fail otherwise.
        // NOTE:
        //  It is allowed to have an empty precision.
        if (!precision.isEmpty() && !precision.startsWith(u"precision-increment", 19)) {
            status = U_INTERNAL_PROGRAM_ERROR;
            return;
        }

        outputUnits_.emplaceBackAndCheckErrorCode(status,
                                                  complexTargetUnitImpl.copy(status).build(status));
        converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnitImpl, complexTargetUnitImpl,
                                                           preference->geq, std::move(precision),
                                                           conversionRates, status);

        if (U_FAILURE(status)) {
            return;
        }
    }
}

RouteResult UnitsRouter::route(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const {
    // Find the matching preference
    const ConverterPreference *converterPreference = nullptr;
    for (int32_t i = 0, n = converterPreferences_.length(); i < n; i++) {
        converterPreference = converterPreferences_[i];
        if (converterPreference->converter.greaterThanOrEqual(std::abs(quantity) * (1 + DBL_EPSILON),
                                                              converterPreference->limit)) {
            break;
        }
    }
    U_ASSERT(converterPreference != nullptr);

    // Set up the rounder for this preference's precision
    if (rounder != nullptr && rounder->fPrecision.isBogus()) {
        if (converterPreference->precision.length() > 0) {
            rounder->fPrecision = parseSkeletonToPrecision(converterPreference->precision, status);
        } else {
            // We use the same rounding mode as COMPACT notation: known to be a
            // human-friendly rounding mode: integers, but add a decimal digit
            // as needed to ensure we have at least 2 significant digits.
            rounder->fPrecision = Precision::integer().withMinDigits(2);
        }
    }

    return RouteResult(converterPreference->converter.convert(quantity, rounder, status),
                       converterPreference->targetUnit.copy(status));
}

const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
    // TODO: consider pulling this from converterPreferences_ and dropping
    // outputUnits_?
    return &outputUnits_;
}

} // namespace units
U_NAMESPACE_END

#endif /* #if !UCONFIG_NO_FORMATTING */