Coverage Report

Created: 2025-12-07 06:36

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
13.8k
void trimSpaces(CharString& factor, UErrorCode& status){
32
13.8k
   CharString trimmed;
33
138k
   for (int i = 0 ; i < factor.length(); i++) {
34
124k
       if (factor[i] == ' ') continue;
35
36
123k
       trimmed.append(factor[i], status);
37
123k
   }
38
39
13.8k
   factor = std::move(trimmed);
40
13.8k
}
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
90
    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
90
    void put(const char *source, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override {
68
90
        if (U_FAILURE(status)) { return; }
69
90
        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
90
        ResourceTable conversionRateTable = value.getTable(status);
76
90
        const char *srcUnit;
77
        // We're reusing `value`, which seems to be a common pattern:
78
14.0k
        for (int32_t unit = 0; conversionRateTable.getKeyAndValue(unit, srcUnit, value); unit++) {
79
13.9k
            ResourceTable unitTable = value.getTable(status);
80
13.9k
            const char *key;
81
13.9k
            UnicodeString baseUnit = ICU_Utility::makeBogusString();
82
13.9k
            UnicodeString factor = ICU_Utility::makeBogusString();
83
13.9k
            UnicodeString offset = ICU_Utility::makeBogusString();
84
13.9k
            UnicodeString special = ICU_Utility::makeBogusString();
85
13.9k
            UnicodeString systems = ICU_Utility::makeBogusString();
86
55.9k
            for (int32_t i = 0; unitTable.getKeyAndValue(i, key, value); i++) {
87
42.0k
                if (uprv_strcmp(key, "target") == 0) {
88
13.9k
                    baseUnit = value.getUnicodeString(status);
89
28.0k
                } else if (uprv_strcmp(key, "factor") == 0) {
90
13.8k
                    factor = value.getUnicodeString(status);
91
14.2k
                } else if (uprv_strcmp(key, "offset") == 0) {
92
180
                    offset = value.getUnicodeString(status);
93
14.0k
                } else if (uprv_strcmp(key, "special") == 0) {
94
90
                    special = value.getUnicodeString(status); // the name of a special mapping used instead of factor + optional offset.
95
13.9k
                } else if (uprv_strcmp(key, "systems") == 0) {
96
13.9k
                    systems = value.getUnicodeString(status);
97
13.9k
                }
98
42.0k
            }
99
13.9k
            if (U_FAILURE(status)) { return; }
100
13.9k
            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
13.9k
            ConversionRateInfo *cr = outVector->emplaceBack();
108
13.9k
            if (!cr) {
109
0
                status = U_MEMORY_ALLOCATION_ERROR;
110
0
                return;
111
13.9k
            } else {
112
13.9k
                cr->sourceUnit = srcUnit;
113
13.9k
                if (cr->sourceUnit.isEmpty() != (*srcUnit == '\0')) {
114
0
                    status = U_MEMORY_ALLOCATION_ERROR;
115
0
                }
116
13.9k
                copyInvariantChars(baseUnit, cr->baseUnit, status);
117
13.9k
                if (U_SUCCESS(status) && !factor.isBogus()) {
118
13.8k
                    CharString tmp;
119
13.8k
                    tmp.appendInvariantChars(factor, status);
120
13.8k
                    trimSpaces(tmp, status);
121
13.8k
                    if (U_SUCCESS(status)) {
122
13.8k
                        cr->factor = tmp.toStringPiece();
123
13.8k
                        if (cr->factor.isEmpty() != tmp.isEmpty()) {
124
0
                            status = U_MEMORY_ALLOCATION_ERROR;
125
0
                        }
126
13.8k
                    }
127
13.8k
                }
128
13.9k
                if (!offset.isBogus()) { copyInvariantChars(offset, cr->offset, status); }
129
13.9k
                if (!special.isBogus()) { copyInvariantChars(special, cr->specialMappingName, status); }
130
13.9k
                copyInvariantChars(systems, cr->systems, status);
131
13.9k
            }
132
13.9k
        }
133
90
    }
134
135
  private:
136
    MaybeStackVector<ConversionRateInfo> *outVector;
137
};
138
139
19.1k
bool operator<(const UnitPreferenceMetadata &a, const UnitPreferenceMetadata &b) {
140
19.1k
    return a.compareTo(b) < 0;
141
19.1k
}
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
90
        : 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
