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