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