90
    void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) override {
174
90
        if (U_FAILURE(status)) { return; }
175
90
        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
90
        ResourceTable unitPreferenceDataTable = value.getTable(status);
186
90
        const char *category;
187
1.35k
        for (int32_t i = 0; unitPreferenceDataTable.getKeyAndValue(i, category, value); i++) {
188
1.26k
            ResourceTable categoryTable = value.getTable(status);
189
1.26k
            const char *usage;
190
4.86k
            for (int32_t j = 0; categoryTable.getKeyAndValue(j, usage, value); j++) {
191
3.60k
                ResourceTable regionTable = value.getTable(status);
192
3.60k
                const char *region;
193
22.8k
                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
19.2k
                    ResourceArray unitPrefs = value.getArray(status);
197
19.2k
                    if (U_FAILURE(status)) { return; }
198
19.2k
                    int32_t prefLen = unitPrefs.getSize();
199
200
                    // Update metadata for this set of preferences.
201
19.2k
                    UnitPreferenceMetadata *meta = metadata->emplaceBack(
202
19.2k
                        category, usage, region, preferences->length(), prefLen, status);
203
19.2k
                    if (!meta) {
204
0
                        status = U_MEMORY_ALLOCATION_ERROR;
205
0
                        return;
206
0
                    }
207
19.2k
                    if (U_FAILURE(status)) { return; }
208
19.2k
                    if (metadata->length() > 1) {
209
                        // Verify that unit preferences are sorted and
210
                        // without duplicates.
211
19.1k
                        if (!(*(*metadata)[metadata->length() - 2] <
212
19.1k
                              *(*metadata)[metadata->length() - 1])) {
213
0
                            status = U_INVALID_FORMAT_ERROR;
214
0
                            return;
215
0
                        }
216
19.1k
                    }
217
218
                    // Collect the individual preferences.
219
45.0k
                    for (int32_t i = 0; unitPrefs.getValue(i, value); i++) {
220
25.7k
                        UnitPreference *up = preferences->emplaceBack();
221
25.7k
                        if (!up) {
222
0
                            status = U_MEMORY_ALLOCATION_ERROR;
223
0
                            return;
224
0
                        }
225
25.7k
                        ResourceTable unitPref = value.getTable(status);
226
25.7k
                        if (U_FAILURE(status)) { return; }
227
54.0k
                        for (int32_t i = 0; unitPref.getKeyAndValue(i, key, value); ++i) {
228
28.3k
                            if (uprv_strcmp(key, "unit") == 0) {
229
25.7k
                                copyInvariantChars(value.getUnicodeString(status), up->unit, status);
230
25.7k
                            } else if (uprv_strcmp(key, "geq") == 0) {
231
1.53k
                                int32_t length;
232
1.53k
                                const char16_t *g = value.getString(length, status);
233
1.53k
                                CharString geq;
234
1.53k
                                geq.appendInvariantChars(g, length, status);
235
1.53k
                                DecimalQuantity dq;
236
1.53k
                                dq.setToDecNumber(geq.data(), status);
237
1.53k
                                up->geq = dq.toDouble();
238
1.53k
                            } else if (uprv_strcmp(key, "skeleton") == 0) {
239
1.08k
                                up->skeleton = value.getUnicodeString(status);
240
1.08k
                            }
241
28.3k
                        }
242
25.7k
                    }
243
19.2k
                }
244
3.60k
            }
245
1.26k
        }
