Line data Source code
1 : // Copyright 2018 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #ifndef V8_INTL_SUPPORT
6 : #error Internationalization is expected to be enabled.
7 : #endif // V8_INTL_SUPPORT
8 :
9 : #include "src/objects/js-date-time-format.h"
10 :
11 : #include <memory>
12 : #include <string>
13 : #include <vector>
14 :
15 : #include "src/date.h"
16 : #include "src/heap/factory.h"
17 : #include "src/isolate.h"
18 : #include "src/objects/intl-objects.h"
19 : #include "src/objects/js-date-time-format-inl.h"
20 :
21 : #include "unicode/calendar.h"
22 : #include "unicode/dtptngen.h"
23 : #include "unicode/gregocal.h"
24 : #include "unicode/smpdtfmt.h"
25 : #include "unicode/unistr.h"
26 :
27 : namespace v8 {
28 : namespace internal {
29 :
30 : namespace {
31 :
32 1737252 : class PatternMap {
33 : public:
34 : PatternMap(std::string pattern, std::string value)
35 668556 : : pattern(std::move(pattern)), value(std::move(value)) {}
36 4811616 : virtual ~PatternMap() = default;
37 : std::string pattern;
38 : std::string value;
39 : };
40 :
41 155412 : class PatternItem {
42 : public:
43 155412 : PatternItem(const std::string property, std::vector<PatternMap> pairs,
44 : std::vector<const char*> allowed_values)
45 : : property(std::move(property)),
46 : pairs(std::move(pairs)),
47 310824 : allowed_values(allowed_values) {}
48 932472 : virtual ~PatternItem() = default;
49 :
50 : const std::string property;
51 : // It is important for the pattern in the pairs from longer one to shorter one
52 : // if the longer one contains substring of an shorter one.
53 : std::vector<PatternMap> pairs;
54 : std::vector<const char*> allowed_values;
55 : };
56 :
57 17268 : const std::vector<PatternItem> GetPatternItems() {
58 : const std::vector<const char*> kLongShort = {"long", "short"};
59 : const std::vector<const char*> kNarrowLongShort = {"narrow", "long", "short"};
60 : const std::vector<const char*> k2DigitNumeric = {"2-digit", "numeric"};
61 : const std::vector<const char*> kNarrowLongShort2DigitNumeric = {
62 : "narrow", "long", "short", "2-digit", "numeric"};
63 : const std::vector<PatternItem> kPatternItems = {
64 : PatternItem("weekday",
65 : {{"EEEEE", "narrow"},
66 : {"EEEE", "long"},
67 : {"EEE", "short"},
68 : {"ccccc", "narrow"},
69 : {"cccc", "long"},
70 : {"ccc", "short"}},
71 103608 : kNarrowLongShort),
72 : PatternItem("era",
73 : {{"GGGGG", "narrow"}, {"GGGG", "long"}, {"GGG", "short"}},
74 51804 : kNarrowLongShort),
75 : PatternItem("year", {{"yy", "2-digit"}, {"y", "numeric"}},
76 34536 : k2DigitNumeric),
77 : // Sometimes we get L instead of M for month - standalone name.
78 : PatternItem("month",
79 : {{"MMMMM", "narrow"},
80 : {"MMMM", "long"},
81 : {"MMM", "short"},
82 : {"MM", "2-digit"},
83 : {"M", "numeric"},
84 : {"LLLLL", "narrow"},
85 : {"LLLL", "long"},
86 : {"LLL", "short"},
87 : {"LL", "2-digit"},
88 : {"L", "numeric"}},
89 172680 : kNarrowLongShort2DigitNumeric),
90 34536 : PatternItem("day", {{"dd", "2-digit"}, {"d", "numeric"}}, k2DigitNumeric),
91 : PatternItem("hour",
92 : {{"HH", "2-digit"},
93 : {"H", "numeric"},
94 : {"hh", "2-digit"},
95 : {"h", "numeric"},
96 : {"kk", "2-digit"},
97 : {"k", "numeric"},
98 : {"KK", "2-digit"},
99 : {"K", "numeric"}},
100 138144 : k2DigitNumeric),
101 : PatternItem("minute", {{"mm", "2-digit"}, {"m", "numeric"}},
102 34536 : k2DigitNumeric),
103 : PatternItem("second", {{"ss", "2-digit"}, {"s", "numeric"}},
104 34536 : k2DigitNumeric),
105 : PatternItem("timeZoneName", {{"zzzz", "long"}, {"z", "short"}},
106 2538396 : kLongShort)};
107 17268 : return kPatternItems;
108 : }
109 :
110 764712 : class PatternData {
111 : public:
112 133380 : PatternData(const std::string property, std::vector<PatternMap> pairs,
113 : std::vector<const char*> allowed_values)
114 266760 : : property(std::move(property)), allowed_values(allowed_values) {
115 726180 : for (const auto& pair : pairs) {
116 918840 : map.insert(std::make_pair(pair.value, pair.pattern));
117 : }
118 133380 : }
119 1031472 : virtual ~PatternData() = default;
120 :
121 : const std::string property;
122 : std::map<const std::string, const std::string> map;
123 : std::vector<const char*> allowed_values;
124 : };
125 :
126 14820 : const std::vector<PatternData> CreateCommonData(const PatternData& hour_data) {
127 : std::vector<PatternData> build;
128 163020 : for (const PatternItem& item : GetPatternItems()) {
129 266760 : if (item.property == "hour") {
130 14820 : build.push_back(hour_data);
131 : } else {
132 : build.push_back(
133 474240 : PatternData(item.property, item.pairs, item.allowed_values));
134 : }
135 14820 : }
136 14820 : return build;
137 : }
138 :
139 14820 : const std::vector<PatternData> CreateData(const char* digit2,
140 : const char* numeric) {
141 : return CreateCommonData(
142 : PatternData("hour", {{digit2, "2-digit"}, {numeric, "numeric"}},
143 133380 : {"2-digit", "numeric"}));
144 : }
145 :
146 : // According to "Date Field Symbol Table" in
147 : // http://userguide.icu-project.org/formatparse/datetime
148 : // Symbol | Meaning | Example(s)
149 : // h hour in am/pm (1~12) h 7
150 : // hh 07
151 : // H hour in day (0~23) H 0
152 : // HH 00
153 : // k hour in day (1~24) k 24
154 : // kk 24
155 : // K hour in am/pm (0~11) K 0
156 : // KK 00
157 2964 : const std::vector<PatternData> GetPatternData(Intl::HourCycle hour_cycle) {
158 2964 : const std::vector<PatternData> data = CreateData("jj", "j");
159 5928 : const std::vector<PatternData> data_h11 = CreateData("KK", "K");
160 5928 : const std::vector<PatternData> data_h12 = CreateData("hh", "h");
161 5928 : const std::vector<PatternData> data_h23 = CreateData("HH", "H");
162 5928 : const std::vector<PatternData> data_h24 = CreateData("kk", "k");
163 2964 : switch (hour_cycle) {
164 : case Intl::HourCycle::kH11:
165 27 : return data_h11;
166 : case Intl::HourCycle::kH12:
167 36 : return data_h12;
168 : case Intl::HourCycle::kH23:
169 27 : return data_h23;
170 : case Intl::HourCycle::kH24:
171 27 : return data_h24;
172 : case Intl::HourCycle::kUndefined:
173 2847 : return data;
174 : default:
175 0 : UNREACHABLE();
176 2964 : }
177 : }
178 :
179 72 : std::string GetGMTTzID(Isolate* isolate, const std::string& input) {
180 72 : std::string ret = "Etc/GMT";
181 72 : switch (input.length()) {
182 : case 8:
183 9 : if (input[7] == '0') return ret + '0';
184 : break;
185 : case 9:
186 72 : if ((input[7] == '+' || input[7] == '-') &&
187 36 : IsInRange(input[8], '0', '9')) {
188 72 : return ret + input[7] + input[8];
189 : }
190 : break;
191 : case 10:
192 54 : if ((input[7] == '+' || input[7] == '-') && (input[8] == '1') &&
193 27 : IsInRange(input[9], '0', '4')) {
194 81 : return ret + input[7] + input[8] + input[9];
195 : }
196 : break;
197 : }
198 0 : return "";
199 : }
200 :
201 : // Locale independenty version of isalpha for ascii range. This will return
202 : // false if the ch is alpha but not in ascii range.
203 : bool IsAsciiAlpha(char ch) {
204 11079 : return IsInRange(ch, 'A', 'Z') || IsInRange(ch, 'a', 'z');
205 : }
206 :
207 : // Locale independent toupper for ascii range. This will not return İ (dotted I)
208 : // for i under Turkish locale while std::toupper may.
209 7092 : char LocaleIndependentAsciiToUpper(char ch) {
210 8073 : return (IsInRange(ch, 'a', 'z')) ? (ch - 'a' + 'A') : ch;
211 : }
212 :
213 : // Locale independent tolower for ascii range.
214 : char LocaleIndependentAsciiToLower(char ch) {
215 4347 : return (IsInRange(ch, 'A', 'Z')) ? (ch - 'A' + 'a') : ch;
216 : }
217 :
218 : // Returns titlecased location, bueNos_airES -> Buenos_Aires
219 : // or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
220 : // deals with ASCII only characters.
221 : // 'of', 'au' and 'es' are special-cased and lowercased.
222 : // ICU's timezone parsing is case sensitive, but ECMAScript is case insensitive
223 387 : std::string ToTitleCaseTimezoneLocation(Isolate* isolate,
224 : const std::string& input) {
225 : std::string title_cased;
226 : int word_length = 0;
227 12339 : for (char ch : input) {
228 : // Convert first char to upper case, the rest to lower case
229 6003 : if (IsAsciiAlpha(ch)) {
230 10584 : title_cased += word_length == 0 ? LocaleIndependentAsciiToUpper(ch)
231 : : LocaleIndependentAsciiToLower(ch);
232 5292 : word_length++;
233 711 : } else if (ch == '_' || ch == '-' || ch == '/') {
234 : // Special case Au/Es/Of to be lower case.
235 657 : if (word_length == 2) {
236 45 : size_t pos = title_cased.length() - 2;
237 45 : std::string substr = title_cased.substr(pos, 2);
238 90 : if (substr == "Of" || substr == "Es" || substr == "Au") {
239 72 : title_cased[pos] = LocaleIndependentAsciiToLower(title_cased[pos]);
240 : }
241 : }
242 : title_cased += ch;
243 : word_length = 0;
244 : } else {
245 : // Invalid input
246 : return std::string();
247 : }
248 : }
249 : return title_cased;
250 : }
251 :
252 : } // namespace
253 :
254 522 : std::string JSDateTimeFormat::CanonicalizeTimeZoneID(Isolate* isolate,
255 : const std::string& input) {
256 522 : std::string upper = input;
257 : transform(upper.begin(), upper.end(), upper.begin(),
258 522 : LocaleIndependentAsciiToUpper);
259 1953 : if (upper == "UTC" || upper == "GMT" || upper == "ETC/UTC" ||
260 : upper == "ETC/GMT") {
261 63 : return "UTC";
262 : }
263 : // We expect only _, '-' and / beside ASCII letters.
264 : // All inputs should conform to Area/Location(/Location)*, or Etc/GMT* .
265 : // TODO(jshin): 1. Support 'GB-Eire", 'EST5EDT", "ROK', 'US/*', 'NZ' and many
266 : // other aliases/linked names when moving timezone validation code to C++.
267 : // See crbug.com/364374 and crbug.com/v8/8007 .
268 : // 2. Resolve the difference betwee CLDR/ICU and IANA time zone db.
269 : // See http://unicode.org/cldr/trac/ticket/9892 and crbug.com/645807 .
270 459 : if (strncmp(upper.c_str(), "ETC/GMT", 7) == 0) {
271 72 : return GetGMTTzID(isolate, input);
272 : }
273 387 : return ToTitleCaseTimezoneLocation(isolate, input);
274 : }
275 :
276 : // ecma402 #sec-intl.datetimeformat.prototype.resolvedoptions
277 2448 : MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
278 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
279 : Factory* factory = isolate->factory();
280 : // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
281 2448 : Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
282 :
283 : Handle<Object> resolved_obj;
284 :
285 2448 : CHECK(!date_time_format->icu_locale().is_null());
286 4896 : CHECK_NOT_NULL(date_time_format->icu_locale()->raw());
287 4896 : icu::Locale* icu_locale = date_time_format->icu_locale()->raw();
288 2448 : Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(*icu_locale);
289 2448 : MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSObject>());
290 : std::string locale_str = maybe_locale_str.FromJust();
291 : Handle<String> locale =
292 2448 : factory->NewStringFromAsciiChecked(locale_str.c_str());
293 :
294 : icu::SimpleDateFormat* icu_simple_date_format =
295 4896 : date_time_format->icu_simple_date_format()->raw();
296 : // calendar
297 2448 : const icu::Calendar* calendar = icu_simple_date_format->getCalendar();
298 : // getType() returns legacy calendar type name instead of LDML/BCP47 calendar
299 : // key values. intl.js maps them to BCP47 values for key "ca".
300 : // TODO(jshin): Consider doing it here, instead.
301 2448 : std::string calendar_str = calendar->getType();
302 :
303 : // Maps ICU calendar names to LDML/BCP47 types for key 'ca'.
304 : // See typeMap section in third_party/icu/source/data/misc/keyTypeData.txt
305 : // and
306 : // http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
307 2448 : if (calendar_str == "gregorian") {
308 : calendar_str = "gregory";
309 612 : } else if (calendar_str == "ethiopic-amete-alem") {
310 : calendar_str = "ethioaa";
311 : }
312 :
313 2448 : const icu::TimeZone& tz = calendar->getTimeZone();
314 2448 : icu::UnicodeString time_zone;
315 : tz.getID(time_zone);
316 2448 : UErrorCode status = U_ZERO_ERROR;
317 2448 : icu::UnicodeString canonical_time_zone;
318 2448 : icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status);
319 : Handle<Object> timezone_value;
320 2448 : if (U_SUCCESS(status)) {
321 : // In CLDR (http://unicode.org/cldr/trac/ticket/9943), Etc/UTC is made
322 : // a separate timezone ID from Etc/GMT even though they're still the same
323 : // timezone. We have Etc/UTC because 'UTC', 'Etc/Universal',
324 : // 'Etc/Zulu' and others are turned to 'Etc/UTC' by ICU. Etc/GMT comes
325 : // from Etc/GMT0, Etc/GMT+0, Etc/GMT-0, Etc/Greenwich.
326 : // ecma402#sec-canonicalizetimezonename step 3
327 9729 : if (canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/UTC") ||
328 7218 : canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/GMT")) {
329 99 : timezone_value = factory->UTC_string();
330 : } else {
331 4698 : ASSIGN_RETURN_ON_EXCEPTION(isolate, timezone_value,
332 : Intl::ToString(isolate, canonical_time_zone),
333 : JSObject);
334 : }
335 : } else {
336 : // Somehow on Windows we will reach here.
337 0 : timezone_value = factory->undefined_value();
338 : }
339 :
340 : // Ugly hack. ICU doesn't expose numbering system in any way, so we have
341 : // to assume that for given locale NumberingSystem constructor produces the
342 : // same digits as NumberFormat/Calendar would.
343 : // Tracked by https://unicode-org.atlassian.net/browse/ICU-13431
344 2448 : std::string numbering_system = Intl::GetNumberingSystem(*icu_locale);
345 :
346 2448 : icu::UnicodeString pattern_unicode;
347 2448 : icu_simple_date_format->toPattern(pattern_unicode);
348 : std::string pattern;
349 2448 : pattern_unicode.toUTF8String(pattern);
350 :
351 : // 5. For each row of Table 6, except the header row, in table order, do
352 : // Table 6: Resolved Options of DateTimeFormat Instances
353 : // Internal Slot Property
354 : // [[Locale]] "locale"
355 : // [[Calendar]] "calendar"
356 : // [[NumberingSystem]] "numberingSystem"
357 : // [[TimeZone]] "timeZone"
358 : // [[HourCycle]] "hourCycle"
359 : // "hour12"
360 : // [[Weekday]] "weekday"
361 : // [[Era]] "era"
362 : // [[Year]] "year"
363 : // [[Month]] "month"
364 : // [[Day]] "day"
365 : // [[Hour]] "hour"
366 : // [[Minute]] "minute"
367 : // [[Second]] "second"
368 : // [[TimeZoneName]] "timeZoneName"
369 4896 : CHECK(JSReceiver::CreateDataProperty(
370 : isolate, options, factory->locale_string(), locale, kDontThrow)
371 : .FromJust());
372 7344 : CHECK(JSReceiver::CreateDataProperty(
373 : isolate, options, factory->calendar_string(),
374 : factory->NewStringFromAsciiChecked(calendar_str.c_str()),
375 : kDontThrow)
376 : .FromJust());
377 2448 : if (!numbering_system.empty()) {
378 7344 : CHECK(JSReceiver::CreateDataProperty(
379 : isolate, options, factory->numberingSystem_string(),
380 : factory->NewStringFromAsciiChecked(numbering_system.c_str()),
381 : kDontThrow)
382 : .FromJust());
383 : }
384 4896 : CHECK(JSReceiver::CreateDataProperty(isolate, options,
385 : factory->timeZone_string(),
386 : timezone_value, kDontThrow)
387 : .FromJust());
388 :
389 : // 5.b.i. Let hc be dtf.[[HourCycle]].
390 : Intl::HourCycle hc = date_time_format->hour_cycle();
391 :
392 2448 : if (hc != Intl::HourCycle::kUndefined) {
393 135 : CHECK(JSReceiver::CreateDataProperty(
394 : isolate, options, factory->hourCycle_string(),
395 : date_time_format->HourCycleAsString(), kDontThrow)
396 : .FromJust());
397 45 : switch (hc) {
398 : // ii. If hc is "h11" or "h12", let v be true.
399 : case Intl::HourCycle::kH11:
400 : case Intl::HourCycle::kH12:
401 36 : CHECK(JSReceiver::CreateDataProperty(isolate, options,
402 : factory->hour12_string(),
403 : factory->true_value(), kDontThrow)
404 : .FromJust());
405 : break;
406 : // iii. Else if, hc is "h23" or "h24", let v be false.
407 : case Intl::HourCycle::kH23:
408 : case Intl::HourCycle::kH24:
409 54 : CHECK(JSReceiver::CreateDataProperty(isolate, options,
410 : factory->hour12_string(),
411 : factory->false_value(), kDontThrow)
412 : .FromJust());
413 : break;
414 : // iv. Else, let v be undefined.
415 : case Intl::HourCycle::kUndefined:
416 : break;
417 : }
418 : }
419 :
420 26928 : for (const auto& item : GetPatternItems()) {
421 115848 : for (const auto& pair : item.pairs) {
422 78849 : if (pattern.find(pair.pattern) != std::string::npos) {
423 28260 : CHECK(JSReceiver::CreateDataProperty(
424 : isolate, options,
425 : factory->NewStringFromAsciiChecked(item.property.c_str()),
426 : factory->NewStringFromAsciiChecked(pair.value.c_str()),
427 : kDontThrow)
428 : .FromJust());
429 : break;
430 : }
431 : }
432 2448 : }
433 :
434 2448 : return options;
435 : }
436 :
437 : namespace {
438 :
439 : // ecma402/#sec-formatdatetime
440 : // FormatDateTime( dateTimeFormat, x )
441 711 : MaybeHandle<String> FormatDateTime(Isolate* isolate,
442 : const icu::SimpleDateFormat& date_format,
443 : double x) {
444 711 : double date_value = DateCache::TimeClip(x);
445 711 : if (std::isnan(date_value)) {
446 54 : THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
447 : String);
448 : }
449 :
450 : icu::UnicodeString result;
451 657 : date_format.format(date_value, result);
452 :
453 657 : return Intl::ToString(isolate, result);
454 : }
455 :
456 : } // namespace
457 :
458 : // ecma402/#sec-datetime-format-functions
459 : // DateTime Format Functions
460 540 : MaybeHandle<String> JSDateTimeFormat::DateTimeFormat(
461 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
462 : Handle<Object> date) {
463 : // 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]]
464 : // internal slot.
465 :
466 : // 3. If date is not provided or is undefined, then
467 : double x;
468 1080 : if (date->IsUndefined()) {
469 : // 3.a Let x be Call(%Date_now%, undefined).
470 27 : x = JSDate::CurrentTimeValue(isolate);
471 : } else {
472 : // 4. Else,
473 : // a. Let x be ? ToNumber(date).
474 1026 : ASSIGN_RETURN_ON_EXCEPTION(isolate, date, Object::ToNumber(isolate, date),
475 : String);
476 1026 : CHECK(date->IsNumber());
477 513 : x = date->Number();
478 : }
479 : // 5. Return FormatDateTime(dtf, x).
480 : icu::SimpleDateFormat* format =
481 1080 : date_time_format->icu_simple_date_format()->raw();
482 540 : return FormatDateTime(isolate, *format, x);
483 : }
484 :
485 : namespace {
486 : Isolate::ICUObjectCacheType ConvertToCacheType(
487 : JSDateTimeFormat::DefaultsOption type) {
488 180 : switch (type) {
489 : case JSDateTimeFormat::DefaultsOption::kDate:
490 : return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForDate;
491 : case JSDateTimeFormat::DefaultsOption::kTime:
492 : return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForTime;
493 : case JSDateTimeFormat::DefaultsOption::kAll:
494 : return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormat;
495 : }
496 : }
497 : } // namespace
498 :
499 180 : MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime(
500 : Isolate* isolate, Handle<Object> date, Handle<Object> locales,
501 : Handle<Object> options, RequiredOption required, DefaultsOption defaults) {
502 : Isolate::ICUObjectCacheType cache_type = ConvertToCacheType(defaults);
503 :
504 : Factory* factory = isolate->factory();
505 : // 1. Let x be ? thisTimeValue(this value);
506 360 : if (!date->IsJSDate()) {
507 0 : THROW_NEW_ERROR(isolate,
508 : NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
509 : factory->Date_string()),
510 : String);
511 : }
512 :
513 360 : double const x = Handle<JSDate>::cast(date)->value()->Number();
514 : // 2. If x is NaN, return "Invalid Date"
515 180 : if (std::isnan(x)) {
516 0 : return factory->Invalid_Date_string();
517 : }
518 :
519 : // We only cache the instance when both locales and options are undefined,
520 : // as that is the only case when the specified side-effects of examining
521 : // those arguments are unobservable.
522 : bool can_cache =
523 522 : locales->IsUndefined(isolate) && options->IsUndefined(isolate);
524 180 : if (can_cache) {
525 : // Both locales and options are undefined, check the cache.
526 : icu::SimpleDateFormat* cached_icu_simple_date_format =
527 : static_cast<icu::SimpleDateFormat*>(
528 54 : isolate->get_cached_icu_object(cache_type));
529 54 : if (cached_icu_simple_date_format != nullptr) {
530 24 : return FormatDateTime(isolate, *cached_icu_simple_date_format, x);
531 : }
532 : }
533 : // 3. Let options be ? ToDateTimeOptions(options, required, defaults).
534 : Handle<JSObject> internal_options;
535 312 : ASSIGN_RETURN_ON_EXCEPTION(
536 : isolate, internal_options,
537 : ToDateTimeOptions(isolate, options, required, defaults), String);
538 :
539 : // 4. Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
540 : Handle<JSFunction> constructor = Handle<JSFunction>(
541 : JSFunction::cast(isolate->context()
542 294 : ->native_context()
543 294 : ->intl_date_time_format_function()),
544 294 : isolate);
545 : Handle<JSObject> obj;
546 294 : ASSIGN_RETURN_ON_EXCEPTION(
547 : isolate, obj,
548 : JSObject::New(constructor, constructor, Handle<AllocationSite>::null()),
549 : String);
550 : Handle<JSDateTimeFormat> date_time_format;
551 294 : ASSIGN_RETURN_ON_EXCEPTION(
552 : isolate, date_time_format,
553 : JSDateTimeFormat::Initialize(isolate, Handle<JSDateTimeFormat>::cast(obj),
554 : locales, internal_options),
555 : String);
556 :
557 147 : if (can_cache) {
558 : isolate->set_icu_object_in_cache(
559 : cache_type, std::static_pointer_cast<icu::UObject>(
560 120 : date_time_format->icu_simple_date_format()->get()));
561 : }
562 : // 5. Return FormatDateTime(dateFormat, x).
563 : icu::SimpleDateFormat* format =
564 294 : date_time_format->icu_simple_date_format()->raw();
565 147 : return FormatDateTime(isolate, *format, x);
566 : }
567 :
568 : namespace {
569 :
570 22544 : Maybe<bool> IsPropertyUndefined(Isolate* isolate, Handle<JSObject> options,
571 : const char* property) {
572 : Factory* factory = isolate->factory();
573 : // i. Let prop be the property name.
574 : // ii. Let value be ? Get(options, prop).
575 : Handle<Object> value;
576 67632 : ASSIGN_RETURN_ON_EXCEPTION_VALUE(
577 : isolate, value,
578 : Object::GetPropertyOrElement(
579 : isolate, options, factory->NewStringFromAsciiChecked(property)),
580 : Nothing<bool>());
581 45088 : return Just(value->IsUndefined(isolate));
582 : }
583 :
584 6436 : Maybe<bool> NeedsDefault(Isolate* isolate, Handle<JSObject> options,
585 : const std::vector<std::string>& props) {
586 : bool needs_default = true;
587 35416 : for (const auto& prop : props) {
588 : // i. Let prop be the property name.
589 : // ii. Let value be ? Get(options, prop)
590 : Maybe<bool> maybe_undefined =
591 22544 : IsPropertyUndefined(isolate, options, prop.c_str());
592 22544 : MAYBE_RETURN(maybe_undefined, Nothing<bool>());
593 : // iii. If value is not undefined, let needDefaults be false.
594 22544 : if (!maybe_undefined.FromJust()) {
595 : needs_default = false;
596 : }
597 : }
598 : return Just(needs_default);
599 : }
600 :
601 2659 : Maybe<bool> CreateDefault(Isolate* isolate, Handle<JSObject> options,
602 : const std::vector<std::string>& props) {
603 : Factory* factory = isolate->factory();
604 : // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
605 13295 : for (const auto& prop : props) {
606 15954 : MAYBE_RETURN(
607 : JSReceiver::CreateDataProperty(
608 : isolate, options, factory->NewStringFromAsciiChecked(prop.c_str()),
609 : factory->numeric_string(), kThrowOnError),
610 : Nothing<bool>());
611 : }
612 : return Just(true);
613 : }
614 :
615 : } // namespace
616 :
617 : // ecma-402/#sec-todatetimeoptions
618 3282 : MaybeHandle<JSObject> JSDateTimeFormat::ToDateTimeOptions(
619 : Isolate* isolate, Handle<Object> input_options, RequiredOption required,
620 : DefaultsOption defaults) {
621 : Factory* factory = isolate->factory();
622 : // 1. If options is undefined, let options be null; otherwise let options be ?
623 : // ToObject(options).
624 : Handle<JSObject> options;
625 6564 : if (input_options->IsUndefined(isolate)) {
626 1965 : options = factory->NewJSObjectWithNullProto();
627 : } else {
628 : Handle<JSReceiver> options_obj;
629 2634 : ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
630 : Object::ToObject(isolate, input_options),
631 : JSObject);
632 : // 2. Let options be ObjectCreate(options).
633 2616 : ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
634 : JSObject::ObjectCreate(isolate, options_obj),
635 : JSObject);
636 : }
637 :
638 : // 3. Let needDefaults be true.
639 : bool needs_default = true;
640 :
641 : // 4. If required is "date" or "any", then
642 3273 : if (required == RequiredOption::kAny || required == RequiredOption::kDate) {
643 : // a. For each of the property names "weekday", "year", "month", "day", do
644 6472 : const std::vector<std::string> list({"weekday", "year", "month", "day"});
645 3236 : Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
646 3236 : MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
647 3236 : needs_default = maybe_needs_default.FromJust();
648 : }
649 :
650 : // 5. If required is "time" or "any", then
651 3273 : if (required == RequiredOption::kAny || required == RequiredOption::kTime) {
652 : // a. For each of the property names "hour", "minute", "second", do
653 6400 : const std::vector<std::string> list({"hour", "minute", "second"});
654 3200 : Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
655 3200 : MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
656 3200 : needs_default &= maybe_needs_default.FromJust();
657 : }
658 :
659 : // 6. If needDefaults is true and defaults is either "date" or "all", then
660 3273 : if (needs_default) {
661 2631 : if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kDate) {
662 : // a. For each of the property names "year", "month", "day", do)
663 5206 : const std::vector<std::string> list({"year", "month", "day"});
664 2603 : MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
665 : }
666 : // 7. If needDefaults is true and defaults is either "time" or "all", then
667 2631 : if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kTime) {
668 : // a. For each of the property names "hour", "minute", "second", do
669 112 : const std::vector<std::string> list({"hour", "minute", "second"});
670 56 : MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
671 : }
672 : }
673 : // 8. Return options.
674 3273 : return options;
675 : }
676 :
677 3051 : MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::UnwrapDateTimeFormat(
678 : Isolate* isolate, Handle<JSReceiver> format_holder) {
679 : Handle<Context> native_context =
680 6102 : Handle<Context>(isolate->context()->native_context(), isolate);
681 : Handle<JSFunction> constructor = Handle<JSFunction>(
682 6102 : JSFunction::cast(native_context->intl_date_time_format_function()),
683 6102 : isolate);
684 : Handle<Object> dtf;
685 9153 : ASSIGN_RETURN_ON_EXCEPTION(
686 : isolate, dtf,
687 : Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
688 : format_holder->IsJSDateTimeFormat()),
689 : JSDateTimeFormat);
690 : // 2. If Type(dtf) is not Object or dtf does not have an
691 : // [[InitializedDateTimeFormat]] internal slot, then
692 6102 : if (!dtf->IsJSDateTimeFormat()) {
693 : // a. Throw a TypeError exception.
694 90 : THROW_NEW_ERROR(isolate,
695 : NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
696 : isolate->factory()->NewStringFromAsciiChecked(
697 : "UnwrapDateTimeFormat"),
698 : format_holder),
699 : JSDateTimeFormat);
700 : }
701 : // 3. Return dtf.
702 3006 : return Handle<JSDateTimeFormat>::cast(dtf);
703 : }
704 :
705 : namespace {
706 :
707 : // ecma-402/#sec-isvalidtimezonename
708 468 : bool IsValidTimeZoneName(const icu::TimeZone& tz) {
709 468 : UErrorCode status = U_ZERO_ERROR;
710 : icu::UnicodeString id;
711 : tz.getID(id);
712 468 : icu::UnicodeString canonical;
713 468 : icu::TimeZone::getCanonicalID(id, canonical, status);
714 936 : return U_SUCCESS(status) &&
715 1404 : canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV);
716 : }
717 :
718 3108 : std::unique_ptr<icu::TimeZone> CreateTimeZone(Isolate* isolate,
719 : const char* timezone) {
720 : // Create time zone as specified by the user. We have to re-create time zone
721 : // since calendar takes ownership.
722 3108 : if (timezone == nullptr) {
723 : // 19.a. Else / Let timeZone be DefaultTimeZone().
724 2586 : return std::unique_ptr<icu::TimeZone>(icu::TimeZone::createDefault());
725 : }
726 : std::string canonicalized =
727 1044 : JSDateTimeFormat::CanonicalizeTimeZoneID(isolate, timezone);
728 522 : if (canonicalized.empty()) return std::unique_ptr<icu::TimeZone>();
729 : std::unique_ptr<icu::TimeZone> tz(
730 468 : icu::TimeZone::createTimeZone(canonicalized.c_str()));
731 : // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
732 : // i. Throw a RangeError exception.
733 468 : if (!IsValidTimeZoneName(*tz)) return std::unique_ptr<icu::TimeZone>();
734 : return tz;
735 : }
736 :
737 3108 : std::unique_ptr<icu::Calendar> CreateCalendar(Isolate* isolate,
738 : const icu::Locale& icu_locale,
739 : const char* timezone) {
740 3108 : std::unique_ptr<icu::TimeZone> tz = CreateTimeZone(isolate, timezone);
741 3108 : if (tz.get() == nullptr) return std::unique_ptr<icu::Calendar>();
742 :
743 : // Create a calendar using locale, and apply time zone to it.
744 2964 : UErrorCode status = U_ZERO_ERROR;
745 : std::unique_ptr<icu::Calendar> calendar(
746 2964 : icu::Calendar::createInstance(tz.release(), icu_locale, status));
747 5928 : CHECK(U_SUCCESS(status));
748 2964 : CHECK_NOT_NULL(calendar.get());
749 :
750 5928 : if (calendar->getDynamicClassID() ==
751 2964 : icu::GregorianCalendar::getStaticClassID()) {
752 : icu::GregorianCalendar* gc =
753 : static_cast<icu::GregorianCalendar*>(calendar.get());
754 2235 : UErrorCode status = U_ZERO_ERROR;
755 : // The beginning of ECMAScript time, namely -(2**53)
756 : const double start_of_time = -9007199254740992;
757 2235 : gc->setGregorianChange(start_of_time, status);
758 : DCHECK(U_SUCCESS(status));
759 : }
760 : return calendar;
761 : }
762 :
763 2964 : std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormat(
764 : Isolate* isolate, const icu::Locale& icu_locale,
765 : const std::string& skeleton) {
766 : // See https://github.com/tc39/ecma402/issues/225 . The best pattern
767 : // generation needs to be done in the base locale according to the
768 : // current spec however odd it may be. See also crbug.com/826549 .
769 : // This is a temporary work-around to get v8's external behavior to match
770 : // the current spec, but does not follow the spec provisions mentioned
771 : // in the above Ecma 402 issue.
772 : // TODO(jshin): The spec may need to be revised because using the base
773 : // locale for the pattern match is not quite right. Moreover, what to
774 : // do with 'related year' part when 'chinese/dangi' calendar is specified
775 : // has to be discussed. Revisit once the spec is clarified/revised.
776 2964 : icu::Locale no_extension_locale(icu_locale.getBaseName());
777 2964 : UErrorCode status = U_ZERO_ERROR;
778 : std::unique_ptr<icu::DateTimePatternGenerator> generator(
779 : icu::DateTimePatternGenerator::createInstance(no_extension_locale,
780 2964 : status));
781 2964 : icu::UnicodeString pattern;
782 2964 : if (U_SUCCESS(status)) {
783 5928 : pattern =
784 : generator->getBestPattern(icu::UnicodeString(skeleton.c_str()), status);
785 : }
786 :
787 : // Make formatter from skeleton. Calendar and numbering system are added
788 : // to the locale as Unicode extension (if they were specified at all).
789 2964 : status = U_ZERO_ERROR;
790 : std::unique_ptr<icu::SimpleDateFormat> date_format(
791 2964 : new icu::SimpleDateFormat(pattern, icu_locale, status));
792 2964 : if (U_FAILURE(status)) return std::unique_ptr<icu::SimpleDateFormat>();
793 :
794 2964 : CHECK_NOT_NULL(date_format.get());
795 2964 : return date_format;
796 : }
797 :
798 209 : Intl::HourCycle HourCycleDefault(icu::SimpleDateFormat* date_format) {
799 : icu::UnicodeString pattern;
800 209 : date_format->toPattern(pattern);
801 209 : if (pattern.indexOf('K') >= 0) {
802 : return Intl::HourCycle::kH11;
803 200 : } else if (pattern.indexOf('h') >= 0) {
804 : return Intl::HourCycle::kH12;
805 63 : } else if (pattern.indexOf('H') >= 0) {
806 : return Intl::HourCycle::kH23;
807 0 : } else if (pattern.indexOf('k') >= 0) {
808 : return Intl::HourCycle::kH24;
809 : }
810 0 : return Intl::HourCycle::kUndefined;
811 : }
812 :
813 : } // namespace
814 :
815 : enum FormatMatcherOption { kBestFit, kBasic };
816 :
817 : // ecma402/#sec-initializedatetimeformat
818 3135 : MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
819 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
820 : Handle<Object> locales, Handle<Object> input_options) {
821 : date_time_format->set_flags(0);
822 : // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
823 : Maybe<std::vector<std::string>> maybe_requested_locales =
824 3135 : Intl::CanonicalizeLocaleList(isolate, locales);
825 3135 : MAYBE_RETURN(maybe_requested_locales, Handle<JSDateTimeFormat>());
826 : std::vector<std::string> requested_locales =
827 3126 : maybe_requested_locales.FromJust();
828 : // 2. Let options be ? ToDateTimeOptions(options, "any", "date").
829 : Handle<JSObject> options;
830 6252 : ASSIGN_RETURN_ON_EXCEPTION(
831 : isolate, options,
832 : JSDateTimeFormat::ToDateTimeOptions(
833 : isolate, input_options, RequiredOption::kAny, DefaultsOption::kDate),
834 : JSDateTimeFormat);
835 :
836 : // 4. Let matcher be ? GetOption(options, "localeMatcher", "string",
837 : // « "lookup", "best fit" », "best fit").
838 : // 5. Set opt.[[localeMatcher]] to matcher.
839 : Maybe<Intl::MatcherOption> maybe_locale_matcher =
840 3126 : Intl::GetLocaleMatcher(isolate, options, "Intl.DateTimeFormat");
841 3126 : MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
842 : Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
843 :
844 : // 6. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined,
845 : // undefined).
846 : bool hour12;
847 : Maybe<bool> maybe_get_hour12 = Intl::GetBoolOption(
848 3126 : isolate, options, "hour12", "Intl.DateTimeFormat", &hour12);
849 3126 : MAYBE_RETURN(maybe_get_hour12, Handle<JSDateTimeFormat>());
850 :
851 : // 7. Let hourCycle be ? GetOption(options, "hourCycle", "string", « "h11",
852 : // "h12", "h23", "h24" », undefined).
853 : Maybe<Intl::HourCycle> maybe_hour_cycle =
854 3126 : Intl::GetHourCycle(isolate, options, "Intl.DateTimeFormat");
855 3126 : MAYBE_RETURN(maybe_hour_cycle, MaybeHandle<JSDateTimeFormat>());
856 : Intl::HourCycle hour_cycle = maybe_hour_cycle.FromJust();
857 :
858 : // 8. If hour12 is not undefined, then
859 3126 : if (maybe_get_hour12.FromJust()) {
860 : // a. Let hourCycle be null.
861 : hour_cycle = Intl::HourCycle::kUndefined;
862 : }
863 : // 9. Set opt.[[hc]] to hourCycle.
864 :
865 : // ecma402/#sec-intl.datetimeformat-internal-slots
866 : // The value of the [[RelevantExtensionKeys]] internal slot is
867 : // « "ca", "nu", "hc" ».
868 6252 : std::set<std::string> relevant_extension_keys = {"nu", "ca", "hc"};
869 :
870 : // 10. Let localeData be %DateTimeFormat%.[[LocaleData]].
871 : // 11. Let r be ResolveLocale( %DateTimeFormat%.[[AvailableLocales]],
872 : // requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]],
873 : // localeData).
874 : //
875 : Intl::ResolvedLocale r = Intl::ResolveLocale(
876 : isolate, JSDateTimeFormat::GetAvailableLocales(), requested_locales,
877 9378 : locale_matcher, relevant_extension_keys);
878 :
879 6252 : icu::Locale icu_locale = r.icu_locale;
880 : DCHECK(!icu_locale.isBogus());
881 :
882 3126 : if (!maybe_get_hour12.FromJust() &&
883 : hour_cycle == Intl::HourCycle::kUndefined) {
884 6234 : auto hc_extension_it = r.extensions.find("hc");
885 3117 : if (hc_extension_it != r.extensions.end()) {
886 216 : hour_cycle = Intl::ToHourCycle(hc_extension_it->second.c_str());
887 : }
888 : }
889 :
890 : // 17. Let timeZone be ? Get(options, "timeZone").
891 : const std::vector<const char*> empty_values;
892 : std::unique_ptr<char[]> timezone = nullptr;
893 : Maybe<bool> maybe_timezone =
894 : Intl::GetStringOption(isolate, options, "timeZone", empty_values,
895 9378 : "Intl.DateTimeFormat", &timezone);
896 3126 : MAYBE_RETURN(maybe_timezone, Handle<JSDateTimeFormat>());
897 :
898 : std::unique_ptr<icu::Calendar> calendar(
899 3108 : CreateCalendar(isolate, icu_locale, timezone.get()));
900 :
901 : // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
902 : // i. Throw a RangeError exception.
903 3108 : if (calendar.get() == nullptr) {
904 288 : THROW_NEW_ERROR(isolate,
905 : NewRangeError(MessageTemplate::kInvalidTimeZone,
906 : isolate->factory()->NewStringFromAsciiChecked(
907 : timezone.get())),
908 : JSDateTimeFormat);
909 : }
910 :
911 : // 29. If dateTimeFormat.[[Hour]] is not undefined, then
912 2964 : if (hour_cycle == Intl::HourCycle::kUndefined) {
913 : // d. If hour12 is not undefined, then
914 2856 : if (maybe_get_hour12.FromJust()) {
915 : // i. If hour12 is true, then
916 9 : if (hour12) {
917 : hour_cycle = Intl::HourCycle::kH12;
918 : } else { // ii. Else,
919 : hour_cycle = Intl::HourCycle::kH23;
920 : }
921 : }
922 : }
923 :
924 : bool has_hour_option = false;
925 : // 22. For each row of Table 5, except the header row, do
926 : std::string skeleton;
927 32604 : for (const PatternData& item : GetPatternData(hour_cycle)) {
928 26676 : std::unique_ptr<char[]> input;
929 : // a. Let prop be the name given in the Property column of the row.
930 : // b. Let value be ? GetOption(options, prop, "string", « the strings given
931 : // in the Values column of the row », undefined).
932 : Maybe<bool> maybe_get_option = Intl::GetStringOption(
933 : isolate, options, item.property.c_str(), item.allowed_values,
934 80028 : "Intl.DateTimeFormat", &input);
935 26676 : MAYBE_RETURN(maybe_get_option, Handle<JSDateTimeFormat>());
936 26676 : if (maybe_get_option.FromJust()) {
937 17376 : if (item.property == "hour") {
938 : has_hour_option = true;
939 : }
940 : DCHECK_NOT_NULL(input.get());
941 : // c. Set opt.[[<prop>]] to value.
942 26064 : skeleton += item.map.find(input.get())->second;
943 : }
944 2964 : }
945 :
946 : enum FormatMatcherOption { kBestFit, kBasic };
947 : // We implement only best fit algorithm, but still need to check
948 : // if the formatMatcher values are in range.
949 : // 25. Let matcher be ? GetOption(options, "formatMatcher", "string",
950 : // « "basic", "best fit" », "best fit").
951 : Maybe<FormatMatcherOption> maybe_format_matcher =
952 : Intl::GetStringOption<FormatMatcherOption>(
953 : isolate, options, "formatMatcher", "Intl.DateTimeFormat",
954 : {"best fit", "basic"},
955 : {FormatMatcherOption::kBestFit, FormatMatcherOption::kBasic},
956 8892 : FormatMatcherOption::kBestFit);
957 2964 : MAYBE_RETURN(maybe_format_matcher, MaybeHandle<JSDateTimeFormat>());
958 : // TODO(ftang): uncomment the following line and handle format_matcher.
959 : // FormatMatcherOption format_matcher = maybe_format_matcher.FromJust();
960 :
961 : std::unique_ptr<icu::SimpleDateFormat> date_format(
962 2964 : CreateICUDateFormat(isolate, icu_locale, skeleton));
963 2964 : if (date_format.get() == nullptr) {
964 : // Remove extensions and try again.
965 0 : icu_locale = icu::Locale(icu_locale.getBaseName());
966 0 : date_format = CreateICUDateFormat(isolate, icu_locale, skeleton);
967 0 : if (date_format.get() == nullptr) {
968 0 : FATAL("Failed to create ICU date format, are ICU data files missing?");
969 : }
970 : }
971 :
972 : // The creation of Calendar depends on timeZone so we have to put 13 after 17.
973 : // Also date_format is not created until here.
974 : // 13. Set dateTimeFormat.[[Calendar]] to r.[[ca]].
975 5928 : date_format->adoptCalendar(calendar.release());
976 :
977 : // 29. If dateTimeFormat.[[Hour]] is not undefined, then
978 2964 : if (has_hour_option) {
979 : // a. Let hcDefault be dataLocaleData.[[hourCycle]].
980 209 : Intl::HourCycle hc_default = HourCycleDefault(date_format.get());
981 : // b. Let hc be dateTimeFormat.[[HourCycle]].
982 : Intl::HourCycle hc = hour_cycle;
983 : // c. If hc is null, then
984 209 : if (hc == Intl::HourCycle::kUndefined) {
985 : // i. Set hc to hcDefault.
986 : hc = hc_default;
987 : }
988 : // e. Set dateTimeFormat.[[HourCycle]] to hc.
989 209 : date_time_format->set_hour_cycle(hc);
990 : // 30. Else
991 : } else {
992 : // a. Set dateTimeFormat.[[HourCycle]] to undefined.
993 2755 : date_time_format->set_hour_cycle(Intl::HourCycle::kUndefined);
994 : }
995 : Handle<Managed<icu::Locale>> managed_locale =
996 2964 : Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
997 :
998 2964 : date_time_format->set_icu_locale(*managed_locale);
999 : Handle<Managed<icu::SimpleDateFormat>> managed_format =
1000 : Managed<icu::SimpleDateFormat>::FromUniquePtr(isolate, 0,
1001 5928 : std::move(date_format));
1002 2964 : date_time_format->set_icu_simple_date_format(*managed_format);
1003 :
1004 2964 : return date_time_format;
1005 : }
1006 :
1007 : namespace {
1008 :
1009 : // The list comes from third_party/icu/source/i18n/unicode/udat.h.
1010 : // They're mapped to DateTimeFormat components listed at
1011 : // https://tc39.github.io/ecma402/#sec-datetimeformat-abstracts .
1012 603 : Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) {
1013 603 : switch (field_id) {
1014 : case -1:
1015 : return isolate->factory()->literal_string();
1016 : case UDAT_YEAR_FIELD:
1017 : case UDAT_EXTENDED_YEAR_FIELD:
1018 : case UDAT_YEAR_NAME_FIELD:
1019 : return isolate->factory()->year_string();
1020 : case UDAT_MONTH_FIELD:
1021 : case UDAT_STANDALONE_MONTH_FIELD:
1022 : return isolate->factory()->month_string();
1023 : case UDAT_DATE_FIELD:
1024 : return isolate->factory()->day_string();
1025 : case UDAT_HOUR_OF_DAY1_FIELD:
1026 : case UDAT_HOUR_OF_DAY0_FIELD:
1027 : case UDAT_HOUR1_FIELD:
1028 : case UDAT_HOUR0_FIELD:
1029 : return isolate->factory()->hour_string();
1030 : case UDAT_MINUTE_FIELD:
1031 : return isolate->factory()->minute_string();
1032 : case UDAT_SECOND_FIELD:
1033 : return isolate->factory()->second_string();
1034 : case UDAT_DAY_OF_WEEK_FIELD:
1035 : case UDAT_DOW_LOCAL_FIELD:
1036 : case UDAT_STANDALONE_DAY_FIELD:
1037 : return isolate->factory()->weekday_string();
1038 : case UDAT_AM_PM_FIELD:
1039 : return isolate->factory()->dayPeriod_string();
1040 : case UDAT_TIMEZONE_FIELD:
1041 : case UDAT_TIMEZONE_RFC_FIELD:
1042 : case UDAT_TIMEZONE_GENERIC_FIELD:
1043 : case UDAT_TIMEZONE_SPECIAL_FIELD:
1044 : case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
1045 : case UDAT_TIMEZONE_ISO_FIELD:
1046 : case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
1047 : return isolate->factory()->timeZoneName_string();
1048 : case UDAT_ERA_FIELD:
1049 : return isolate->factory()->era_string();
1050 : default:
1051 : // Other UDAT_*_FIELD's cannot show up because there is no way to specify
1052 : // them via options of Intl.DateTimeFormat.
1053 0 : UNREACHABLE();
1054 : // To prevent MSVC from issuing C4715 warning.
1055 : return Handle<String>();
1056 : }
1057 : }
1058 :
1059 : } // namespace
1060 :
1061 153 : MaybeHandle<Object> JSDateTimeFormat::FormatToParts(
1062 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
1063 : double date_value) {
1064 : Factory* factory = isolate->factory();
1065 : icu::SimpleDateFormat* format =
1066 306 : date_time_format->icu_simple_date_format()->raw();
1067 153 : CHECK_NOT_NULL(format);
1068 :
1069 : icu::UnicodeString formatted;
1070 306 : icu::FieldPositionIterator fp_iter;
1071 153 : icu::FieldPosition fp;
1072 153 : UErrorCode status = U_ZERO_ERROR;
1073 153 : format->format(date_value, formatted, &fp_iter, status);
1074 153 : if (U_FAILURE(status)) {
1075 0 : THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), Object);
1076 : }
1077 :
1078 153 : Handle<JSArray> result = factory->NewJSArray(0);
1079 : int32_t length = formatted.length();
1080 153 : if (length == 0) return result;
1081 :
1082 : int index = 0;
1083 : int32_t previous_end_pos = 0;
1084 : Handle<String> substring;
1085 540 : while (fp_iter.next(fp)) {
1086 387 : int32_t begin_pos = fp.getBeginIndex();
1087 387 : int32_t end_pos = fp.getEndIndex();
1088 :
1089 387 : if (previous_end_pos < begin_pos) {
1090 432 : ASSIGN_RETURN_ON_EXCEPTION(
1091 : isolate, substring,
1092 : Intl::ToString(isolate, formatted, previous_end_pos, begin_pos),
1093 : Object);
1094 : Intl::AddElement(isolate, result, index,
1095 216 : IcuDateFieldIdToDateType(-1, isolate), substring);
1096 216 : ++index;
1097 : }
1098 774 : ASSIGN_RETURN_ON_EXCEPTION(
1099 : isolate, substring,
1100 : Intl::ToString(isolate, formatted, begin_pos, end_pos), Object);
1101 : Intl::AddElement(isolate, result, index,
1102 : IcuDateFieldIdToDateType(fp.getField(), isolate),
1103 387 : substring);
1104 : previous_end_pos = end_pos;
1105 387 : ++index;
1106 : }
1107 153 : if (previous_end_pos < length) {
1108 0 : ASSIGN_RETURN_ON_EXCEPTION(
1109 : isolate, substring,
1110 : Intl::ToString(isolate, formatted, previous_end_pos, length), Object);
1111 : Intl::AddElement(isolate, result, index,
1112 0 : IcuDateFieldIdToDateType(-1, isolate), substring);
1113 : }
1114 153 : JSObject::ValidateElements(*result);
1115 306 : return result;
1116 : }
1117 :
1118 3257 : std::set<std::string> JSDateTimeFormat::GetAvailableLocales() {
1119 3257 : int32_t num_locales = 0;
1120 : const icu::Locale* icu_available_locales =
1121 3257 : icu::DateFormat::getAvailableLocales(num_locales);
1122 3257 : return Intl::BuildLocaleSet(icu_available_locales, num_locales);
1123 : }
1124 :
1125 45 : Handle<String> JSDateTimeFormat::HourCycleAsString() const {
1126 45 : switch (hour_cycle()) {
1127 : case Intl::HourCycle::kUndefined:
1128 0 : return GetReadOnlyRoots().undefined_string_handle();
1129 : case Intl::HourCycle::kH11:
1130 0 : return GetReadOnlyRoots().h11_string_handle();
1131 : case Intl::HourCycle::kH12:
1132 36 : return GetReadOnlyRoots().h12_string_handle();
1133 : case Intl::HourCycle::kH23:
1134 54 : return GetReadOnlyRoots().h23_string_handle();
1135 : case Intl::HourCycle::kH24:
1136 0 : return GetReadOnlyRoots().h24_string_handle();
1137 : default:
1138 0 : UNREACHABLE();
1139 : }
1140 : }
1141 :
1142 : } // namespace internal
1143 183867 : } // namespace v8
|