1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 #include "mozilla/intl/RelativeTimeFormat.h"
5 #include "mozilla/FloatingPoint.h"
6 
7 #include "unicode/unum.h"
8 
9 #include "NumberFormatFields.h"
10 #include "ICU4CGlue.h"
11 #include "ScopedICUObject.h"
12 
13 namespace mozilla::intl {
14 
15 /*static*/ Result<UniquePtr<RelativeTimeFormat>, ICUError>
TryCreate(const char * aLocale,const RelativeTimeFormatOptions & aOptions)16 RelativeTimeFormat::TryCreate(const char* aLocale,
17                               const RelativeTimeFormatOptions& aOptions) {
18   UErrorCode status = U_ZERO_ERROR;
19 
20   UFormattedRelativeDateTime* formattedRelativeDateTime =
21       ureldatefmt_openResult(&status);
22   if (U_FAILURE(status)) {
23     return Err(ToICUError(status));
24   }
25   ScopedICUObject<UFormattedRelativeDateTime, ureldatefmt_closeResult>
26       closeFormattedRelativeDate(formattedRelativeDateTime);
27 
28   UNumberFormat* nf =
29       unum_open(UNUM_DECIMAL, nullptr, 0, IcuLocale(aLocale), nullptr, &status);
30   if (U_FAILURE(status)) {
31     return Err(ToICUError(status));
32   }
33   ScopedICUObject<UNumberFormat, unum_close> closeNumberFormatter(nf);
34 
35   // Use the default values as if a new Intl.NumberFormat had been constructed.
36   unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, 1);
37   unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, 0);
38   unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, 3);
39   unum_setAttribute(nf, UNUM_GROUPING_USED, true);
40   unum_setAttribute(nf, UNUM_MINIMUM_GROUPING_DIGITS,
41                     UNUM_MINIMUM_GROUPING_DIGITS_AUTO);
42 
43   UDateRelativeDateTimeFormatterStyle relDateTimeStyle;
44   switch (aOptions.style) {
45     case RelativeTimeFormatOptions::Style::Short:
46       relDateTimeStyle = UDAT_STYLE_SHORT;
47       break;
48     case RelativeTimeFormatOptions::Style::Narrow:
49       relDateTimeStyle = UDAT_STYLE_NARROW;
50       break;
51     case RelativeTimeFormatOptions::Style::Long:
52       relDateTimeStyle = UDAT_STYLE_LONG;
53       break;
54   }
55 
56   URelativeDateTimeFormatter* formatter =
57       ureldatefmt_open(IcuLocale(aLocale), nf, relDateTimeStyle,
58                        UDISPCTX_CAPITALIZATION_FOR_STANDALONE, &status);
59 
60   if (U_FAILURE(status)) {
61     return Err(ToICUError(status));
62   }
63 
64   // Ownership was transferred to mFormatter.
65   closeNumberFormatter.forget();
66 
67   UniquePtr<RelativeTimeFormat> rtf = MakeUnique<RelativeTimeFormat>(
68       aOptions.numeric, formatter, formattedRelativeDateTime);
69 
70   // Ownership was transferred to rtf.
71   closeFormattedRelativeDate.forget();
72   return rtf;
73 }
74 
RelativeTimeFormat(RelativeTimeFormatOptions::Numeric aNumeric,URelativeDateTimeFormatter * aFormatter,UFormattedRelativeDateTime * aFormattedRelativeDateTime)75 RelativeTimeFormat::RelativeTimeFormat(
76     RelativeTimeFormatOptions::Numeric aNumeric,
77     URelativeDateTimeFormatter* aFormatter,
78     UFormattedRelativeDateTime* aFormattedRelativeDateTime)
79     : mNumeric(aNumeric),
80       mFormatter(aFormatter),
81       mFormattedRelativeDateTime(aFormattedRelativeDateTime) {}
82 
~RelativeTimeFormat()83 RelativeTimeFormat::~RelativeTimeFormat() {
84   if (mFormattedRelativeDateTime) {
85     ureldatefmt_closeResult(mFormattedRelativeDateTime);
86     mFormattedRelativeDateTime = nullptr;
87   }
88 
89   if (mFormatter) {
90     ureldatefmt_close(mFormatter);
91     mFormatter = nullptr;
92   }
93 }
94 
ToURelativeDateTimeUnit(FormatUnit unit) const95 URelativeDateTimeUnit RelativeTimeFormat::ToURelativeDateTimeUnit(
96     FormatUnit unit) const {
97   switch (unit) {
98     case FormatUnit::Second:
99       return UDAT_REL_UNIT_SECOND;
100     case FormatUnit::Minute:
101       return UDAT_REL_UNIT_MINUTE;
102     case FormatUnit::Hour:
103       return UDAT_REL_UNIT_HOUR;
104     case FormatUnit::Day:
105       return UDAT_REL_UNIT_DAY;
106     case FormatUnit::Week:
107       return UDAT_REL_UNIT_WEEK;
108     case FormatUnit::Month:
109       return UDAT_REL_UNIT_MONTH;
110     case FormatUnit::Quarter:
111       return UDAT_REL_UNIT_QUARTER;
112     case FormatUnit::Year:
113       return UDAT_REL_UNIT_YEAR;
114   };
115   MOZ_ASSERT_UNREACHABLE();
116   return UDAT_REL_UNIT_SECOND;
117 }
118 
formatToParts(double aNumber,FormatUnit aUnit,NumberPartVector & aParts) const119 Result<Span<const char16_t>, ICUError> RelativeTimeFormat::formatToParts(
120     double aNumber, FormatUnit aUnit, NumberPartVector& aParts) const {
121   UErrorCode status = U_ZERO_ERROR;
122 
123   if (mNumeric == RelativeTimeFormatOptions::Numeric::Auto) {
124     ureldatefmt_formatToResult(mFormatter, aNumber,
125                                ToURelativeDateTimeUnit(aUnit),
126                                mFormattedRelativeDateTime, &status);
127   } else {
128     ureldatefmt_formatNumericToResult(mFormatter, aNumber,
129                                       ToURelativeDateTimeUnit(aUnit),
130                                       mFormattedRelativeDateTime, &status);
131   }
132   if (U_FAILURE(status)) {
133     return Err(ToICUError(status));
134   }
135 
136   const UFormattedValue* formattedValue =
137       ureldatefmt_resultAsValue(mFormattedRelativeDateTime, &status);
138   if (U_FAILURE(status)) {
139     return Err(ToICUError(status));
140   }
141 
142   bool isNegative = !IsNaN(aNumber) && IsNegative(aNumber);
143 
144   // Necessary until all of intl is using Span (Bug 1709880)
145   return FormatResultToParts(formattedValue, Nothing(), isNegative,
146                              false /*formatForUnit*/, aParts)
147       .andThen([](std::u16string_view result)
148                    -> Result<Span<const char16_t>, ICUError> {
149         return Span<const char16_t>(result.data(), result.length());
150       });
151 }
152 
153 }  // namespace mozilla::intl
154