246
90
    }
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
90
                     bool *foundRegion, UErrorCode &status) {
256
90
    if (U_FAILURE(status)) { return -1; }
257
90
    int32_t start = 0;
258
90
    int32_t end = metadata->length();
259
90
    *foundCategory = false;
260
90
    *foundUsage = false;
261
90
    *foundRegion = false;
262
720
    while (start < end) {
263
630
        int32_t mid = (start + end) / 2;
264
630
        int32_t cmp = (*metadata)[mid]->compareTo(desired, foundCategory, foundUsage, foundRegion);
265
630
        if (cmp < 0) {
266
450
            start = mid + 1;
267
450
        } else if (cmp > 0) {
268
180
            end = mid;
269
180
        } else {
270
0
            return mid;
271
0
        }
272
630
    }
273
90
    return -1;
274
90
}
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
90
                                   UErrorCode &status) {
300
90
    if (U_FAILURE(status)) { return -1; }
301
90
    bool foundCategory, foundUsage, foundRegion;
302
90
    UnitPreferenceMetadata desired(category, usage, region, -1, -1, status);
303
90
    int32_t idx = binarySearch(metadata, desired, &foundCategory, &foundUsage, &foundRegion, status);
304
90
    if (U_FAILURE(status)) { return -1; }
305
90
    if (idx >= 0) { return idx; }
306
90
    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
90
        status = U_ILLEGAL_ARGUMENT_ERROR;
312
90
        return -1;
313
90
    }
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
19.3k
                                               int32_t prefsCount, UErrorCode &status) {
354
19.3k
    this->category.append(category, status);
355
19.3k
    this->usage.append(usage, status);
356
19.3k
    this->region.append(region, status);
357
19.3k
    this->prefsOffset = prefsOffset;
358
19.3k
    this->prefsCount = prefsCount;
359
19.3k
}
360
361
19.1k
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other) const {
362
19.1k
    int32_t cmp = uprv_strcmp(category.data(), other.category.data());
363
19.1k
    if (cmp == 0) {
364
18.0k
        cmp = uprv_strcmp(usage.data(), other.usage.data());
365
18.0k
    }
366
19.1k
    if (cmp == 0) {
367
15.6k
        cmp = uprv_strcmp(region.data(), other.region.data());
368
15.6k
    }
369
19.1k
    return cmp;
370
19.1k
}
371
372
int32_t UnitPreferenceMetadata::compareTo(const UnitPreferenceMetadata &other, bool *foundCategory,
373
630
                                          bool *foundUsage, bool *foundRegion) const {
374
630
    int32_t cmp = uprv_strcmp(category.data(), other.category.data());
375
630
    if (cmp == 0) {
376
0
        *foundCategory = true;
377
0
        cmp = uprv_strcmp(usage.data(), other.usage.data());
378
0
    }
379
630
    if (cmp == 0) {
380
0
        *foundUsage = true;
381
0
        cmp = uprv_strcmp(region.data(), other.region.data());
382
0
    }
383
630
    if (cmp == 0) {
384
0
        *foundRegion = true;
385
0
    }
386
630
    return cmp;
387
630
}
388
389
// TODO: this may be unnecessary. Fold into ConversionRates class? Or move to anonymous namespace?
390
90
void U_I18N_API getAllConversionRates(MaybeStackVector<ConversionRateInfo> &result, UErrorCode &status) {
391
90
    LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status));
392
90
    ConversionRateDataSink sink(&result);
393
90
    ures_getAllItemsWithFallback(unitsBundle.getAlias(), "convertUnits", sink, status);
394
90
}
395
396
const ConversionRateInfo *ConversionRates::extractConversionInfo(StringPiece source,
397
90
                                                                 UErrorCode &status) const {
398
9.54k
    for (size_t i = 0, n = conversionInfo_.length(); i < n; ++i) {
399
9.54k
        if (uprv_strncmp(conversionInfo_[i]->sourceUnit.data(), source.data(), source.size()) == 0) {
400
90
            return conversionInfo_[i];
401
90
        }
402
9.54k
    }
403
404
0
    status = U_INTERNAL_PROGRAM_ERROR;
405
0
    return nullptr;
406
90
}
407
408
90
UnitPreferences::UnitPreferences(UErrorCode& status) {
409
90
    LocalUResourceBundlePointer unitsBundle(ures_openDirect(nullptr, "units", &status));
410
90
    UnitPreferencesSink sink(&unitPrefs_, &metadata_);
411
90
    ures_getAllItemsWithFallback(unitsBundle.getAlias(), "unitPreferenceData", sink, status);
412
90
}
413
414
90
CharString getKeyWordValue(const Locale &locale, StringPiece kw, UErrorCode &status) {
415
90
    if (U_FAILURE(status)) { return {}; }
416
90
    auto result = locale.getKeywordValue<CharString>(kw, status);
417
90
    if (U_SUCCESS(status) && result.isEmpty()) {
418
90
        status = U_MISSING_RESOURCE_ERROR;
419
90
    }
420
90
    return result;
421
90
}
422
423
MaybeStackVector<UnitPreference> UnitPreferences::getPreferencesFor(StringPiece category,
424
                                                                    StringPiece usage,
425
                                                                    const Locale& locale,
426
90
                                                                    UErrorCode& status) const {
427
90
    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
90
    UErrorCode internalMuStatus = U_ZERO_ERROR;
432
90
    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
90
    CharString region = ulocimp_getRegionForSupplementalData(locale.getName(), true, status);
457
458
    // Check the locale system tag, e.g `ms=metric`.
459
90
    UErrorCode internalMeasureTagStatus = U_ZERO_ERROR;
460
90
    CharString localeSystem = getKeyWordValue(locale, "measure", internalMeasureTagStatus);
461
90
    bool isLocaleSystem = false;
462
90
    if (U_SUCCESS(internalMeasureTagStatus) && (localeSystem == "metric" || localeSystem == "ussystem" || localeSystem == "uksystem")) {
463
0
        isLocaleSystem = true;
464
0
    }
465
466
90
    int32_t idx =
467
90
        getPreferenceMetadataIndex(&metadata_, category, usage, region.toStringPiece(), status);
468
90
    if (U_FAILURE(status)) {
469
90
        return result;
470
90
    }
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 */