Commit 73c56a50 authored by Frank Tang's avatar Frank Tang Committed by Commit Bot

[Intl] Implement proposal-intl-DateTimeFormat-formatRange

https://rawgit.com/fabalbon/proposal-intl-DateTimeFormat-formatRange/master/out/

Design Doc https://goo.gl/PGUQ1d

Bug: v8:7729
Change-Id: I38b53ffdf610400b4132a25da99dac4be67bdf4b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1510574Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Commit-Queue: Frank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60418}
parent c31a45d9
......@@ -4355,6 +4355,37 @@ void Genesis::InitializeGlobal_harmony_weak_refs() {
}
#ifdef V8_INTL_SUPPORT
void Genesis::InitializeGlobal_harmony_intl_date_format_range() {
if (!FLAG_harmony_intl_date_format_range) return;
Handle<JSObject> intl = Handle<JSObject>::cast(
JSReceiver::GetProperty(
isolate(),
Handle<JSReceiver>(native_context()->global_object(), isolate()),
factory()->InternalizeUtf8String("Intl"))
.ToHandleChecked());
Handle<JSObject> date_time_format_object = Handle<JSObject>::cast(
JSReceiver::GetProperty(
isolate(), Handle<JSReceiver>(JSReceiver::cast(*intl), isolate()),
factory()->InternalizeUtf8String("DateTimeFormat"))
.ToHandleChecked());
Handle<JSFunction> date_time_format_constructor =
Handle<JSFunction>(JSFunction::cast(*date_time_format_object), isolate());
Handle<JSObject> prototype(
JSObject::cast(date_time_format_constructor->prototype()), isolate_);
SimpleInstallFunction(isolate_, prototype, "formatRange",
Builtins::kDateTimeFormatPrototypeFormatRange, 2,
false);
SimpleInstallFunction(isolate_, prototype, "formatRangeToParts",
Builtins::kDateTimeFormatPrototypeFormatRangeToParts, 2,
false);
}
void Genesis::InitializeGlobal_harmony_locale() {
if (!FLAG_harmony_locale) return;
......
......@@ -1268,6 +1268,10 @@ namespace internal {
CPP(DateTimeFormatInternalFormat) \
/* ecma402 #sec-intl.datetimeformat.prototype.format */ \
CPP(DateTimeFormatPrototypeFormat) \
/* ecma402 #sec-intl.datetimeformat.prototype.formatrange */ \
CPP(DateTimeFormatPrototypeFormatRange) \
/* ecma402 #sec-intl.datetimeformat.prototype.formatrangetoparts */ \
CPP(DateTimeFormatPrototypeFormatRangeToParts) \
/* ecma402 #sec-intl.datetimeformat.prototype.formattoparts */ \
CPP(DateTimeFormatPrototypeFormatToParts) \
/* ecma402 #sec-intl.datetimeformat.prototype.resolvedoptions */ \
......
......@@ -157,6 +157,72 @@ BUILTIN(DateTimeFormatPrototypeFormatToParts) {
isolate, JSDateTimeFormat::FormatToParts(isolate, dtf, date_value));
}
// Common code for DateTimeFormatPrototypeFormtRange(|ToParts)
template <class T>
V8_WARN_UNUSED_RESULT Object DateTimeFormatRange(
BuiltinArguments args, Isolate* isolate, const char* const method,
MaybeHandle<T> (*format)(Isolate*, Handle<JSDateTimeFormat>, double,
double)) {
// 1. Let dtf be this value.
// 2. If Type(dtf) is not Object, throw a TypeError exception.
CHECK_RECEIVER(JSObject, date_format_holder, method);
Factory* factory = isolate->factory();
// 3. If dtf does not have an [[InitializedDateTimeFormat]] internal slot,
// throw a TypeError exception.
if (!date_format_holder->IsJSDateTimeFormat()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
factory->NewStringFromAsciiChecked(method),
date_format_holder));
}
Handle<JSDateTimeFormat> dtf =
Handle<JSDateTimeFormat>::cast(date_format_holder);
// 4. If startDate is undefined or endDate is undefined, throw a RangeError
// exception.
Handle<Object> start_date = args.atOrUndefined(isolate, 1);
Handle<Object> end_date = args.atOrUndefined(isolate, 2);
if (start_date->IsUndefined(isolate) || end_date->IsUndefined(isolate)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kInvalidTimeValue));
}
// 5. Let x be ? ToNumber(startDate).
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, start_date,
Object::ToNumber(isolate, start_date));
double x = start_date->Number();
// 6. Let y be ? ToNumber(endDate).
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, end_date,
Object::ToNumber(isolate, end_date));
double y = end_date->Number();
// 7. If x is greater than y, throw a RangeError exception.
if (x > y) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kInvalidTimeValue));
}
// 8. Return ? FormatDateTimeRange(dtf, x, y)
// OR
// 8. Return ? FormatDateTimeRangeToParts(dtf, x, y).
RETURN_RESULT_OR_FAILURE(isolate, format(isolate, dtf, x, y));
}
BUILTIN(DateTimeFormatPrototypeFormatRange) {
const char* const method = "Intl.DateTimeFormat.prototype.formatRange";
HandleScope handle_scope(isolate);
return DateTimeFormatRange<String>(args, isolate, method,
JSDateTimeFormat::FormatRange);
}
BUILTIN(DateTimeFormatPrototypeFormatRangeToParts) {
const char* const method = "Intl.DateTimeFormat.prototype.formatRangeToParts";
HandleScope handle_scope(isolate);
return DateTimeFormatRange<JSArray>(args, isolate, method,
JSDateTimeFormat::FormatRangeToParts);
}
namespace {
Handle<JSFunction> CreateBoundFunction(Isolate* isolate,
Handle<JSObject> object,
......
......@@ -220,10 +220,11 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields)
V(harmony_weak_refs, "harmony weak references") \
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(harmony_intl_bigint, "BigInt.prototype.toLocaleString") \
V(harmony_intl_datetime_style, "dateStyle timeStyle for DateTimeFormat")
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(harmony_intl_bigint, "BigInt.prototype.toLocaleString") \
V(harmony_intl_datetime_style, "dateStyle timeStyle for DateTimeFormat") \
V(harmony_intl_date_format_range, "DateTimeFormat formatRange")
#else
#define HARMONY_INPROGRESS(V) HARMONY_INPROGRESS_BASE(V)
#endif
......
......@@ -2151,6 +2151,7 @@ void JSDateTimeFormat::JSDateTimeFormatVerify(Isolate* isolate) {
JSObjectVerify(isolate);
VerifyObjectField(isolate, kICULocaleOffset);
VerifyObjectField(isolate, kICUSimpleDateFormatOffset);
VerifyObjectField(isolate, kICUDateIntervalFormatOffset);
VerifyObjectField(isolate, kBoundFormatOffset);
VerifyObjectField(isolate, kFlagsOffset);
}
......
......@@ -2089,6 +2089,7 @@ void JSDateTimeFormat::JSDateTimeFormatPrint(std::ostream& os) { // NOLINT
JSObjectPrintHeader(os, *this, "JSDateTimeFormat");
os << "\n - icu locale: " << Brief(icu_locale());
os << "\n - icu simple date format: " << Brief(icu_simple_date_format());
os << "\n - icu date interval format: " << Brief(icu_date_interval_format());
os << "\n - bound format: " << Brief(bound_format());
os << "\n - hour cycle: " << HourCycleAsString();
JSObjectPrintBody(os, *this);
......
......@@ -23,6 +23,8 @@ OBJECT_CONSTRUCTORS_IMPL(JSDateTimeFormat, JSObject)
ACCESSORS(JSDateTimeFormat, icu_locale, Managed<icu::Locale>, kICULocaleOffset)
ACCESSORS(JSDateTimeFormat, icu_simple_date_format,
Managed<icu::SimpleDateFormat>, kICUSimpleDateFormatOffset)
ACCESSORS(JSDateTimeFormat, icu_date_interval_format,
Managed<icu::DateIntervalFormat>, kICUDateIntervalFormatOffset)
ACCESSORS(JSDateTimeFormat, bound_format, Object, kBoundFormatOffset)
SMI_ACCESSORS(JSDateTimeFormat, flags, kFlagsOffset)
......
......@@ -19,7 +19,9 @@
#include "src/objects/js-date-time-format-inl.h"
#include "unicode/calendar.h"
#include "unicode/dtitvfmt.h"
#include "unicode/dtptngen.h"
#include "unicode/fieldpos.h"
#include "unicode/gregocal.h"
#include "unicode/smpdtfmt.h"
#include "unicode/unistr.h"
......@@ -951,6 +953,16 @@ std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormatFromCache(
cache.Pointer()->Create(icu_locale, skeleton, generator));
}
std::unique_ptr<icu::DateIntervalFormat> CreateICUDateIntervalFormat(
const icu::Locale& icu_locale, const icu::UnicodeString& skeleton) {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::DateIntervalFormat> date_interval_format(
icu::DateIntervalFormat::createInstance(skeleton, icu_locale, status));
if (U_FAILURE(status)) return std::unique_ptr<icu::DateIntervalFormat>();
CHECK_NOT_NULL(date_interval_format.get());
return date_interval_format;
}
Intl::HourCycle HourCycleFromPattern(const icu::UnicodeString pattern) {
bool in_quote = false;
for (int32_t i = 0; i < pattern.length(); i++) {
......@@ -1085,6 +1097,18 @@ std::unique_ptr<icu::SimpleDateFormat> DateTimeStylePattern(
generator);
}
icu::UnicodeString SkeletonFromDateFormat(
const icu::SimpleDateFormat& icu_date_format) {
icu::UnicodeString pattern;
pattern = icu_date_format.toPattern(pattern);
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString skeleton =
icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
CHECK(U_SUCCESS(status));
return skeleton;
}
class DateTimePatternGeneratorCache {
public:
// Return a clone copy that the caller have to free.
......@@ -1267,6 +1291,7 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
DateTimeStyle date_style = DateTimeStyle::kUndefined;
DateTimeStyle time_style = DateTimeStyle::kUndefined;
std::unique_ptr<icu::SimpleDateFormat> icu_date_format;
std::unique_ptr<icu::DateIntervalFormat> icu_date_interval_format;
if (FLAG_harmony_intl_datetime_style) {
// 28. Let dateStyle be ? GetOption(options, "dateStyle", "string", «
......@@ -1309,6 +1334,10 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
time_style != DateTimeStyle::kUndefined) {
icu_date_format = DateTimeStylePattern(date_style, time_style, icu_locale,
hc, *generator);
if (FLAG_harmony_intl_date_format_range) {
icu_date_interval_format = CreateICUDateIntervalFormat(
icu_locale, SkeletonFromDateFormat(*icu_date_format));
}
}
}
// 33. Else,
......@@ -1362,6 +1391,10 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
FATAL("Failed to create ICU date format, are ICU data files missing?");
}
}
if (FLAG_harmony_intl_date_format_range) {
icu_date_interval_format =
CreateICUDateIntervalFormat(icu_locale, skeleton_ustr);
}
// g. If dateTimeFormat.[[Hour]] is not undefined, then
if (!has_hour_option) {
......@@ -1410,6 +1443,12 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
Managed<icu::SimpleDateFormat>::FromUniquePtr(isolate, 0,
std::move(icu_date_format));
date_time_format->set_icu_simple_date_format(*managed_format);
if (FLAG_harmony_intl_date_format_range) {
Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
Managed<icu::DateIntervalFormat>::FromUniquePtr(
isolate, 0, std::move(icu_date_interval_format));
date_time_format->set_icu_date_interval_format(*managed_interval_format);
}
return date_time_format;
}
......@@ -1468,7 +1507,7 @@ Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) {
} // namespace
MaybeHandle<Object> JSDateTimeFormat::FormatToParts(
MaybeHandle<JSArray> JSDateTimeFormat::FormatToParts(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
double date_value) {
Factory* factory = isolate->factory();
......@@ -1482,7 +1521,7 @@ MaybeHandle<Object> JSDateTimeFormat::FormatToParts(
UErrorCode status = U_ZERO_ERROR;
format->format(date_value, formatted, &fp_iter, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), Object);
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
}
Handle<JSArray> result = factory->NewJSArray(0);
......@@ -1500,14 +1539,14 @@ MaybeHandle<Object> JSDateTimeFormat::FormatToParts(
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted, previous_end_pos, begin_pos),
Object);
JSArray);
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring);
++index;
}
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted, begin_pos, end_pos), Object);
Intl::ToString(isolate, formatted, begin_pos, end_pos), JSArray);
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(fp.getField(), isolate),
substring);
......@@ -1517,7 +1556,7 @@ MaybeHandle<Object> JSDateTimeFormat::FormatToParts(
if (previous_end_pos < length) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted, previous_end_pos, length), Object);
Intl::ToString(isolate, formatted, previous_end_pos, length), JSArray);
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring);
}
......@@ -1546,5 +1585,76 @@ Handle<String> JSDateTimeFormat::HourCycleAsString() const {
}
}
MaybeHandle<String> JSDateTimeFormat::FormatRange(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
double y) {
// TODO(ftang): Merge the following with FormatRangeToParts after
// the landing of ICU64 to make it cleaner.
// #sec-partitiondatetimerangepattern
// 1. Let x be TimeClip(x).
x = DateCache::TimeClip(x);
// 2. If x is NaN, throw a RangeError exception.
if (std::isnan(x)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
String);
}
// 3. Let y be TimeClip(y).
y = DateCache::TimeClip(y);
// 4. If y is NaN, throw a RangeError exception.
if (std::isnan(y)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
String);
}
icu::DateIntervalFormat* date_interval_format =
date_time_format->icu_date_interval_format()->raw();
CHECK_NOT_NULL(date_interval_format);
icu::DateInterval interval(x, y);
icu::UnicodeString result;
icu::FieldPosition fpos;
UErrorCode status = U_ZERO_ERROR;
date_interval_format->format(&interval, result, fpos, status);
CHECK(U_SUCCESS(status));
return Intl::ToString(isolate, result);
}
MaybeHandle<JSArray> JSDateTimeFormat::FormatRangeToParts(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
double y) {
// TODO(ftang): Merge the following with FormatRangeToParts after
// the landing of ICU64 to make it cleaner.
// #sec-partitiondatetimerangepattern
// 1. Let x be TimeClip(x).
x = DateCache::TimeClip(x);
// 2. If x is NaN, throw a RangeError exception.
if (std::isnan(x)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
JSArray);
}
// 3. Let y be TimeClip(y).
y = DateCache::TimeClip(y);
// 4. If y is NaN, throw a RangeError exception.
if (std::isnan(y)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
JSArray);
}
icu::DateIntervalFormat* date_interval_format =
date_time_format->icu_date_interval_format()->raw();
CHECK_NOT_NULL(date_interval_format);
Factory* factory = isolate->factory();
Handle<JSArray> result = factory->NewJSArray(0);
// TODO(ftang) To be implemented after ICU64 landed that support
// DateIntervalFormat::formatToValue() and FormattedDateInterval.
JSObject::ValidateElements(*result);
return result;
}
} // namespace internal
} // namespace v8
......@@ -21,6 +21,7 @@
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class DateIntervalFormat;
class Locale;
class SimpleDateFormat;
} // namespace U_ICU_NAMESPACE
......@@ -56,10 +57,21 @@ class JSDateTimeFormat : public JSObject {
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
Handle<Object> date);
V8_WARN_UNUSED_RESULT static MaybeHandle<Object> FormatToParts(
// ecma402/#sec-Intl.DateTimeFormat.prototype.formatToParts
V8_WARN_UNUSED_RESULT static MaybeHandle<JSArray> FormatToParts(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
double date_value);
// ecma402/#sec-intl.datetimeformat.prototype.formatRange
V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormatRange(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
double x_date_value, double y_date_value);
// ecma402/sec-Intl.DateTimeFormat.prototype.formatRangeToParts
V8_WARN_UNUSED_RESULT static MaybeHandle<JSArray> FormatRangeToParts(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
double x_date_value, double y_date_value);
// ecma-402/#sec-todatetimeoptions
enum class RequiredOption { kDate, kTime, kAny };
enum class DefaultsOption { kDate, kTime, kAll };
......@@ -80,12 +92,13 @@ class JSDateTimeFormat : public JSObject {
enum class DateTimeStyle { kUndefined, kFull, kLong, kMedium, kShort };
// Layout description.
#define JS_DATE_TIME_FORMAT_FIELDS(V) \
V(kICULocaleOffset, kTaggedSize) \
V(kICUSimpleDateFormatOffset, kTaggedSize) \
V(kBoundFormatOffset, kTaggedSize) \
V(kFlagsOffset, kTaggedSize) \
/* Total size. */ \
#define JS_DATE_TIME_FORMAT_FIELDS(V) \
V(kICULocaleOffset, kTaggedSize) \
V(kICUSimpleDateFormatOffset, kTaggedSize) \
V(kICUDateIntervalFormatOffset, kTaggedSize) \
V(kBoundFormatOffset, kTaggedSize) \
V(kFlagsOffset, kTaggedSize) \
/* Total size. */ \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize,
......@@ -130,6 +143,7 @@ class JSDateTimeFormat : public JSObject {
DECL_ACCESSORS(icu_locale, Managed<icu::Locale>)
DECL_ACCESSORS(icu_simple_date_format, Managed<icu::SimpleDateFormat>)
DECL_ACCESSORS(icu_date_interval_format, Managed<icu::DateIntervalFormat>)
DECL_ACCESSORS(bound_format, Object)
DECL_INT_ACCESSORS(flags)
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-date-format-range
let descriptor = Object.getOwnPropertyDescriptor(
Intl.DateTimeFormat.prototype, "formatRangeToParts");
assertTrue(descriptor.writable);
assertFalse(descriptor.enumerable);
assertTrue(descriptor.configurable);
const date1 = new Date("2019-1-3");
const date2 = new Date("2019-3-4");
const dtf = new Intl.DateTimeFormat();
assertThrows(() => dtf.formatRangeToParts(), RangeError);
assertThrows(() => dtf.formatRangeToParts(date1), RangeError);
assertThrows(() => dtf.formatRangeToParts(undefined, date2), RangeError);
assertThrows(() => dtf.formatRangeToParts(date1, undefined), RangeError);
assertThrows(() => dtf.formatRangeToParts("2019-1-3", date2), RangeError);
assertThrows(() => dtf.formatRangeToParts(date1, "2019-5-4"), RangeError);
assertThrows(() => dtf.formatRangeToParts(date2, date1), RangeError);
assertDoesNotThrow(() =>dtf.formatRangeToParts(date1, date2));
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-date-format-range
let descriptor = Object.getOwnPropertyDescriptor(
Intl.DateTimeFormat.prototype, "formatRange");
assertTrue(descriptor.writable);
assertFalse(descriptor.enumerable);
assertTrue(descriptor.configurable);
const date1 = new Date("2019-1-3");
const date2 = new Date("2019-1-5");
const date3 = new Date("2019-3-4");
const date4 = new Date("2020-3-4");
var dtf = new Intl.DateTimeFormat(["en"]);
assertThrows(() => dtf.formatRange(), RangeError);
assertThrows(() => dtf.formatRange(date1), RangeError);
assertThrows(() => dtf.formatRange(undefined, date2), RangeError);
assertThrows(() => dtf.formatRange(date1, undefined), RangeError);
assertThrows(() => dtf.formatRange("2019-1-3", date2), RangeError);
assertThrows(() => dtf.formatRange(date1, "2019-5-4"), RangeError);
assertThrows(() => dtf.formatRange(date2, date1), RangeError);
assertDoesNotThrow(() =>dtf.formatRange(date1, date2));
assertEquals("1/3/2019 – 1/5/2019", dtf.formatRange(date1, date2));
assertEquals("1/3/2019 – 3/4/2019", dtf.formatRange(date1, date3));
assertEquals("1/3/2019 – 3/4/2020", dtf.formatRange(date1, date4));
assertEquals("1/5/2019 – 3/4/2019", dtf.formatRange(date2, date3));
assertEquals("1/5/2019 – 3/4/2020", dtf.formatRange(date2, date4));
assertEquals("3/4/2019 – 3/4/2020", dtf.formatRange(date3, date4));
dtf = new Intl.DateTimeFormat(["en"], {year: "numeric", month: "short", day: "numeric"});
assertEquals("Jan 3 – 5, 2019", dtf.formatRange(date1, date2));
assertEquals("Jan 3 – Mar 4, 2019", dtf.formatRange(date1, date3));
assertEquals("Jan 3, 2019 – Mar 4, 2020", dtf.formatRange(date1, date4));
assertEquals("Jan 5 – Mar 4, 2019", dtf.formatRange(date2, date3));
assertEquals("Jan 5, 2019 – Mar 4, 2020", dtf.formatRange(date2, date4));
assertEquals("Mar 4, 2019 – Mar 4, 2020", dtf.formatRange(date3, date4));
// Test the sequence of ToNumber and TimeClip
var secondDateAccessed = false;
assertThrows(
() =>
dtf.formatRange(
new Date(864000000*10000000 + 1), // a date will cause TimeClip return NaN
{ get [Symbol.toPrimitive]() { secondDateAccessed = true; return {}} }),
TypeError);
assertTrue(secondDateAccessed);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment