Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
/* Intl.DateTimeFormat implementation. */
8
9
#include "builtin/intl/DateTimeFormat.h"
10
11
#include "mozilla/Assertions.h"
12
#include "mozilla/Range.h"
13
14
#include "jsfriendapi.h"
15
16
#include "builtin/Array.h"
17
#include "builtin/intl/CommonFunctions.h"
18
#include "builtin/intl/LanguageTag.h"
19
#include "builtin/intl/ScopedICUObject.h"
20
#include "builtin/intl/SharedIntlData.h"
21
#include "builtin/intl/TimeZoneDataGenerated.h"
22
#include "gc/FreeOp.h"
23
#include "js/CharacterEncoding.h"
24
#include "js/Date.h"
25
#include "js/PropertySpec.h"
26
#include "js/StableStringChars.h"
27
#include "unicode/ucal.h"
28
#include "unicode/udat.h"
29
#include "unicode/udatpg.h"
30
#include "unicode/uenum.h"
31
#include "unicode/ufieldpositer.h"
32
#include "unicode/uloc.h"
33
#include "unicode/utypes.h"
34
#include "vm/DateTime.h"
35
#include "vm/GlobalObject.h"
36
#include "vm/JSContext.h"
37
#include "vm/Runtime.h"
38
39
#include "vm/JSObject-inl.h"
40
#include "vm/NativeObject-inl.h"
41
42
using namespace js;
43
44
using JS::AutoStableStringChars;
45
using JS::ClippedTime;
46
using JS::TimeClip;
47
48
using js::intl::CallICU;
49
using js::intl::DateTimeFormatOptions;
50
using js::intl::IcuLocale;
51
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
52
using js::intl::SharedIntlData;
53
using js::intl::StringsAreEqual;
54
55
const JSClassOps DateTimeFormatObject::classOps_ = {
56
nullptr, /* addProperty */
57
nullptr, /* delProperty */
58
nullptr, /* enumerate */
59
nullptr, /* newEnumerate */
60
nullptr, /* resolve */
61
nullptr, /* mayResolve */
62
DateTimeFormatObject::finalize};
63
64
const JSClass DateTimeFormatObject::class_ = {
65
js_Object_str,
66
JSCLASS_HAS_RESERVED_SLOTS(DateTimeFormatObject::SLOT_COUNT) |
67
JSCLASS_HAS_CACHED_PROTO(JSProto_DateTimeFormat) |
68
JSCLASS_FOREGROUND_FINALIZE,
69
&DateTimeFormatObject::classOps_, &DateTimeFormatObject::classSpec_};
70
71
const JSClass& DateTimeFormatObject::protoClass_ = PlainObject::class_;
72
73
static bool dateTimeFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
74
CallArgs args = CallArgsFromVp(argc, vp);
75
args.rval().setString(cx->names().DateTimeFormat);
76
return true;
77
}
78
79
static const JSFunctionSpec dateTimeFormat_static_methods[] = {
80
JS_SELF_HOSTED_FN("supportedLocalesOf",
81
"Intl_DateTimeFormat_supportedLocalesOf", 1, 0),
82
JS_FS_END};
83
84
static const JSFunctionSpec dateTimeFormat_methods[] = {
85
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions",
86
0, 0),
87
JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 1,
88
0),
89
JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), JS_FS_END};
90
91
static const JSPropertySpec dateTimeFormat_properties[] = {
92
JS_SELF_HOSTED_GET("format", "$Intl_DateTimeFormat_format_get", 0),
93
JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END};
94
95
static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp);
96
97
const ClassSpec DateTimeFormatObject::classSpec_ = {
98
GenericCreateConstructor<DateTimeFormat, 0, gc::AllocKind::FUNCTION>,
99
GenericCreatePrototype<DateTimeFormatObject>,
100
dateTimeFormat_static_methods,
101
nullptr,
102
dateTimeFormat_methods,
103
dateTimeFormat_properties,
104
nullptr,
105
ClassSpec::DontDefineConstructor};
106
107
/**
108
* 12.2.1 Intl.DateTimeFormat([ locales [, options]])
109
*
110
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
111
*/
112
static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct,
113
DateTimeFormatOptions dtfOptions) {
114
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
115
116
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
117
JSProtoKey protoKey = dtfOptions == DateTimeFormatOptions::Standard
118
? JSProto_DateTimeFormat
119
: JSProto_Null;
120
RootedObject proto(cx);
121
if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) {
122
return false;
123
}
124
125
Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
126
dateTimeFormat = NewObjectWithClassProto<DateTimeFormatObject>(cx, proto);
127
if (!dateTimeFormat) {
128
return false;
129
}
130
131
RootedValue thisValue(
132
cx, construct ? ObjectValue(*dateTimeFormat) : args.thisv());
133
HandleValue locales = args.get(0);
134
HandleValue options = args.get(1);
135
136
// Step 3.
137
return intl::LegacyInitializeObject(
138
cx, dateTimeFormat, cx->names().InitializeDateTimeFormat, thisValue,
139
locales, options, dtfOptions, args.rval());
140
}
141
142
static bool DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
143
CallArgs args = CallArgsFromVp(argc, vp);
144
return DateTimeFormat(cx, args, args.isConstructing(),
145
DateTimeFormatOptions::Standard);
146
}
147
148
static bool MozDateTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
149
CallArgs args = CallArgsFromVp(argc, vp);
150
151
// Don't allow to call mozIntl.DateTimeFormat as a function. That way we
152
// don't need to worry how to handle the legacy initialization semantics
153
// when applied on mozIntl.DateTimeFormat.
154
if (!ThrowIfNotConstructing(cx, args, "mozIntl.DateTimeFormat")) {
155
return false;
156
}
157
158
return DateTimeFormat(cx, args, true,
159
DateTimeFormatOptions::EnableMozExtensions);
160
}
161
162
bool js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
163
CallArgs args = CallArgsFromVp(argc, vp);
164
MOZ_ASSERT(args.length() == 2);
165
MOZ_ASSERT(!args.isConstructing());
166
// intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it
167
// cannot be used with "new", but it still has to be treated as a
168
// constructor.
169
return DateTimeFormat(cx, args, true, DateTimeFormatOptions::Standard);
170
}
171
172
void js::DateTimeFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
173
MOZ_ASSERT(fop->onMainThread());
174
175
if (UDateFormat* df = obj->as<DateTimeFormatObject>().getDateFormat()) {
176
intl::RemoveICUCellMemory(fop, obj,
177
DateTimeFormatObject::EstimatedMemoryUse);
178
179
udat_close(df);
180
}
181
}
182
183
bool js::AddMozDateTimeFormatConstructor(JSContext* cx,
184
JS::Handle<JSObject*> intl) {
185
RootedObject ctor(
186
cx, GlobalObject::createConstructor(cx, MozDateTimeFormat,
187
cx->names().DateTimeFormat, 0));
188
if (!ctor) {
189
return false;
190
}
191
192
RootedObject proto(
193
cx, GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()));
194
if (!proto) {
195
return false;
196
}
197
198
if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
199
return false;
200
}
201
202
// 12.3.2
203
if (!JS_DefineFunctions(cx, ctor, dateTimeFormat_static_methods)) {
204
return false;
205
}
206
207
// 12.4.4 and 12.4.5
208
if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) {
209
return false;
210
}
211
212
// 12.4.2 and 12.4.3
213
if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties)) {
214
return false;
215
}
216
217
RootedValue ctorValue(cx, ObjectValue(*ctor));
218
return DefineDataProperty(cx, intl, cx->names().DateTimeFormat, ctorValue, 0);
219
}
220
221
static bool DefaultCalendar(JSContext* cx, const UniqueChars& locale,
222
MutableHandleValue rval) {
223
UErrorCode status = U_ZERO_ERROR;
224
UCalendar* cal = ucal_open(nullptr, 0, locale.get(), UCAL_DEFAULT, &status);
225
226
// This correctly handles nullptr |cal| when opening failed.
227
ScopedICUObject<UCalendar, ucal_close> closeCalendar(cal);
228
229
const char* calendar = ucal_getType(cal, &status);
230
if (U_FAILURE(status)) {
231
intl::ReportInternalError(cx);
232
return false;
233
}
234
235
// ICU returns old-style keyword values; map them to BCP 47 equivalents
236
calendar = uloc_toUnicodeLocaleType("ca", calendar);
237
if (!calendar) {
238
intl::ReportInternalError(cx);
239
return false;
240
}
241
242
JSString* str = NewStringCopyZ<CanGC>(cx, calendar);
243
if (!str) {
244
return false;
245
}
246
247
rval.setString(str);
248
return true;
249
}
250
251
struct CalendarAlias {
252
const char* const calendar;
253
const char* const alias;
254
};
255
256
const CalendarAlias calendarAliases[] = {{"islamic-civil", "islamicc"},
257
{"ethioaa", "ethiopic-amete-alem"}};
258
259
bool js::intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp) {
260
CallArgs args = CallArgsFromVp(argc, vp);
261
MOZ_ASSERT(args.length() == 1);
262
MOZ_ASSERT(args[0].isString());
263
264
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
265
if (!locale) {
266
return false;
267
}
268
269
RootedObject calendars(cx, NewDenseEmptyArray(cx));
270
if (!calendars) {
271
return false;
272
}
273
274
// We need the default calendar for the locale as the first result.
275
RootedValue defaultCalendar(cx);
276
if (!DefaultCalendar(cx, locale, &defaultCalendar)) {
277
return false;
278
}
279
280
if (!NewbornArrayPush(cx, calendars, defaultCalendar)) {
281
return false;
282
}
283
284
// Now get the calendars that "would make a difference", i.e., not the
285
// default.
286
UErrorCode status = U_ZERO_ERROR;
287
UEnumeration* values =
288
ucal_getKeywordValuesForLocale("ca", locale.get(), false, &status);
289
if (U_FAILURE(status)) {
290
intl::ReportInternalError(cx);
291
return false;
292
}
293
ScopedICUObject<UEnumeration, uenum_close> toClose(values);
294
295
uint32_t count = uenum_count(values, &status);
296
if (U_FAILURE(status)) {
297
intl::ReportInternalError(cx);
298
return false;
299
}
300
301
for (; count > 0; count--) {
302
const char* calendar = uenum_next(values, nullptr, &status);
303
if (U_FAILURE(status)) {
304
intl::ReportInternalError(cx);
305
return false;
306
}
307
308
// ICU returns old-style keyword values; map them to BCP 47 equivalents
309
calendar = uloc_toUnicodeLocaleType("ca", calendar);
310
if (!calendar) {
311
intl::ReportInternalError(cx);
312
return false;
313
}
314
315
JSString* jscalendar = NewStringCopyZ<CanGC>(cx, calendar);
316
if (!jscalendar) {
317
return false;
318
}
319
if (!NewbornArrayPush(cx, calendars, StringValue(jscalendar))) {
320
return false;
321
}
322
323
// ICU doesn't return calendar aliases, append them here.
324
for (const auto& calendarAlias : calendarAliases) {
325
if (StringsAreEqual(calendar, calendarAlias.calendar)) {
326
JSString* jscalendar = NewStringCopyZ<CanGC>(cx, calendarAlias.alias);
327
if (!jscalendar) {
328
return false;
329
}
330
if (!NewbornArrayPush(cx, calendars, StringValue(jscalendar))) {
331
return false;
332
}
333
}
334
}
335
}
336
337
args.rval().setObject(*calendars);
338
return true;
339
}
340
341
bool js::intl_defaultCalendar(JSContext* cx, unsigned argc, Value* vp) {
342
CallArgs args = CallArgsFromVp(argc, vp);
343
MOZ_ASSERT(args.length() == 1);
344
MOZ_ASSERT(args[0].isString());
345
346
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
347
if (!locale) {
348
return false;
349
}
350
351
return DefaultCalendar(cx, locale, args.rval());
352
}
353
354
bool js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp) {
355
CallArgs args = CallArgsFromVp(argc, vp);
356
MOZ_ASSERT(args.length() == 1);
357
MOZ_ASSERT(args[0].isString());
358
359
SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
360
361
RootedString timeZone(cx, args[0].toString());
362
RootedAtom validatedTimeZone(cx);
363
if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone)) {
364
return false;
365
}
366
367
if (validatedTimeZone) {
368
cx->markAtom(validatedTimeZone);
369
args.rval().setString(validatedTimeZone);
370
} else {
371
args.rval().setNull();
372
}
373
374
return true;
375
}
376
377
bool js::intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp) {
378
CallArgs args = CallArgsFromVp(argc, vp);
379
MOZ_ASSERT(args.length() == 1);
380
MOZ_ASSERT(args[0].isString());
381
382
SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
383
384
// Some time zone names are canonicalized differently by ICU -- handle
385
// those first:
386
RootedString timeZone(cx, args[0].toString());
387
RootedAtom ianaTimeZone(cx);
388
if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(
389
cx, timeZone, &ianaTimeZone)) {
390
return false;
391
}
392
393
if (ianaTimeZone) {
394
cx->markAtom(ianaTimeZone);
395
args.rval().setString(ianaTimeZone);
396
return true;
397
}
398
399
AutoStableStringChars stableChars(cx);
400
if (!stableChars.initTwoByte(cx, timeZone)) {
401
return false;
402
}
403
404
mozilla::Range<const char16_t> tzchars = stableChars.twoByteRange();
405
406
JSString* str = CallICU(cx, [&tzchars](UChar* chars, uint32_t size,
407
UErrorCode* status) {
408
return ucal_getCanonicalTimeZoneID(tzchars.begin().get(), tzchars.length(),
409
chars, size, nullptr, status);
410
});
411
if (!str) {
412
return false;
413
}
414
415
args.rval().setString(str);
416
return true;
417
}
418
419
bool js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp) {
420
CallArgs args = CallArgsFromVp(argc, vp);
421
MOZ_ASSERT(args.length() == 0);
422
423
// The current default might be stale, because JS::ResetTimeZone() doesn't
424
// immediately update ICU's default time zone. So perform an update if
425
// needed.
426
js::ResyncICUDefaultTimeZone();
427
428
JSString* str = CallICU(cx, ucal_getDefaultTimeZone);
429
if (!str) {
430
return false;
431
}
432
433
args.rval().setString(str);
434
return true;
435
}
436
437
bool js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) {
438
CallArgs args = CallArgsFromVp(argc, vp);
439
MOZ_ASSERT(args.length() == 0);
440
441
UErrorCode status = U_ZERO_ERROR;
442
const UChar* uTimeZone = nullptr;
443
int32_t uTimeZoneLength = 0;
444
const char* rootLocale = "";
445
UCalendar* cal =
446
ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status);
447
if (U_FAILURE(status)) {
448
intl::ReportInternalError(cx);
449
return false;
450
}
451
ScopedICUObject<UCalendar, ucal_close> toClose(cal);
452
453
int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
454
if (U_FAILURE(status)) {
455
intl::ReportInternalError(cx);
456
return false;
457
}
458
459
args.rval().setInt32(offset);
460
return true;
461
}
462
463
bool js::intl_isDefaultTimeZone(JSContext* cx, unsigned argc, Value* vp) {
464
CallArgs args = CallArgsFromVp(argc, vp);
465
MOZ_ASSERT(args.length() == 1);
466
MOZ_ASSERT(args[0].isString() || args[0].isUndefined());
467
468
// |undefined| is the default value when the Intl runtime caches haven't
469
// yet been initialized. Handle it the same way as a cache miss.
470
if (args[0].isUndefined()) {
471
args.rval().setBoolean(false);
472
return true;
473
}
474
475
// The current default might be stale, because JS::ResetTimeZone() doesn't
476
// immediately update ICU's default time zone. So perform an update if
477
// needed.
478
js::ResyncICUDefaultTimeZone();
479
480
Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
481
MOZ_ALWAYS_TRUE(chars.resize(INITIAL_CHAR_BUFFER_SIZE));
482
483
int32_t size = CallICU(cx, ucal_getDefaultTimeZone, chars);
484
if (size < 0) {
485
return false;
486
}
487
488
JSLinearString* str = args[0].toString()->ensureLinear(cx);
489
if (!str) {
490
return false;
491
}
492
493
bool equals;
494
if (str->length() == size_t(size)) {
495
JS::AutoCheckCannotGC nogc;
496
equals =
497
str->hasLatin1Chars()
498
? EqualChars(str->latin1Chars(nogc), chars.begin(), str->length())
499
: EqualChars(str->twoByteChars(nogc), chars.begin(), str->length());
500
} else {
501
equals = false;
502
}
503
504
args.rval().setBoolean(equals);
505
return true;
506
}
507
508
bool js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) {
509
CallArgs args = CallArgsFromVp(argc, vp);
510
MOZ_ASSERT(args.length() == 2);
511
MOZ_ASSERT(args[0].isString());
512
MOZ_ASSERT(args[1].isString());
513
514
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
515
if (!locale) {
516
return false;
517
}
518
519
AutoStableStringChars skeleton(cx);
520
if (!skeleton.initTwoByte(cx, args[1].toString())) {
521
return false;
522
}
523
524
mozilla::Range<const char16_t> skelChars = skeleton.twoByteRange();
525
526
SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
527
UDateTimePatternGenerator* gen =
528
sharedIntlData.getDateTimePatternGenerator(cx, locale.get());
529
if (!gen) {
530
return false;
531
}
532
533
JSString* str = CallICU(
534
cx, [gen, &skelChars](UChar* chars, uint32_t size, UErrorCode* status) {
535
return udatpg_getBestPattern(gen, skelChars.begin().get(),
536
skelChars.length(), chars, size, status);
537
});
538
if (!str) {
539
return false;
540
}
541
542
args.rval().setString(str);
543
return true;
544
}
545
546
bool js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp) {
547
CallArgs args = CallArgsFromVp(argc, vp);
548
MOZ_ASSERT(args.length() == 4);
549
MOZ_ASSERT(args[0].isString());
550
551
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
552
if (!locale) {
553
return false;
554
}
555
556
UDateFormatStyle dateStyle = UDAT_NONE;
557
UDateFormatStyle timeStyle = UDAT_NONE;
558
559
if (args[1].isString()) {
560
JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx);
561
if (!dateStyleStr) {
562
return false;
563
}
564
565
if (StringEqualsLiteral(dateStyleStr, "full")) {
566
dateStyle = UDAT_FULL;
567
} else if (StringEqualsLiteral(dateStyleStr, "long")) {
568
dateStyle = UDAT_LONG;
569
} else if (StringEqualsLiteral(dateStyleStr, "medium")) {
570
dateStyle = UDAT_MEDIUM;
571
} else if (StringEqualsLiteral(dateStyleStr, "short")) {
572
dateStyle = UDAT_SHORT;
573
} else {
574
MOZ_ASSERT_UNREACHABLE("unexpected dateStyle");
575
}
576
}
577
578
if (args[2].isString()) {
579
JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx);
580
if (!timeStyleStr) {
581
return false;
582
}
583
584
if (StringEqualsLiteral(timeStyleStr, "full")) {
585
timeStyle = UDAT_FULL;
586
} else if (StringEqualsLiteral(timeStyleStr, "long")) {
587
timeStyle = UDAT_LONG;
588
} else if (StringEqualsLiteral(timeStyleStr, "medium")) {
589
timeStyle = UDAT_MEDIUM;
590
} else if (StringEqualsLiteral(timeStyleStr, "short")) {
591
timeStyle = UDAT_SHORT;
592
} else {
593
MOZ_ASSERT_UNREACHABLE("unexpected timeStyle");
594
}
595
}
596
597
AutoStableStringChars timeZone(cx);
598
if (!timeZone.initTwoByte(cx, args[3].toString())) {
599
return false;
600
}
601
602
mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
603
604
UErrorCode status = U_ZERO_ERROR;
605
UDateFormat* df = udat_open(timeStyle, dateStyle, IcuLocale(locale.get()),
606
timeZoneChars.begin().get(),
607
timeZoneChars.length(), nullptr, -1, &status);
608
if (U_FAILURE(status)) {
609
intl::ReportInternalError(cx);
610
return false;
611
}
612
ScopedICUObject<UDateFormat, udat_close> toClose(df);
613
614
JSString* str =
615
CallICU(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) {
616
return udat_toPattern(df, false, chars, size, status);
617
});
618
if (!str) {
619
return false;
620
}
621
args.rval().setString(str);
622
return true;
623
}
624
625
/**
626
* Returns a new UDateFormat with the locale and date-time formatting options
627
* of the given DateTimeFormat.
628
*/
629
static UDateFormat* NewUDateFormat(
630
JSContext* cx, Handle<DateTimeFormatObject*> dateTimeFormat) {
631
RootedValue value(cx);
632
633
RootedObject internals(cx, intl::GetInternalsObject(cx, dateTimeFormat));
634
if (!internals) {
635
return nullptr;
636
}
637
638
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
639
return nullptr;
640
}
641
642
// ICU expects calendar and numberingSystem as Unicode locale extensions on
643
// locale.
644
645
intl::LanguageTag tag(cx);
646
{
647
JSLinearString* locale = value.toString()->ensureLinear(cx);
648
if (!locale) {
649
return nullptr;
650
}
651
652
if (!intl::LanguageTagParser::parse(cx, locale, tag)) {
653
return nullptr;
654
}
655
}
656
657
JS::RootedVector<intl::UnicodeExtensionKeyword> keywords(cx);
658
659
if (!GetProperty(cx, internals, internals, cx->names().calendar, &value)) {
660
return nullptr;
661
}
662
663
{
664
JSLinearString* calendar = value.toString()->ensureLinear(cx);
665
if (!calendar) {
666
return nullptr;
667
}
668
669
if (!keywords.emplaceBack("ca", calendar)) {
670
return nullptr;
671
}
672
}
673
674
if (!GetProperty(cx, internals, internals, cx->names().numberingSystem,
675
&value)) {
676
return nullptr;
677
}
678
679
{
680
JSLinearString* numberingSystem = value.toString()->ensureLinear(cx);
681
if (!numberingSystem) {
682
return nullptr;
683
}
684
685
if (!keywords.emplaceBack("nu", numberingSystem)) {
686
return nullptr;
687
}
688
}
689
690
// |ApplyUnicodeExtensionToTag| applies the new keywords to the front of
691
// the Unicode extension subtag. We're then relying on ICU to follow RFC
692
// 6067, which states that any trailing keywords using the same key
693
// should be ignored.
694
if (!intl::ApplyUnicodeExtensionToTag(cx, tag, keywords)) {
695
return nullptr;
696
}
697
698
UniqueChars locale = tag.toStringZ(cx);
699
if (!locale) {
700
return nullptr;
701
}
702
703
if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value)) {
704
return nullptr;
705
}
706
707
AutoStableStringChars timeZone(cx);
708
if (!timeZone.initTwoByte(cx, value.toString())) {
709
return nullptr;
710
}
711
712
mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
713
714
if (!GetProperty(cx, internals, internals, cx->names().pattern, &value)) {
715
return nullptr;
716
}
717
718
AutoStableStringChars pattern(cx);
719
if (!pattern.initTwoByte(cx, value.toString())) {
720
return nullptr;
721
}
722
723
mozilla::Range<const char16_t> patternChars = pattern.twoByteRange();
724
725
UErrorCode status = U_ZERO_ERROR;
726
UDateFormat* df =
727
udat_open(UDAT_PATTERN, UDAT_PATTERN, IcuLocale(locale.get()),
728
timeZoneChars.begin().get(), timeZoneChars.length(),
729
patternChars.begin().get(), patternChars.length(), &status);
730
if (U_FAILURE(status)) {
731
intl::ReportInternalError(cx);
732
return nullptr;
733
}
734
735
// ECMAScript requires the Gregorian calendar to be used from the beginning
736
// of ECMAScript time.
737
UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(df));
738
ucal_setGregorianChange(cal, StartOfTime, &status);
739
740
// An error here means the calendar is not Gregorian, so we don't care.
741
742
return df;
743
}
744
745
static bool intl_FormatDateTime(JSContext* cx, UDateFormat* df, ClippedTime x,
746
MutableHandleValue result) {
747
MOZ_ASSERT(x.isValid());
748
749
JSString* str =
750
CallICU(cx, [df, x](UChar* chars, int32_t size, UErrorCode* status) {
751
return udat_format(df, x.toDouble(), chars, size, nullptr, status);
752
});
753
if (!str) {
754
return false;
755
}
756
757
result.setString(str);
758
return true;
759
}
760
761
using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
762
763
static FieldType GetFieldTypeForFormatField(UDateFormatField fieldName) {
764
// See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This
765
// switch is deliberately exhaustive: cases might have to be added/removed
766
// if this code is compiled with a different ICU with more
767
// UDateFormatField enum initializers. Please guard such cases with
768
// appropriate ICU version-testing #ifdefs, should cross-version divergence
769
// occur.
770
switch (fieldName) {
771
case UDAT_ERA_FIELD:
772
return &JSAtomState::era;
773
774
case UDAT_YEAR_FIELD:
775
case UDAT_YEAR_WOY_FIELD:
776
case UDAT_EXTENDED_YEAR_FIELD:
777
return &JSAtomState::year;
778
779
case UDAT_YEAR_NAME_FIELD:
780
#ifdef NIGHTLY_BUILD
781
return &JSAtomState::yearName;
782
#else
783
// Currently restricted to Nightly.
784
return &JSAtomState::year;
785
#endif
786
787
case UDAT_MONTH_FIELD:
788
case UDAT_STANDALONE_MONTH_FIELD:
789
return &JSAtomState::month;
790
791
case UDAT_DATE_FIELD:
792
case UDAT_JULIAN_DAY_FIELD:
793
return &JSAtomState::day;
794
795
case UDAT_HOUR_OF_DAY1_FIELD:
796
case UDAT_HOUR_OF_DAY0_FIELD:
797
case UDAT_HOUR1_FIELD:
798
case UDAT_HOUR0_FIELD:
799
return &JSAtomState::hour;
800
801
case UDAT_MINUTE_FIELD:
802
return &JSAtomState::minute;
803
804
case UDAT_SECOND_FIELD:
805
return &JSAtomState::second;
806
807
case UDAT_DAY_OF_WEEK_FIELD:
808
case UDAT_STANDALONE_DAY_FIELD:
809
case UDAT_DOW_LOCAL_FIELD:
810
case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD:
811
return &JSAtomState::weekday;
812
813
case UDAT_AM_PM_FIELD:
814
return &JSAtomState::dayPeriod;
815
816
case UDAT_TIMEZONE_FIELD:
817
return &JSAtomState::timeZoneName;
818
819
case UDAT_FRACTIONAL_SECOND_FIELD:
820
#ifdef NIGHTLY_BUILD
821
return &JSAtomState::fractionalSecond;
822
#else
823
// Currently restricted to Nightly.
824
return &JSAtomState::unknown;
825
#endif
826
827
#ifndef U_HIDE_INTERNAL_API
828
case UDAT_RELATED_YEAR_FIELD:
829
# ifdef NIGHTLY_BUILD
830
return &JSAtomState::relatedYear;
831
# else
832
// Currently restricted to Nightly.
833
return &JSAtomState::unknown;
834
# endif
835
#endif
836
837
case UDAT_DAY_OF_YEAR_FIELD:
838
case UDAT_WEEK_OF_YEAR_FIELD:
839
case UDAT_WEEK_OF_MONTH_FIELD:
840
case UDAT_MILLISECONDS_IN_DAY_FIELD:
841
case UDAT_TIMEZONE_RFC_FIELD:
842
case UDAT_TIMEZONE_GENERIC_FIELD:
843
case UDAT_QUARTER_FIELD:
844
case UDAT_STANDALONE_QUARTER_FIELD:
845
case UDAT_TIMEZONE_SPECIAL_FIELD:
846
case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
847
case UDAT_TIMEZONE_ISO_FIELD:
848
case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
849
case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
850
case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
851
#ifndef U_HIDE_INTERNAL_API
852
case UDAT_TIME_SEPARATOR_FIELD:
853
#endif
854
// These fields are all unsupported.
855
return &JSAtomState::unknown;
856
857
#ifndef U_HIDE_DEPRECATED_API
858
case UDAT_FIELD_COUNT:
859
MOZ_ASSERT_UNREACHABLE(
860
"format field sentinel value returned by "
861
"iterator!");
862
#endif
863
}
864
865
MOZ_ASSERT_UNREACHABLE(
866
"unenumerated, undocumented format field returned "
867
"by iterator");
868
return nullptr;
869
}
870
871
static bool intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df,
872
ClippedTime x,
873
MutableHandleValue result) {
874
MOZ_ASSERT(x.isValid());
875
876
UErrorCode status = U_ZERO_ERROR;
877
UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
878
if (U_FAILURE(status)) {
879
intl::ReportInternalError(cx);
880
return false;
881
}
882
ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(
883
fpositer);
884
885
RootedString overallResult(cx);
886
overallResult = CallICU(
887
cx, [df, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
888
return udat_formatForFields(df, x.toDouble(), chars, size, fpositer,
889
status);
890
});
891
if (!overallResult) {
892
return false;
893
}
894
895
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
896
if (!partsArray) {
897
return false;
898
}
899
900
if (overallResult->length() == 0) {
901
// An empty string contains no parts, so avoid extra work below.
902
result.setObject(*partsArray);
903
return true;
904
}
905
906
size_t lastEndIndex = 0;
907
908
RootedObject singlePart(cx);
909
RootedValue val(cx);
910
911
auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
912
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
913
if (!singlePart) {
914
return false;
915
}
916
917
val = StringValue(cx->names().*type);
918
if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
919
return false;
920
}
921
922
JSLinearString* partSubstr = NewDependentString(
923
cx, overallResult, beginIndex, endIndex - beginIndex);
924
if (!partSubstr) {
925
return false;
926
}
927
928
val = StringValue(partSubstr);
929
if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
930
return false;
931
}
932
933
if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
934
return false;
935
}
936
937
lastEndIndex = endIndex;
938
return true;
939
};
940
941
int32_t fieldInt, beginIndexInt, endIndexInt;
942
while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt,
943
&endIndexInt)) >= 0) {
944
MOZ_ASSERT(beginIndexInt >= 0);
945
MOZ_ASSERT(endIndexInt >= 0);
946
MOZ_ASSERT(beginIndexInt <= endIndexInt,
947
"field iterator returning invalid range");
948
949
size_t beginIndex(beginIndexInt);
950
size_t endIndex(endIndexInt);
951
952
// Technically this isn't guaranteed. But it appears true in pratice,
954
// correct the documentation lapse.
955
MOZ_ASSERT(lastEndIndex <= beginIndex,
956
"field iteration didn't return fields in order start to "
957
"finish as expected");
958
959
if (FieldType type = GetFieldTypeForFormatField(
960
static_cast<UDateFormatField>(fieldInt))) {
961
if (lastEndIndex < beginIndex) {
962
if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) {
963
return false;
964
}
965
}
966
967
if (!AppendPart(type, beginIndex, endIndex)) {
968
return false;
969
}
970
}
971
}
972
973
// Append any final literal.
974
if (lastEndIndex < overallResult->length()) {
975
if (!AppendPart(&JSAtomState::literal, lastEndIndex,
976
overallResult->length())) {
977
return false;
978
}
979
}
980
981
result.setObject(*partsArray);
982
return true;
983
}
984
985
bool js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) {
986
CallArgs args = CallArgsFromVp(argc, vp);
987
MOZ_ASSERT(args.length() == 3);
988
MOZ_ASSERT(args[0].isObject());
989
MOZ_ASSERT(args[1].isNumber());
990
MOZ_ASSERT(args[2].isBoolean());
991
992
Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
993
dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
994
995
bool formatToParts = args[2].toBoolean();
996
997
ClippedTime x = TimeClip(args[1].toNumber());
998
if (!x.isValid()) {
999
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1000
JSMSG_DATE_NOT_FINITE, "DateTimeFormat",
1001
formatToParts ? "formatToParts" : "format");
1002
return false;
1003
}
1004
1005
// Obtain a cached UDateFormat object.
1006
UDateFormat* df = dateTimeFormat->getDateFormat();
1007
if (!df) {
1008
df = NewUDateFormat(cx, dateTimeFormat);
1009
if (!df) {
1010
return false;
1011
}
1012
dateTimeFormat->setDateFormat(df);
1013
1014
intl::AddICUCellMemory(dateTimeFormat,
1015
DateTimeFormatObject::EstimatedMemoryUse);
1016
}
1017
1018
// Use the UDateFormat to actually format the time stamp.
1019
return formatToParts ? intl_FormatToPartsDateTime(cx, df, x, args.rval())
1020
: intl_FormatDateTime(cx, df, x, args.rval());
1021
}