summaryrefslogtreecommitdiffstats
path: root/intl/components/src/NumberRangeFormat.h
diff options
context:
space:
mode:
Diffstat (limited to 'intl/components/src/NumberRangeFormat.h')
-rw-r--r--intl/components/src/NumberRangeFormat.h237
1 files changed, 237 insertions, 0 deletions
diff --git a/intl/components/src/NumberRangeFormat.h b/intl/components/src/NumberRangeFormat.h
new file mode 100644
index 0000000000..40bb85d6d2
--- /dev/null
+++ b/intl/components/src/NumberRangeFormat.h
@@ -0,0 +1,237 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef intl_components_NumberRangeFormat_h_
+#define intl_components_NumberRangeFormat_h_
+
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/intl/ICUError.h"
+#include "mozilla/intl/NumberFormat.h"
+#include "mozilla/Result.h"
+#include "mozilla/UniquePtr.h"
+
+#include <stdint.h>
+#include <string_view>
+
+#include "unicode/utypes.h"
+
+struct UFormattedNumberRange;
+struct UNumberRangeFormatter;
+struct UPluralRules;
+
+namespace mozilla::intl {
+
+/**
+ * NumberRangeFormatOptions supports the same set of options as
+ * NumberFormatOptions and additionally allows to control how to display ranges.
+ */
+struct MOZ_STACK_CLASS NumberRangeFormatOptions : public NumberFormatOptions {
+ /**
+ * Controls if and how to collapse identical parts in a range.
+ */
+ enum class RangeCollapse {
+ /**
+ * Apply locale-specific heuristics.
+ */
+ Auto,
+
+ /**
+ * Never collapse identical parts.
+ */
+ None,
+
+ /**
+ * Collapse identical unit parts.
+ */
+ Unit,
+
+ /**
+ * Collapse all identical parts.
+ */
+ All,
+ } mRangeCollapse = RangeCollapse::Auto;
+
+ /**
+ * Controls how to display identical numbers.
+ */
+ enum class RangeIdentityFallback {
+ /**
+ * Display the range as a single value.
+ */
+ SingleValue,
+
+ /**
+ * Display the range as a single value if both numbers were equal before
+ * rounding. Otherwise display with a locale-sensitive approximation
+ * pattern.
+ */
+ ApproximatelyOrSingleValue,
+
+ /**
+ * Display with a locale-sensitive approximation pattern.
+ */
+ Approximately,
+
+ /**
+ * Display as a range expression.
+ */
+ Range,
+ } mRangeIdentityFallback = RangeIdentityFallback::SingleValue;
+};
+
+/**
+ * A NumberRangeFormat implementation that roughly mirrors the API provided by
+ * the ECMA-402 Intl.NumberFormat object for formatting number ranges.
+ *
+ * https://tc39.es/ecma402/#numberformat-objects
+ */
+class NumberRangeFormat final {
+ public:
+ /**
+ * Initialize a new NumberRangeFormat for the provided locale and using the
+ * provided options.
+ *
+ * https://tc39.es/ecma402/#sec-initializenumberformat
+ */
+ static Result<UniquePtr<NumberRangeFormat>, ICUError> TryCreate(
+ std::string_view aLocale, const NumberRangeFormatOptions& aOptions);
+
+ NumberRangeFormat() = default;
+ NumberRangeFormat(const NumberRangeFormat&) = delete;
+ NumberRangeFormat& operator=(const NumberRangeFormat&) = delete;
+
+ ~NumberRangeFormat();
+
+ /**
+ * Formats a double range to a utf-16 string. The string view is valid until
+ * another number range is formatted. Accessing the string view after this
+ * event is undefined behavior.
+ *
+ * https://tc39.es/ecma402/#sec-formatnumericrange
+ */
+ Result<std::u16string_view, ICUError> format(double start, double end) const {
+ if (!formatInternal(start, end)) {
+ return Err(ICUError::InternalError);
+ }
+
+ return formatResult();
+ }
+
+ /**
+ * Formats a double range to a utf-16 string, and fills the provided parts
+ * vector. The string view is valid until another number is formatted.
+ * Accessing the string view after this event is undefined behavior.
+ *
+ * https://tc39.es/ecma402/#sec-partitionnumberrangepattern
+ */
+ Result<std::u16string_view, ICUError> formatToParts(
+ double start, double end, NumberPartVector& parts) const {
+ if (!formatInternal(start, end)) {
+ return Err(ICUError::InternalError);
+ }
+
+ bool isNegativeStart = !std::isnan(start) && IsNegative(start);
+ bool isNegativeEnd = !std::isnan(end) && IsNegative(end);
+
+ return formatResultToParts(Some(start), isNegativeStart, Some(end),
+ isNegativeEnd, parts);
+ }
+
+ /**
+ * Formats a decimal number range to a utf-16 string. The string view is valid
+ * until another number range is formatted. Accessing the string view after
+ * this event is undefined behavior.
+ *
+ * https://tc39.es/ecma402/#sec-formatnumericrange
+ */
+ Result<std::u16string_view, ICUError> format(std::string_view start,
+ std::string_view end) const {
+ if (!formatInternal(start, end)) {
+ return Err(ICUError::InternalError);
+ }
+
+ return formatResult();
+ }
+
+ /**
+ * Formats a string encoded decimal number range to a utf-16 string, and fills
+ * the provided parts vector. The string view is valid until another number is
+ * formatted. Accessing the string view after this event is undefined
+ * behavior.
+ *
+ * https://tc39.es/ecma402/#sec-partitionnumberrangepattern
+ */
+ Result<std::u16string_view, ICUError> formatToParts(
+ std::string_view start, std::string_view end,
+ NumberPartVector& parts) const {
+ if (!formatInternal(start, end)) {
+ return Err(ICUError::InternalError);
+ }
+
+ Maybe<double> numStart = Nothing();
+ if (start == "Infinity" || start == "+Infinity") {
+ numStart.emplace(PositiveInfinity<double>());
+ } else if (start == "-Infinity") {
+ numStart.emplace(NegativeInfinity<double>());
+ } else {
+ // Not currently expected, so we assert here.
+ MOZ_ASSERT(start != "NaN");
+ }
+
+ Maybe<double> numEnd = Nothing();
+ if (end == "Infinity" || end == "+Infinity") {
+ numEnd.emplace(PositiveInfinity<double>());
+ } else if (end == "-Infinity") {
+ numEnd.emplace(NegativeInfinity<double>());
+ } else {
+ // Not currently expected, so we assert here.
+ MOZ_ASSERT(end != "NaN");
+ }
+
+ bool isNegativeStart = !start.empty() && start[0] == '-';
+ bool isNegativeEnd = !end.empty() && end[0] == '-';
+
+ return formatResultToParts(numStart, isNegativeStart, numEnd, isNegativeEnd,
+ parts);
+ }
+
+ /**
+ * Formats the number range and selects the keyword by using a provided
+ * UPluralRules object.
+ *
+ * https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.selectrange
+ *
+ * TODO(1713917) This is necessary because both PluralRules and
+ * NumberRangeFormat have a shared dependency on the raw UFormattedNumberRange
+ * type. Once we transition to using ICU4X, the FFI calls should no
+ * longer require such shared dependencies. At that time, this
+ * functionality should be removed from NumberRangeFormat and invoked
+ * solely from PluralRules.
+ */
+ Result<int32_t, ICUError> selectForRange(
+ double start, double end, char16_t* keyword, int32_t keywordSize,
+ const UPluralRules* pluralRules) const;
+
+ private:
+ UNumberRangeFormatter* mNumberRangeFormatter = nullptr;
+ UFormattedNumberRange* mFormattedNumberRange = nullptr;
+ bool mFormatForUnit = false;
+
+ Result<Ok, ICUError> initialize(std::string_view aLocale,
+ const NumberRangeFormatOptions& aOptions);
+
+ [[nodiscard]] bool formatInternal(double start, double end) const;
+
+ [[nodiscard]] bool formatInternal(std::string_view start,
+ std::string_view end) const;
+
+ Result<std::u16string_view, ICUError> formatResult() const;
+
+ Result<std::u16string_view, ICUError> formatResultToParts(
+ Maybe<double> start, bool startIsNegative, Maybe<double> end,
+ bool endIsNegative, NumberPartVector& parts) const;
+};
+
+} // namespace mozilla::intl
+
+#endif