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
/* Implementation of the Intl.RelativeTimeFormat proposal. */
8
9
#include "builtin/intl/RelativeTimeFormat.h"
10
11
#include "mozilla/Assertions.h"
12
#include "mozilla/FloatingPoint.h"
13
14
#include "builtin/intl/CommonFunctions.h"
15
#include "builtin/intl/NumberFormat.h"
16
#include "builtin/intl/ScopedICUObject.h"
17
#include "gc/FreeOp.h"
18
#include "js/CharacterEncoding.h"
19
#include "js/PropertySpec.h"
20
#include "unicode/udisplaycontext.h"
21
#include "unicode/uloc.h"
22
#include "unicode/ureldatefmt.h"
23
#include "unicode/utypes.h"
24
#include "vm/GlobalObject.h"
25
#include "vm/JSContext.h"
26
#include "vm/Printer.h"
27
#include "vm/StringType.h"
28
29
#include "vm/NativeObject-inl.h"
30
31
using namespace js;
32
33
using js::intl::CallICU;
34
using js::intl::IcuLocale;
35
36
/**************** RelativeTimeFormat *****************/
37
38
const JSClassOps RelativeTimeFormatObject::classOps_ = {
39
nullptr, // addProperty
40
nullptr, // delProperty
41
nullptr, // enumerate
42
nullptr, // newEnumerate
43
nullptr, // resolve
44
nullptr, // mayResolve
45
RelativeTimeFormatObject::finalize, // finalize
46
nullptr, // call
47
nullptr, // hasInstance
48
nullptr, // construct
49
nullptr, // trace
50
};
51
52
const JSClass RelativeTimeFormatObject::class_ = {
53
js_Object_str,
54
JSCLASS_HAS_RESERVED_SLOTS(RelativeTimeFormatObject::SLOT_COUNT) |
55
JSCLASS_HAS_CACHED_PROTO(JSProto_RelativeTimeFormat) |
56
JSCLASS_FOREGROUND_FINALIZE,
57
&RelativeTimeFormatObject::classOps_,
58
&RelativeTimeFormatObject::classSpec_};
59
60
const JSClass& RelativeTimeFormatObject::protoClass_ = PlainObject::class_;
61
62
static bool relativeTimeFormat_toSource(JSContext* cx, unsigned argc,
63
Value* vp) {
64
CallArgs args = CallArgsFromVp(argc, vp);
65
args.rval().setString(cx->names().RelativeTimeFormat);
66
return true;
67
}
68
69
static const JSFunctionSpec relativeTimeFormat_static_methods[] = {
70
JS_SELF_HOSTED_FN("supportedLocalesOf",
71
"Intl_RelativeTimeFormat_supportedLocalesOf", 1, 0),
72
JS_FS_END};
73
74
static const JSFunctionSpec relativeTimeFormat_methods[] = {
75
JS_SELF_HOSTED_FN("resolvedOptions",
76
"Intl_RelativeTimeFormat_resolvedOptions", 0, 0),
77
JS_SELF_HOSTED_FN("format", "Intl_RelativeTimeFormat_format", 2, 0),
78
#ifndef U_HIDE_DRAFT_API
79
JS_SELF_HOSTED_FN("formatToParts", "Intl_RelativeTimeFormat_formatToParts",
80
2, 0),
81
#endif
82
JS_FN(js_toSource_str, relativeTimeFormat_toSource, 0, 0), JS_FS_END};
83
84
static const JSPropertySpec relativeTimeFormat_properties[] = {
85
JS_STRING_SYM_PS(toStringTag, "Intl.RelativeTimeFormat", JSPROP_READONLY),
86
JS_PS_END};
87
88
static bool RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp);
89
90
const ClassSpec RelativeTimeFormatObject::classSpec_ = {
91
GenericCreateConstructor<RelativeTimeFormat, 0, gc::AllocKind::FUNCTION>,
92
GenericCreatePrototype<RelativeTimeFormatObject>,
93
relativeTimeFormat_static_methods,
94
nullptr,
95
relativeTimeFormat_methods,
96
relativeTimeFormat_properties,
97
nullptr,
98
ClassSpec::DontDefineConstructor};
99
100
/**
101
* RelativeTimeFormat constructor.
102
* Spec: ECMAScript 402 API, RelativeTimeFormat, 1.1
103
*/
104
static bool RelativeTimeFormat(JSContext* cx, unsigned argc, Value* vp) {
105
CallArgs args = CallArgsFromVp(argc, vp);
106
107
// Step 1.
108
if (!ThrowIfNotConstructing(cx, args, "Intl.RelativeTimeFormat")) {
109
return false;
110
}
111
112
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
113
RootedObject proto(cx);
114
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_RelativeTimeFormat,
115
&proto)) {
116
return false;
117
}
118
119
Rooted<RelativeTimeFormatObject*> relativeTimeFormat(cx);
120
relativeTimeFormat =
121
NewObjectWithClassProto<RelativeTimeFormatObject>(cx, proto);
122
if (!relativeTimeFormat) {
123
return false;
124
}
125
126
HandleValue locales = args.get(0);
127
HandleValue options = args.get(1);
128
129
// Step 3.
130
if (!intl::InitializeObject(cx, relativeTimeFormat,
131
cx->names().InitializeRelativeTimeFormat, locales,
132
options)) {
133
return false;
134
}
135
136
args.rval().setObject(*relativeTimeFormat);
137
return true;
138
}
139
140
void js::RelativeTimeFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
141
MOZ_ASSERT(fop->onMainThread());
142
143
if (URelativeDateTimeFormatter* rtf =
144
obj->as<RelativeTimeFormatObject>().getRelativeDateTimeFormatter()) {
145
intl::RemoveICUCellMemory(fop, obj,
146
RelativeTimeFormatObject::EstimatedMemoryUse);
147
148
ureldatefmt_close(rtf);
149
}
150
}
151
152
/**
153
* Returns a new URelativeDateTimeFormatter with the locale and options of the
154
* given RelativeTimeFormatObject.
155
*/
156
static URelativeDateTimeFormatter* NewURelativeDateTimeFormatter(
157
JSContext* cx, Handle<RelativeTimeFormatObject*> relativeTimeFormat) {
158
RootedObject internals(cx, intl::GetInternalsObject(cx, relativeTimeFormat));
159
if (!internals) {
160
return nullptr;
161
}
162
163
RootedValue value(cx);
164
165
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
166
return nullptr;
167
}
168
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
169
if (!locale) {
170
return nullptr;
171
}
172
173
if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
174
return nullptr;
175
}
176
177
UDateRelativeDateTimeFormatterStyle relDateTimeStyle;
178
{
179
JSLinearString* style = value.toString()->ensureLinear(cx);
180
if (!style) {
181
return nullptr;
182
}
183
184
if (StringEqualsLiteral(style, "short")) {
185
relDateTimeStyle = UDAT_STYLE_SHORT;
186
} else if (StringEqualsLiteral(style, "narrow")) {
187
relDateTimeStyle = UDAT_STYLE_NARROW;
188
} else {
189
MOZ_ASSERT(StringEqualsLiteral(style, "long"));
190
relDateTimeStyle = UDAT_STYLE_LONG;
191
}
192
}
193
194
UErrorCode status = U_ZERO_ERROR;
195
URelativeDateTimeFormatter* rtf =
196
ureldatefmt_open(IcuLocale(locale.get()), nullptr, relDateTimeStyle,
197
UDISPCTX_CAPITALIZATION_FOR_STANDALONE, &status);
198
if (U_FAILURE(status)) {
199
intl::ReportInternalError(cx);
200
return nullptr;
201
}
202
return rtf;
203
}
204
205
enum class RelativeTimeNumeric {
206
/**
207
* Only strings with numeric components like `1 day ago`.
208
*/
209
Always,
210
/**
211
* Natural-language strings like `yesterday` when possible,
212
* otherwise strings with numeric components as in `7 months ago`.
213
*/
214
Auto,
215
};
216
217
static bool intl_FormatRelativeTime(JSContext* cx,
218
URelativeDateTimeFormatter* rtf, double t,
219
URelativeDateTimeUnit unit,
220
RelativeTimeNumeric numeric,
221
MutableHandleValue result) {
222
JSString* str = CallICU(
223
cx,
224
[rtf, t, unit, numeric](UChar* chars, int32_t size, UErrorCode* status) {
225
auto fmt = numeric == RelativeTimeNumeric::Auto
226
? ureldatefmt_format
227
: ureldatefmt_formatNumeric;
228
return fmt(rtf, t, unit, chars, size, status);
229
});
230
if (!str) {
231
return false;
232
}
233
234
result.setString(str);
235
return true;
236
}
237
238
#ifndef U_HIDE_DRAFT_API
239
static bool intl_FormatToPartsRelativeTime(JSContext* cx,
240
URelativeDateTimeFormatter* rtf,
241
double t, URelativeDateTimeUnit unit,
242
RelativeTimeNumeric numeric,
243
MutableHandleValue result) {
244
UErrorCode status = U_ZERO_ERROR;
245
UFormattedRelativeDateTime* formatted = ureldatefmt_openResult(&status);
246
if (U_FAILURE(status)) {
247
intl::ReportInternalError(cx);
248
return false;
249
}
250
ScopedICUObject<UFormattedRelativeDateTime, ureldatefmt_closeResult> toClose(
251
formatted);
252
253
if (numeric == RelativeTimeNumeric::Auto) {
254
ureldatefmt_formatToResult(rtf, t, unit, formatted, &status);
255
} else {
256
ureldatefmt_formatNumericToResult(rtf, t, unit, formatted, &status);
257
}
258
if (U_FAILURE(status)) {
259
intl::ReportInternalError(cx);
260
return false;
261
}
262
263
const UFormattedValue* formattedValue =
264
ureldatefmt_resultAsValue(formatted, &status);
265
if (U_FAILURE(status)) {
266
intl::ReportInternalError(cx);
267
return false;
268
}
269
270
intl::FieldType unitType;
271
switch (unit) {
272
case UDAT_REL_UNIT_SECOND:
273
unitType = &JSAtomState::second;
274
break;
275
case UDAT_REL_UNIT_MINUTE:
276
unitType = &JSAtomState::minute;
277
break;
278
case UDAT_REL_UNIT_HOUR:
279
unitType = &JSAtomState::hour;
280
break;
281
case UDAT_REL_UNIT_DAY:
282
unitType = &JSAtomState::day;
283
break;
284
case UDAT_REL_UNIT_WEEK:
285
unitType = &JSAtomState::week;
286
break;
287
case UDAT_REL_UNIT_MONTH:
288
unitType = &JSAtomState::month;
289
break;
290
case UDAT_REL_UNIT_QUARTER:
291
unitType = &JSAtomState::quarter;
292
break;
293
case UDAT_REL_UNIT_YEAR:
294
unitType = &JSAtomState::year;
295
break;
296
default:
297
MOZ_CRASH("unexpected relative time unit");
298
}
299
300
Value tval = DoubleValue(t);
301
return intl::FormattedNumberToParts(cx, formattedValue,
302
HandleValue::fromMarkedLocation(&tval),
303
unitType, result);
304
}
305
#endif
306
307
bool js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) {
308
CallArgs args = CallArgsFromVp(argc, vp);
309
MOZ_ASSERT(args.length() == 5);
310
311
Rooted<RelativeTimeFormatObject*> relativeTimeFormat(cx);
312
relativeTimeFormat = &args[0].toObject().as<RelativeTimeFormatObject>();
313
314
bool formatToParts = args[4].toBoolean();
315
316
// PartitionRelativeTimePattern, step 4.
317
double t = args[1].toNumber();
318
if (!mozilla::IsFinite(t)) {
319
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
320
JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat",
321
formatToParts ? "formatToParts" : "format");
322
return false;
323
}
324
325
// Obtain a cached URelativeDateTimeFormatter object.
326
URelativeDateTimeFormatter* rtf =
327
relativeTimeFormat->getRelativeDateTimeFormatter();
328
if (!rtf) {
329
rtf = NewURelativeDateTimeFormatter(cx, relativeTimeFormat);
330
if (!rtf) {
331
return false;
332
}
333
relativeTimeFormat->setRelativeDateTimeFormatter(rtf);
334
335
intl::AddICUCellMemory(relativeTimeFormat,
336
RelativeTimeFormatObject::EstimatedMemoryUse);
337
}
338
339
URelativeDateTimeUnit relDateTimeUnit;
340
{
341
JSLinearString* unit = args[2].toString()->ensureLinear(cx);
342
if (!unit) {
343
return false;
344
}
345
346
// PartitionRelativeTimePattern, step 5.
347
if (StringEqualsLiteral(unit, "second") ||
348
StringEqualsLiteral(unit, "seconds")) {
349
relDateTimeUnit = UDAT_REL_UNIT_SECOND;
350
} else if (StringEqualsLiteral(unit, "minute") ||
351
StringEqualsLiteral(unit, "minutes")) {
352
relDateTimeUnit = UDAT_REL_UNIT_MINUTE;
353
} else if (StringEqualsLiteral(unit, "hour") ||
354
StringEqualsLiteral(unit, "hours")) {
355
relDateTimeUnit = UDAT_REL_UNIT_HOUR;
356
} else if (StringEqualsLiteral(unit, "day") ||
357
StringEqualsLiteral(unit, "days")) {
358
relDateTimeUnit = UDAT_REL_UNIT_DAY;
359
} else if (StringEqualsLiteral(unit, "week") ||
360
StringEqualsLiteral(unit, "weeks")) {
361
relDateTimeUnit = UDAT_REL_UNIT_WEEK;
362
} else if (StringEqualsLiteral(unit, "month") ||
363
StringEqualsLiteral(unit, "months")) {
364
relDateTimeUnit = UDAT_REL_UNIT_MONTH;
365
} else if (StringEqualsLiteral(unit, "quarter") ||
366
StringEqualsLiteral(unit, "quarters")) {
367
relDateTimeUnit = UDAT_REL_UNIT_QUARTER;
368
} else if (StringEqualsLiteral(unit, "year") ||
369
StringEqualsLiteral(unit, "years")) {
370
relDateTimeUnit = UDAT_REL_UNIT_YEAR;
371
} else {
372
if (auto unitChars = QuoteString(cx, unit, '"')) {
373
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
374
JSMSG_INVALID_OPTION_VALUE, "unit",
375
unitChars.get());
376
}
377
return false;
378
}
379
}
380
381
RelativeTimeNumeric relDateTimeNumeric;
382
{
383
JSLinearString* numeric = args[3].toString()->ensureLinear(cx);
384
if (!numeric) {
385
return false;
386
}
387
388
if (StringEqualsLiteral(numeric, "auto")) {
389
relDateTimeNumeric = RelativeTimeNumeric::Auto;
390
} else {
391
MOZ_ASSERT(StringEqualsLiteral(numeric, "always"));
392
relDateTimeNumeric = RelativeTimeNumeric::Always;
393
}
394
}
395
396
#ifndef U_HIDE_DRAFT_API
397
return formatToParts
398
? intl_FormatToPartsRelativeTime(cx, rtf, t, relDateTimeUnit,
399
relDateTimeNumeric, args.rval())
400
: intl_FormatRelativeTime(cx, rtf, t, relDateTimeUnit,
401
relDateTimeNumeric, args.rval());
402
#else
403
MOZ_ASSERT(!formatToParts);
404
return intl_FormatRelativeTime(cx, rtf, t, relDateTimeUnit,
405
relDateTimeNumeric, args.rval());
406
#endif
407
}