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
5 #include "unicode/ucal.h"
6 #include "unicode/udat.h"
7 #include "unicode/udatpg.h"
8
9 #include "ScopedICUObject.h"
10
11 #include "mozilla/intl/Calendar.h"
12 #include "mozilla/intl/DateTimeFormat.h"
13
14 namespace mozilla::intl {
15
~DateTimeFormat()16 DateTimeFormat::~DateTimeFormat() {
17 MOZ_ASSERT(mDateFormat);
18 udat_close(mDateFormat);
19 }
20
ToUDateFormatStyle(DateTimeStyle aStyle)21 static UDateFormatStyle ToUDateFormatStyle(DateTimeStyle aStyle) {
22 switch (aStyle) {
23 case DateTimeStyle::Full:
24 return UDAT_FULL;
25 case DateTimeStyle::Long:
26 return UDAT_LONG;
27 case DateTimeStyle::Medium:
28 return UDAT_MEDIUM;
29 case DateTimeStyle::Short:
30 return UDAT_SHORT;
31 case DateTimeStyle::None:
32 return UDAT_NONE;
33 }
34 MOZ_ASSERT_UNREACHABLE();
35 // Do not use the default: branch so that the enum is exhaustively checked.
36 return UDAT_NONE;
37 }
38
39 /* static */
40 Result<UniquePtr<DateTimeFormat>, DateTimeFormat::StyleError>
TryCreateFromStyle(Span<const char> aLocale,DateTimeStyle aDateStyle,DateTimeStyle aTimeStyle,Maybe<Span<const char16_t>> aTimeZoneOverride)41 DateTimeFormat::TryCreateFromStyle(
42 Span<const char> aLocale, DateTimeStyle aDateStyle,
43 DateTimeStyle aTimeStyle, Maybe<Span<const char16_t>> aTimeZoneOverride) {
44 auto dateStyle = ToUDateFormatStyle(aDateStyle);
45 auto timeStyle = ToUDateFormatStyle(aTimeStyle);
46
47 if (dateStyle == UDAT_NONE && timeStyle == UDAT_NONE) {
48 dateStyle = UDAT_DEFAULT;
49 timeStyle = UDAT_DEFAULT;
50 }
51
52 // The time zone is optional.
53 int32_t tzIDLength = -1;
54 const UChar* tzID = nullptr;
55 if (aTimeZoneOverride) {
56 tzIDLength = static_cast<int32_t>(aTimeZoneOverride->size());
57 tzID = aTimeZoneOverride->Elements();
58 }
59
60 UErrorCode status = U_ZERO_ERROR;
61 UDateFormat* dateFormat =
62 udat_open(dateStyle, timeStyle, aLocale.data(), tzID, tzIDLength,
63 /* pattern */ nullptr, /* pattern length */ -1, &status);
64
65 if (U_SUCCESS(status)) {
66 return UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
67 }
68
69 return Err(DateTimeFormat::StyleError::DateFormatFailure);
70 }
71
DateTimeFormat(UDateFormat * aDateFormat)72 DateTimeFormat::DateTimeFormat(UDateFormat* aDateFormat) {
73 MOZ_RELEASE_ASSERT(aDateFormat, "Expected aDateFormat to not be a nullptr.");
74 mDateFormat = aDateFormat;
75 }
76
77 /* static */
78 Result<UniquePtr<DateTimeFormat>, DateTimeFormat::PatternError>
TryCreateFromPattern(Span<const char> aLocale,Span<const char16_t> aPattern,Maybe<Span<const char16_t>> aTimeZoneOverride)79 DateTimeFormat::TryCreateFromPattern(
80 Span<const char> aLocale, Span<const char16_t> aPattern,
81 Maybe<Span<const char16_t>> aTimeZoneOverride) {
82 UErrorCode status = U_ZERO_ERROR;
83
84 // The time zone is optional.
85 int32_t tzIDLength = -1;
86 const UChar* tzID = nullptr;
87 if (aTimeZoneOverride) {
88 tzIDLength = static_cast<int32_t>(aTimeZoneOverride->size());
89 tzID = aTimeZoneOverride->data();
90 }
91
92 // Create the date formatter.
93 UDateFormat* dateFormat = udat_open(
94 UDAT_PATTERN, UDAT_PATTERN, static_cast<const char*>(aLocale.data()),
95 tzID, tzIDLength, aPattern.data(), static_cast<int32_t>(aPattern.size()),
96 &status);
97
98 if (U_FAILURE(status)) {
99 return Err(PatternError::DateFormatFailure);
100 }
101
102 // The DateTimeFormat wrapper will control the life cycle of the ICU
103 // dateFormat object.
104 return UniquePtr<DateTimeFormat>(new DateTimeFormat(dateFormat));
105 }
106
107 /* static */
108 Result<UniquePtr<DateTimeFormat>, DateTimeFormat::SkeletonError>
TryCreateFromSkeleton(Span<const char> aLocale,Span<const char16_t> aSkeleton,Maybe<Span<const char16_t>> aTimeZoneOverride)109 DateTimeFormat::TryCreateFromSkeleton(
110 Span<const char> aLocale, Span<const char16_t> aSkeleton,
111 Maybe<Span<const char16_t>> aTimeZoneOverride) {
112 UErrorCode status = U_ZERO_ERROR;
113
114 // Create a time pattern generator. Its lifetime is scoped to this function.
115 UDateTimePatternGenerator* dtpg = udatpg_open(aLocale.data(), &status);
116 if (U_FAILURE(status)) {
117 return Err(SkeletonError::PatternGeneratorFailure);
118 }
119 ScopedICUObject<UDateTimePatternGenerator, udatpg_close> datPgToClose(dtpg);
120
121 // Compute the best pattern for the skeleton.
122 mozilla::Vector<char16_t, DateTimeFormat::StackU16VectorSize> bestPattern;
123
124 auto result = FillVectorWithICUCall(
125 bestPattern,
126 [&dtpg, &aSkeleton](UChar* target, int32_t length, UErrorCode* status) {
127 return udatpg_getBestPattern(dtpg, aSkeleton.data(),
128 static_cast<int32_t>(aSkeleton.size()),
129 target, length, status);
130 });
131
132 if (result.isErr()) {
133 return Err(SkeletonError::GetBestPatternFailure);
134 }
135
136 return DateTimeFormat::TryCreateFromPattern(aLocale, bestPattern,
137 aTimeZoneOverride)
138 .mapErr([](DateTimeFormat::PatternError error) {
139 switch (error) {
140 case DateTimeFormat::PatternError::DateFormatFailure:
141 return SkeletonError::DateFormatFailure;
142 }
143 // Do not use the default branch, so that the switch is exhaustively
144 // checked.
145 MOZ_ASSERT_UNREACHABLE();
146 return SkeletonError::DateFormatFailure;
147 });
148 }
149
150 /* static */
151 Result<UniquePtr<DateTimeFormat>, DateTimeFormat::SkeletonError>
TryCreateFromSkeleton(Span<const char> aLocale,Span<const char> aSkeleton,Maybe<Span<const char>> aTimeZoneOverride)152 DateTimeFormat::TryCreateFromSkeleton(
153 Span<const char> aLocale, Span<const char> aSkeleton,
154 Maybe<Span<const char>> aTimeZoneOverride) {
155 // Convert the skeleton to UTF-16.
156 mozilla::Vector<char16_t, DateTimeFormat::StackU16VectorSize>
157 skeletonUtf16Buffer;
158
159 if (!FillUTF16Vector(aSkeleton, skeletonUtf16Buffer)) {
160 return Err(SkeletonError::OutOfMemory);
161 }
162
163 // Convert the timezone to UTF-16 if it exists.
164 mozilla::Vector<char16_t, DateTimeFormat::StackU16VectorSize> tzUtf16Vec;
165 Maybe<Span<const char16_t>> timeZone = Nothing{};
166 if (aTimeZoneOverride) {
167 if (!FillUTF16Vector(*aTimeZoneOverride, tzUtf16Vec)) {
168 return Err(SkeletonError::OutOfMemory);
169 };
170 timeZone =
171 Some(Span<const char16_t>(tzUtf16Vec.begin(), tzUtf16Vec.length()));
172 }
173
174 return DateTimeFormat::TryCreateFromSkeleton(aLocale, skeletonUtf16Buffer,
175 timeZone);
176 }
177
SetStartTimeIfGregorian(double aTime)178 void DateTimeFormat::SetStartTimeIfGregorian(double aTime) {
179 UErrorCode status = U_ZERO_ERROR;
180 UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(mDateFormat));
181 ucal_setGregorianChange(cal, aTime, &status);
182 // An error here means the calendar is not Gregorian, and can be ignored.
183 }
184
185 /* static */
CloneCalendar(double aUnixEpoch) const186 Result<UniquePtr<Calendar>, InternalError> DateTimeFormat::CloneCalendar(
187 double aUnixEpoch) const {
188 UErrorCode status = U_ZERO_ERROR;
189 UCalendar* calendarRaw = ucal_clone(udat_getCalendar(mDateFormat), &status);
190 if (U_FAILURE(status)) {
191 return Err(InternalError{});
192 }
193 auto calendar = MakeUnique<Calendar>(calendarRaw);
194
195 auto setTimeResult = calendar->SetTimeInMs(aUnixEpoch);
196 if (setTimeResult.isErr()) {
197 return Err(InternalError{});
198 }
199 return calendar;
200 }
201
202 } // namespace mozilla::intl
203