Coverage Report

Created: 2026-05-06 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/units_data.cpp
Line
Count
Source
1
// © 2020 and later: Unicode, Inc. and others.
2
// License & terms of use: http://www.unicode.org/copyright.html
3
4
#include "unicode/utypes.h"
5
6
#if !UCONFIG_NO_FORMATTING
7
8
#include "bytesinkutil.h"
9
#include "charstr.h"
10
#include "cstring.h"
11
#include "measunit_impl.h"
12
#include "number_decimalquantity.h"
13
#include "resource.h"
14
#include "uassert.h"
15
#include "ulocimp.h"
16
#include "unicode/locid.h"
17
#include "unicode/unistr.h"
18
#include "unicode/ures.h"
19
#include "units_data.h"
20
#include "uresimp.h"
21
#include "util.h"
22
#include <utility>
23
24
U_NAMESPACE_BEGIN
25
namespace units {
26
27
namespace {
28
29
using icu::number::impl::DecimalQuantity;
30
31
15.4k
void trimSpaces(CharString& factor, UErrorCode& status){
32
15.4k
   CharString trimmed;
33
153k
   for (int i = 0 ; i < factor.length(); i++) {
34
138k
       if (factor[i] == ' ') continue;
35
36
136k
       trimmed.append(factor[i], status);
37
136k
   }
38
39
15.4k
   factor = std::move(trimmed);
40
15.4k
}
41
42
/**
43
 * A ResourceSink that collects conversion rate information.
44
 *
45
 * This class is for use by ures_getAllItemsWithFallback.
46
 */
47
class ConversionRateDataSink : public ResourceSink {
48
  public:
49
    /**
50
     * Constructor.
51
     * @param out The vector to which ConversionRateInfo instances are to be
52
     * added. This vector must outlive the use of the ResourceSink.
53
     */
54
100
    explicit ConversionRateDataSink(MaybeStackVector<ConversionRateInfo> *out) : outVector(out) {}
55
56
    /**
57
     * Method for use by `ures_getAllItemsWithFallback`. Adds the unit
58
     * conversion rates that are found in `value` to the output vector.
59
     *
60
     * @param source This string must be "convertUnits": the resource that this
61
     * class supports reading.
62
     * @param value The "convertUnits" resource, containing unit conversion rate
63
     * information.
64
     * @param noFallback Ignored.
65
     * @param status The standard ICU error code output parameter.
66
     */
67
100
    void put(const char *source, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override {
68
100
        if (U_FAILURE(status)) { return; }
69
100
        if (uprv_strcmp(source, "convertUnits") != 0) {
70
            // This is very strict, however it is the cheapest way to be sure
71
            // that with `value`, we're looking at the convertUnits table.
72
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
73
0
            return;
74
0
        }
75
100
        ResourceTable conversionRateTable = value.getTable(status);
76
100
        const char *srcUnit;
77
        // We're reusing `value`, which seems to be a common pattern:
78
15.6k
        for (int32_t unit = 0; conversionRateTable.getKeyAndValue(unit, srcUnit, value); unit++) {
79
15.5k
            ResourceTable unitTable = value.getTable(status);
80
15.5k
            const char *key;
81
15.5k
            UnicodeString baseUnit = ICU_Utility::makeBogusString();
82
15.5k
            UnicodeString factor = ICU_Utility::makeBogusString();
83
15.5k
            UnicodeString offset = ICU_Utility::makeBogusString();
84
15.5k
            UnicodeString special = ICU_Utility::makeBogusString();
85
15.5k
            UnicodeString systems = ICU_Utility::makeBogusString();
86
62.2k
            for (int32_t i = 0; unitTable.getKeyAndValue(i, key, value); i++) {
87
46.7k
                if (uprv_strcmp(key, "target") == 0) {
88
15.5k
                    baseUnit = value.getUnicodeString(status);
89
31.2k
                } else if (uprv_strcmp(key, "factor") == 0) {
90
15.4k
                    factor = value.getUnicodeString(status);
91
15.8k
                } else if (uprv_strcmp(key, "offset") == 0) {
92
200
                    offset = value.getUnicodeString(status);
93
15.6k
                } else if (uprv_strcmp(key, "special") == 0) {
94
100
                    special = value.getUnicodeString(status); // the name of a special mapping used instead of factor + optional offset.
95
15.5k
                } else if (uprv_strcmp(key, "systems") == 0) {
96
15.5k
                    systems = value.getUnicodeString(status);
97
15.5k
                }
98
46.7k
            }
99
15.5k
            if (U_FAILURE(status)) { return; }
100
15.5k
            if (baseUnit.isBogus() || (factor.isBogus() && special.isBogus())) {
101
                // We could not find a usable conversion rate: bad resource.
102
0
                status = U_MISSING_RESOURCE_ERROR;
103
0
                return;
104
0
            }
105
106
            // We don't have this ConversionRateInfo yet: add it.
107
15.5k
            ConversionRateInfo *cr = outVector->emplaceBack();
108
15.5k
            if (!cr) {
109
0
                status = U_MEMORY_ALLOCATION_ERROR;
110
0
                return;
111
15.5k
            } else {
112
15.5k
                cr->sourceUnit = srcUnit;
113
15.5k
                if (cr->sourceUnit.isEmpty() != (*srcUnit == '\0')) {
114
0
                    status = U_MEMORY_ALLOCATION_ERROR;
115
0
                }
116
15.5k
                copyInvariantChars(baseUnit, cr->baseUnit, status);
117
15.5k
                if (U_SUCCESS(status) && !factor.isBogus()) {
118
15.4k
                    CharString tmp;
119
15.4k
                    tmp.appendInvariantChars(factor, status);
120
15.4k
                    trimSpaces(tmp, status);
121
15.4k
                    if (U_SUCCESS(status)) {
122
15.4k
                        cr->factor = tmp.toStringPiece();
123
15.4k
                        if (cr->factor.isEmpty() != tmp.isEmpty()) {
124
0
                            status = U_MEMORY_ALLOCATION_ERROR;
125
0
                        }
126
15.4k
                    }
127
15.4k
                }
128
15.5k
                if (!offset.isBogus()) { copyInvariantChars(offset, cr->offset, status); }
129
15.5k
                if (!special.isBogus()) { copyInvariantChars(special, cr->specialMappingName, status); }
130
15.5k
                copyInvariantChars(systems, cr->systems, status);
131
15.5k
            }
132
15.5k
        }
133
100
    }
134
135
  private:
136
    MaybeStackVector<ConversionRateInfo> *outVector;
137
};
138
139
21.3k
bool operator<(const UnitPreferenceMetadata &a, const UnitPreferenceMetadata &b) {
140
21.3k
    return a.compareTo(b) < 0;
141
21.3k
}
142
143
/**
144
 * A ResourceSink that collects unit preferences information.
145
 *
146
 * This class is for use by ures_getAllItemsWithFallback.
147
 */
148
class UnitPreferencesSink : public ResourceSink {
149
  public:
150
    /**
151
     * Constructor.
152
     * @param outPrefs The vector to which UnitPreference instances are to be
153
     * added. This vector must outlive the use of the ResourceSink.
154
     * @param outMetadata  The vector to which UnitPreferenceMetadata instances
155
     * are to be added. This vector must outlive the use of the ResourceSink.
156
     */
157
    explicit UnitPreferencesSink(MaybeStackVector<UnitPreference> *outPrefs,
158
                                 MaybeStackVector<UnitPreferenceMetadata> *outMetadata)
159
100
        : preferences(outPrefs), metadata(outMetadata) {}
160
161
    /**
162
     * Method for use by `ures_getAllItemsWithFallback`. Adds the unit
163
     * preferences info that are found in `value` to the output vector.
164
     *
165
     * @param source This string must be "unitPreferenceData": the resource that
166
     * this class supports reading.
167
     * @param value The "unitPreferenceData" resource, containing unit
168
     * preferences data.
169
     * @param noFallback Ignored.
170
     * @param status The standard ICU error code output parameter. Note: if an
171
     * error is returned, outPrefs and outMetadata may be inconsistent.
172
     */
173
100
    void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override {
174
100
        if (U_FAILURE(status)) { return; }
175
100
        if (uprv_strcmp(key, "unitPreferenceData") != 0) {
176
            // This is very strict, however it is the cheapest way to be sure
177
            // that with `value`, we're looking at the convertUnits table.
178
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
179
0
            return;
180
0
        }
181
        // The unitPreferenceData structure (see data/misc/units.txt) contains a
182
        // hierarchy of category/usage/region, within which are a set of
183
        // preferences. Hence three for-loops and another loop for the
184
        // preferences themselves:
185
100
        ResourceTable unitPreferenceDataTable = value.getTable(status);
186
100
        const char *category;
187
1.50k
        for (int32_t i = 0; unitPreferenceDataTable.getKeyAndValue(i, category, value); i++) {
188
1.40k
            ResourceTable categoryTable = value.getTable(status);
189
1.40k
            const char *usage;
190
5.40k
            for (int32_t j = 0; categoryTable.getKeyAndValue(j, usage, value); j++) {
191
4.00k
                ResourceTable regionTable = value.getTable(status);
192
4.00k
                const char *region;
193
25.4k
                for (int32_t k = 0; regionTable.getKeyAndValue(k, region, value); k++) {
194
                    // `value` now contains the set of preferences for
195
                    // category/usage/region.
196
21.4k
                    ResourceArray unitPrefs = value.getArray(status);
197
21.4k
                    if (U_FAILURE(status)) { return; }
198
21.4k
                    int32_t prefLen = unitPrefs.getSize();
199
200
                    // Update metadata for this set of preferences.
201
21.4k
                    UnitPreferenceMetadata *meta = metadata->emplaceBack(
202
21.4k
                        category, usage, region, preferences->length(), prefLen, status);
203
21.4k
                    if (!meta) {
204
0
                        status = U_MEMORY_ALLOCATION_ERROR;
205
0
                        return;
206
0
                    }
207
21.4k
                    if (U_FAILURE(status)) { return; }
208
21.4k
                    if (metadata->length() > 1) {
209
                        // Verify that unit preferences are sorted and
210
                        // without duplicates.
211
21.3k
                        if (!(*(*metadata)[metadata->length() - 2] <
212
21.3k
                              *(*metadata)[metadata->length() - 1])) {
213
0
                            status = U_INVALID_FORMAT_ERROR;
214
0
                            return;
215
0
                        }
216
21.3k
                    }
217
218
                    // Collect the individual preferences.
219
50.0k
                    for (int32_t i = 0; unitPrefs.getValue(i, value); i++) {
220
28.6k
                        UnitPreference *up = preferences->emplaceBack();
221
28.6k
                        if (!up) {
222
0
                            status = U_MEMORY_ALLOCATION_ERROR;
223
0
                            return;
224
0
                        }
225
28.6k
                        ResourceTable unitPref = value.getTable(status);
226
28.6k
                        if (U_FAILURE(status)) { return; }
227
60.1k
                        for (int32_t i = 0; unitPref.getKeyAndValue(i, key, value); ++i) {
228
31.5k
                            if (uprv_strcmp(key, "unit") == 0) {
229
28.6k
                                copyInvariantChars(value.getUnicodeString(status), up->unit, status);
230
28.6k
                            } else if (uprv_strcmp(key, "geq") == 0) {
231
1.70k
                                int32_t length;
232
1.70k
                                const char16_t *g = value.getString(length, status);
233
1.70k
                                CharString geq;
234
1.70k
                                geq.appendInvariantChars(g, length, status);
235
1.70k
                                DecimalQuantity dq;
236
1.70k
                                dq.setToDecNumber(geq.data(), status);
237
1.70k
                                up->geq = dq.toDouble();
238
1.70k
                            } else if (uprv_strcmp(key, "skeleton") == 0) {
239
1.20k
                                up->skeleton = value.getUnicodeString(status);
240
1.20k
                            }
241
31.5k
                        }
242
28.6k
                    }
243
21.4k
                }
244
4.00k
            }
245
1.40k
        }
246
100
    }
247
248
  private:
249
    MaybeStackVector<UnitPreference> *preferences;
250
    MaybeStackVector<UnitPreferenceMetadata> *metadata;
251
};
252
253
int32_t binarySearch(const MaybeStackVector<UnitPreferenceMetadata> *metadata,
254
                     const UnitPreferenceMetadata &desired, bool *foundCategory, bool *foundUsage,
255
100
                     bool *foundRegion, UErrorCode &status) {
256
100
    if (U_FAILURE(status)) { return -1; }
257
100
    int32_t start = 0;
258
100
    int32_t end = metadata->length();
259
100
    *foundCategory = false;
260
100
    *foundUsage = false;
261
100
    *foundRegion = false;
262
800
    while (start < end) {
263
700
        int32_t mid = (start + end) / 2;
264
700
        int32_t cmp = (*metadata)[mid]->compareTo(desired, foundCategory, foundUsage, foundRegion);
265
700
        if (cmp < 0) {
266
500
            start = mid + 1;
267
500
        } else if (cmp > 0) {
268
200
            end = mid;
269
200
        } else {
270
0
            return mid;
271
0
        }
272
700
    }
273
100
    return -1;
274
100
}
275
276
/**
277
 * Finds the UnitPreferenceMetadata instance that matches the given category,
278
 * usage and region: if missing, region falls back to "001", and usage
279
 * repeatedly drops tailing components, eventually trying "default"
280
 * ("land-agriculture-grain" -> "land-agriculture" -> "land" -> "default").
281
 *
282
 * @param metadata The full list of UnitPreferenceMetadata instances.
283
 * @param category The category to search for. See getUnitCategory().
284
 * @param usage The usage for which formatting preferences is needed. If the
285
 * given usage is not known, automatic fallback occurs, see function description
286
 * above.
287
 * @param region The region for which preferences are needed. If there are no
288
 * region-specific preferences, this function automatically falls back to the
289
 * "001" region (global).
290
 * @param status The standard ICU error code output parameter.
291
 *   * If an invalid category is given, status will be U_ILLEGAL_ARGUMENT_ERROR.
292
 *   * If fallback to "default" or "001" didn't resolve, status will be
293
 *     U_MISSING_RESOURCE.
294
 * @return The index into the metadata vector which represents the appropriate
295
 * preferences. If appropriate preferences are not found, -1 is returned.
296
 */
297
int32_t getPreferenceMetadataIndex(const MaybeStackVector<UnitPreferenceMetadata> *metadata,
298
                                   StringPiece category, StringPiece usage, StringPiece region,
299
100
                                   UErrorCode &status) {
300
100
    if (U_FAILURE(status)) { return -1; }
301
100
    bool foundCategory, foundUsage, foundRegion;
302
100
    UnitPreferenceMetadata desired(category, usage, region, -1, -1, status);
303
100
    int32_t idx = binarySearch(metadata, desired, &foundCategory, &foundUsage, &foundRegion, status);
304
100
    if (U_FAILURE(status)) { return -1; }
305
100
    if (idx >= 0) { return idx; }
306
100
    if (!foundCategory) {
307
        // TODO: failures can happen if units::getUnitCategory returns a category
308
        // that does not appear in unitPreferenceData. Do we want a unit test that
309
        // checks unitPreferenceData has full coverage of categories? Or just trust
310
        // CLDR?
311
100
        status = U_ILLEGAL_ARGUMENT_ERROR;
312
100
        return -1;
313
100
    }
314
0
    U_ASSERT(foundCategory);
315
0
    while (!foundUsage) {
316
0
        int32_t lastDashIdx = desired.usage.lastIndexOf('-');
317
0
        if (lastDashIdx > 0) {
318
0
            desired.usage.truncate(lastDashIdx);
319
0
        } else if (uprv_strcmp(desired.usage.data(), "default") != 0) {
320
0
            desired.usage.truncate(0).append("default", status);
321
0
        } else {
322
            // "default" is not supposed to be missing for any valid category.
323
0
            status = U_MISSING_RESOURCE_ERROR;
324
0
            return -1;
325
0
        }
326
0
        idx = binarySearch(metadata, desired, &foundCategory, &foundUsage, &foundRegion, status);
327
0
        if (U_FAILURE(status)) { return -1; }
328
0
    }
329
0
    U_ASSERT(foundCategory);
330
0
    U_ASSERT(foundUsage);
331
0
    if (!foundRegion) {
332
0
        if (uprv_strcmp(desired.region.data(), "001") != 0) {
333
0
            desired.region.truncate(0).append("001", status);
334
0
            idx = binarySearch(metadata, desired, &foundCategory, &foundUsage, &foundRegion, status);
335
0
        }
336
0
        if (!foundRegion) {
337
            // "001" is not supposed to be missing for any valid usage.
338
0
            status = U_MISSING_RESOURCE_ERROR;
339
0
            return -1;
340
0
        }
341
0
    }
342
0
    U_ASSERT(foundCategory);
343
0
    U_ASSERT(foundUsage);
344
0
    U_ASSERT(foundRegion);
345
0
    U_ASSERT(idx >= 0);
346
0
    return idx;
347
0
}
348
349
} // namespace
350
351
UnitPreferenceMetadata::UnitPreferenceMetadata(StringPiece category, StringPiece usage,
352
                                               StringPiece region, int32_t prefsOffset,
353
21.5k
                                               int32_t prefsCount, UErrorCode &status) {
354
21.5k
    this->category.append(category, status);
355
21.5k
    this->usage.append(usage, status);
356
21.5k
    this->region.append(region, status);
357
21.5k
    this->prefsOffset = prefsOffset;
358
21.5k
    this->prefsCount = prefsCount;
359
21.5k
}
360
361
21.3k
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other) const {
362
21.3k
    int32_t cmp = uprv_strcmp(category.data(), other.category.data());
363
21.3k
    if (cmp == 0) {
364
20.0k
        cmp = uprv_strcmp(usage.data(), other.usage.data());
365
20.0k
    }
366
21.3k
    if (cmp == 0) {
367
17.4k
        cmp = uprv_strcmp(region.data(), other.region.data());
368
17.4k
    }
369
21.3k
    return cmp;
370
21.3k
}
371
372
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other, bool *foundCategory,
373
700
                                          bool *foundUsage, bool *foundRegion) const {
374
700
    int32_t cmp = uprv_strcmp(category.data(), other.category.data());
375
700
    if (cmp == 0) {
376
0
        *foundCategory = true;
377
0
        cmp = uprv_strcmp(usage.data(), other.usage.data());
378
0
    }
379
700
    if (cmp == 0) {
380
0
        *foundUsage = true;
381
0
        cmp = uprv_strcmp(region.data(), other.region.data());
382
0
    }
383
700
    if (cmp == 0) {
384
0
        *foundRegion = true;
385
0
    }
386
700
    return cmp;
387
700
}
388
389
// TODO: this may be unnecessary. Fold into ConversionRates class? Or move to anonymous namespace?
390
100
void U_I18N_API getAllConversionRates(MaybeStackVector<ConversionRateInfo> &result, UErrorCode &status) {
391
100
    LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status));
392
100
    ConversionRateDataSink sink(&result);
393
100
    ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", sink, status);
394
100
}
395
396
const ConversionRateInfo *ConversionRates::extractConversionInfo(StringPiece source,
397
100
                                                                 UErrorCode &status) const {
398
10.6k
    for (size_t i = 0, n = conversionInfo_.length(); i < n; ++i) {
399
10.6k
        if (uprv_strncmp(conversionInfo_[i]->sourceUnit.data(), source.data(), source.size()) == 0) {
400
100
            return conversionInfo_[i];
401
100
        }
402
10.6k
    }
403
404
0
    status = U_INTERNAL_PROGRAM_ERROR;
405
0
    return nullptr;
406
100
}
407
408
100
UnitPreferences::UnitPreferences(UErrorCode& status) {
409
100
    LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status));
410
100
    UnitPreferencesSink sink(&unitPrefs_, &metadata_);
411
100
    ures_getAllItemsWithFallback(unitsBundle.getAlias(), "unitPreferenceData", sink, status);
412
100
}
413
414
100
CharString getKeyWordValue(const Locale &locale, StringPiece kw, UErrorCode &status) {
415
100
    if (U_FAILURE(status)) { return {}; }
416
100
    auto result = locale.getKeywordValue<CharString>(kw, status);
417
100
    if (U_SUCCESS(status) && result.isEmpty()) {
418
100
        status = U_MISSING_RESOURCE_ERROR;
419
100
    }
420
100
    return result;
421
100
}
422
423
MaybeStackVector<UnitPreference> UnitPreferences::getPreferencesFor(StringPiece category,
424
                                                                    StringPiece usage,
425
                                                                    const Locale& locale,
426
100
                                                                    UErrorCode& status) const {
427
100
    MaybeStackVector<UnitPreference> result;
428
429
    // TODO: remove this once all the categories are allowed.
430
    // WARNING: when this is removed please make sure to keep the "fahrenhe" => "fahrenheit" mapping
431
100
    UErrorCode internalMuStatus = U_ZERO_ERROR;
432
100
    if (category.compare("temperature") == 0) {
433
0
        CharString localeUnitCharString = getKeyWordValue(locale, "mu", internalMuStatus);
434
0
        if (U_SUCCESS(internalMuStatus)) {
435
            // The value for -u-mu- is `fahrenhe`, but CLDR and everything else uses `fahrenheit`
436
0
            if (localeUnitCharString == "fahrenhe") {
437
0
                localeUnitCharString = CharString("fahrenheit", status);
438
0
            }
439
            // TODO: use the unit category as Java especially when all the categories are allowed..
440
0
            if (localeUnitCharString == "celsius"
441
0
                || localeUnitCharString == "fahrenheit"
442
0
                || localeUnitCharString == "kelvin"
443
0
            ) {
444
0
                UnitPreference unitPref;
445
0
                unitPref.unit = localeUnitCharString.toStringPiece();
446
0
                if (unitPref.unit.isEmpty() != localeUnitCharString.isEmpty()) {
447
0
                    status = U_MISSING_RESOURCE_ERROR;
448
0
                    return result;
449
0
                }
450
0
                result.emplaceBackAndCheckErrorCode(status, unitPref);
451
0
                return result;
452
0
            }
453
0
        }
454
0
    }
455
456
100
    CharString region = ulocimp_getRegionForSupplementalData(locale.getName(), true, status);
457
458
    // Check the locale system tag, e.g `ms=metric`.
459
100
    UErrorCode internalMeasureTagStatus = U_ZERO_ERROR;
460
100
    CharString localeSystem = getKeyWordValue(locale, "measure", internalMeasureTagStatus);
461
100
    bool isLocaleSystem = false;
462
100
    if (U_SUCCESS(internalMeasureTagStatus) && (localeSystem == "metric" || localeSystem == "ussystem" || localeSystem == "uksystem")) {
463
0
        isLocaleSystem = true;
464
0
    }
465
466
100
    int32_t idx =
467
100
        getPreferenceMetadataIndex(&metadata_, category, usage, region.toStringPiece(), status);
468
100
    if (U_FAILURE(status)) {
469
100
        return result;
470
100
    }
471
472
0
    U_ASSERT(idx >= 0); // Failures should have been taken care of by `status`.
473
0
    const UnitPreferenceMetadata *m = metadata_[idx];
474
        
475
0
    if (isLocaleSystem) {
476
        // if the locale ID specifies a measurment system, check if ALL of the units we got back
477
        // are members of that system (or are "metric_adjacent", which we consider to match all
478
        // the systems)
479
0
        bool unitsMatchSystem = true;
480
0
        ConversionRates rates(status);
481
0
        for (int32_t i = 0; unitsMatchSystem && i < m->prefsCount; i++) {
482
0
            const UnitPreference& unitPref = *(unitPrefs_[i + m->prefsOffset]);
483
0
            MeasureUnitImpl measureUnit = MeasureUnitImpl::forIdentifier(unitPref.unit.data(), status);
484
0
            for (int32_t j = 0; unitsMatchSystem && j < measureUnit.singleUnits.length(); j++) {
485
0
                const SingleUnitImpl* singleUnit = measureUnit.singleUnits[j];
486
0
                const ConversionRateInfo* rateInfo = rates.extractConversionInfo(singleUnit->getSimpleUnitID(), status);
487
0
                const char* systems = rateInfo->systems.data();
488
                // "metric-adjacent" is considered to match all the locale systems
489
0
                if (uprv_strstr(systems, "metric_adjacent") == nullptr) {
490
0
                    if (uprv_strstr(systems, localeSystem.data()) == nullptr) {
491
0
                        unitsMatchSystem = false;
492
0
                    }
493
0
                }
494
0
            }
495
0
        }
496
        
497
        // if any of the units we got back above don't match the mearurement system the locale ID asked for,
498
        // throw out the region and just load the units for the base region for the requested measurement system
499
0
        if (!unitsMatchSystem) {
500
0
            region.clear();
501
0
            if (localeSystem == "ussystem") {
502
0
                region.append("US", status);
503
0
            } else if (localeSystem == "uksystem") {
504
0
                region.append("GB", status);
505
0
            } else {
506
0
                region.append("001", status);
507
0
            }
508
0
            idx = getPreferenceMetadataIndex(&metadata_, category, usage, region.toStringPiece(), status);
509
0
            if (U_FAILURE(status)) {
510
0
                return result;
511
0
            }
512
            
513
0
            m = metadata_[idx];
514
0
        }
515
0
    }
516
        
517
0
    for (int32_t i = 0; i < m->prefsCount; i++) {
518
0
        result.emplaceBackAndCheckErrorCode(status, *(unitPrefs_[i + m->prefsOffset]));
519
0
    }
520
0
    return result;
521
0
}
522
523
} // namespace units
524
U_NAMESPACE_END
525
526
#endif /* #if !UCONFIG_NO_FORMATTING */