Skip to content

Commit

Permalink
Add ES9.0 compliant Date toString functions.
Browse files Browse the repository at this point in the history
Summary:
ES9.0 changes the requirements for the `Date.prototype.toString()`
and `Date.prototype.toUTCString()` functions, which now have a specific
format including the weekday, a more human-readable date and time zone.

Reviewed By: dulinriley

Differential Revision: D16343025

fbshipit-source-id: 405f6a5446e98d56463786deff7c875de21b064d
  • Loading branch information
avp authored and facebook-github-bot committed Jul 23, 2019
1 parent 566725b commit a38ec5f
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 46 deletions.
58 changes: 46 additions & 12 deletions include/hermes/VM/JSLib/DateUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ uint32_t monthFromTime(double t);
double dateFromTime(double t);

//===----------------------------------------------------------------------===//
// ES5.1 15.9.1.6
// ES9.0 20.3.1.6

/// Given a timestamp \p t in milliseconds, get the day of the week it is in.
/// \return 0 for Sunday, 1 for Monday, etc.
double weekDay(double t);
int32_t weekDay(double t);

//===----------------------------------------------------------------------===//
// ES5.1 15.9.1.7
Expand Down Expand Up @@ -171,19 +171,17 @@ double timeClip(double t);

/// Creates an ISO 8601 format date string.
/// The string is of the format YYYY-MM-DD.
/// Note: The output of this function is implementation-defined by spec.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza unused, placed for compatibility with the other toStrings.
/// \param buf the buffer to be populated with the resultant string.
void dateToString(double t, double tza, llvm::SmallVectorImpl<char> &buf);
void dateToISOString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

/// Creates an ISO 8601 format time string.
/// The string is of the format HH:mm:ss.sssZ where Z is timezone.
/// Note: The output of this function is implementation-defined by spec.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza time zone adjustment in milliseconds, used for the TZ.
/// \param buf the buffer to be populated with the resultant string.
void timeToString(double t, double tza, llvm::SmallVectorImpl<char> &buf);
void timeToISOString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

/// Creates an ISO 8601 format string, in the provided timezone.
/// The string is of the format YYYY-MM-DDTHH:mm:ss.sssZ where Z is timezone.
Expand All @@ -197,18 +195,54 @@ void datetimeToISOString(
double tza,
llvm::SmallVectorImpl<char> &buf);

/// Same as datetimeToISOString, but uses space instead of 'T' as the separator.
void datetimeToUTCString(
double t,
double tza,
llvm::SmallVectorImpl<char> &buf);

void datetimeToLocaleString(double t, llvm::SmallVectorImpl<char16_t> &buf);

void dateToLocaleString(double t, llvm::SmallVectorImpl<char16_t> &buf);

void timeToLocaleString(double t, llvm::SmallVectorImpl<char16_t> &buf);

/// ES9.0 20.3.4.41.2 DateString
/// Returns a spec-compliant string representing the date of \p t.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza unused, placed for compatibility with the other toStrings.
/// \param buf the buffer to be populated with the resultant string.
void dateString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

/// ES9.0 20.3.4.41.1 TimeString
/// Returns a spec-compliant string representing the time of \p t.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza time zone adjustment in milliseconds, used for the TZ.
/// \param buf the buffer to be populated with the resultant string.
void timeString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

/// ES9.0 20.3.4.41.3 TimeZoneString
/// Returns a spec-compliant string representing the timezone of \p t.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza time zone adjustment in milliseconds, used for the TZ.
/// \param buf the buffer to be populated with the resultant string.
void timeZoneString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

/// ES9.0 20.3.4.41.4 ToDateString
/// Returns a spec-compliant string representing the datetime of \p t.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza unused, placed for compatibility with the other toStrings.
/// \param buf the buffer to be populated with the resultant string.
void dateTimeString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

/// ES9.0 20.3.4.43 Main logic of Date.prototype.toUTCString.
/// Returns a spec-compliant string representing the datetime of \p t.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza unused, placed for compatibility with the other toStrings.
/// \param buf the buffer to be populated with the resultant string.
void dateTimeUTCString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

/// ES9.0 20.3.4.42 ToTimeString
/// Returns a spec-compliant string representing only the time of \p t.
/// \param t timestamp in milliseconds since Jan 1 1970.
/// \param tza unused, placed for compatibility with the other toStrings.
/// \param buf the buffer to be populated with the resultant string.
void timeTZString(double t, double tza, llvm::SmallVectorImpl<char> &buf);

//===----------------------------------------------------------------------===//
// Date parsing

Expand Down
10 changes: 5 additions & 5 deletions lib/VM/JSLib/Date.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ dateConstructor(void *, Runtime *runtime, NativeArgs args) {
#endif
double t = curTime();
double local = localTime(t);
datetimeToUTCString(local, local - t, str);
dateTimeString(local, local - t, str);
#ifdef HERMESVM_SYNTH_REPLAY
}
#endif
Expand Down Expand Up @@ -628,11 +628,11 @@ dateNow(void *, Runtime *runtime, NativeArgs args) {
static CallResult<HermesValue>
datePrototypeToStringHelper(void *ctx, Runtime *runtime, NativeArgs args) {
static ToStringOptions toStringOptions[] = {
{datetimeToISOString, false, false},
{dateToString, false, false},
{timeToString, false, false},
{dateTimeString, false, false},
{dateString, false, false},
{timeTZString, false, false},
{datetimeToISOString, true, true},
{datetimeToUTCString, true, false},
{dateTimeUTCString, true, false},
};
assert(
(uint64_t)ctx < (uint64_t)ToStringKind::NumKinds &&
Expand Down
120 changes: 106 additions & 14 deletions lib/VM/JSLib/DateUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ double dateFromTime(double t) {
//===----------------------------------------------------------------------===//
// ES5.1 15.9.1.6

double weekDay(double t) {
int32_t weekDay(double t) {
return posfmod((day(t) + 4), 7);
}

Expand Down Expand Up @@ -502,7 +502,7 @@ double timeClip(double t) {
//===----------------------------------------------------------------------===//
// toString Functions

void dateToString(double t, double, llvm::SmallVectorImpl<char> &buf) {
void dateToISOString(double t, double, llvm::SmallVectorImpl<char> &buf) {
llvm::raw_svector_ostream os{buf};

/// Make these ints here because we're printing and we have bounds on
Expand All @@ -519,7 +519,7 @@ void dateToString(double t, double, llvm::SmallVectorImpl<char> &buf) {
}
}

void timeToString(double t, double tza, llvm::SmallVectorImpl<char> &buf) {
void timeToISOString(double t, double tza, llvm::SmallVectorImpl<char> &buf) {
llvm::raw_svector_ostream os{buf};

/// Make all of these ints here because we're printing and we have bounds on
Expand All @@ -544,28 +544,21 @@ void timeToString(double t, double tza, llvm::SmallVectorImpl<char> &buf) {
}
}

static void datetimeToString(
static void datetimeToISOString(
double t,
double tza,
llvm::SmallVectorImpl<char> &buf,
char separator) {
dateToString(t, tza, buf);
dateToISOString(t, tza, buf);
buf.push_back(separator);
timeToString(t, tza, buf);
timeToISOString(t, tza, buf);
}

void datetimeToISOString(
double t,
double tza,
llvm::SmallVectorImpl<char> &buf) {
return datetimeToString(t, tza, buf, 'T');
}

void datetimeToUTCString(
double t,
double tza,
llvm::SmallVectorImpl<char> &buf) {
return datetimeToString(t, tza, buf, ' ');
return datetimeToISOString(t, tza, buf, 'T');
}

void datetimeToLocaleString(double t, llvm::SmallVectorImpl<char16_t> &buf) {
Expand Down Expand Up @@ -607,6 +600,105 @@ static const char *const monthNames[12]{
"Dec",
};

void dateString(double t, double, llvm::SmallVectorImpl<char> &buf) {
llvm::raw_svector_ostream os{buf};

// Make these ints here because we're printing and we have bounds on
// their values. Makes printing very easy.
int32_t y = yearFromTime(t);
int32_t m = monthFromTime(t); // monthFromTime(t) is 0-indexed.
int32_t d = dateFromTime(t);
int32_t wd = weekDay(t);

// 7. Return the string-concatenation of weekday, the code unit 0x0020
// (SPACE), month, the code unit 0x0020 (SPACE), day, the code unit 0x0020
// (SPACE), and year.
// Example: Mon Jul 22 2019
os << llvm::format("%s %s %02d %04d", weekdayNames[wd], monthNames[m], d, y);
}

void timeString(double t, double tza, llvm::SmallVectorImpl<char> &buf) {
llvm::raw_svector_ostream os{buf};

int32_t hour = hourFromTime(t);
int32_t minute = minFromTime(t);
int32_t second = secFromTime(t);

// Example: 15:50:49 GMT
os << llvm::format("%02d:%02d:%02d GMT", hour, minute, second);
}

void timeZoneString(double t, double tza, llvm::SmallVectorImpl<char> &buf) {
llvm::raw_svector_ostream os{buf};

// We've already computed the TZA, so use that as the offset.
double offset = tza;

// 4. If offset >= 0, let offsetSign be "+"; otherwise, let offsetSign be "-".
char offsetSign = offset >= 0 ? '+' : '-';

// 5. Let offsetMin be the String representation of MinFromTime(abs(offset)),
// formatted as a two-digit decimal number, padded to the left with a zero if
// necessary.
int32_t offsetMin = minFromTime(std::abs(offset));

// 6. Let offsetHour be the String representation of
// HourFromTime(abs(offset)), formatted as a two-digit decimal number, padded
// to the left with a zero if necessary.
int32_t offsetHour = hourFromTime(std::abs(offset));

// 7. Let tzName be an implementation-defined string that is either the empty
// string or the string-concatenation of the code unit 0x0020 (SPACE), the
// code unit 0x0028 (LEFT PARENTHESIS), an implementation-dependent timezone
// name, and the code unit 0x0029 (RIGHT PARENTHESIS).
// TODO: Make this something other than empty string.

// 8. Return the string-concatenation of offsetSign, offsetHour, offsetMin,
// and tzName.
// Example: -0700
os << llvm::format("%c%02d%02d", offsetSign, offsetHour, offsetMin);
}

void dateTimeString(double tv, double tza, llvm::SmallVectorImpl<char> &buf) {
llvm::raw_svector_ostream os{buf};
dateString(tv, tza, buf);
// Return the string-concatenation of DateString(t), the code unit 0x0020
// (SPACE), TimeString(t), and TimeZoneString(tv).
// Example: Mon Jul 22 2019 15:51:50 GMT-0700
os << " ";
timeString(tv, tza, buf);
timeZoneString(tv, tza, buf);
}

void dateTimeUTCString(
double tv,
double tza,
llvm::SmallVectorImpl<char> &buf) {
llvm::raw_svector_ostream os{buf};

// Make these ints here because we're printing and we have bounds on
// their values. Makes printing very easy.
int32_t y = yearFromTime(tv);
int32_t m = monthFromTime(tv); // monthFromTime(t) is 0-indexed.
int32_t d = dateFromTime(tv);
int32_t wd = weekDay(tv);

// 8. Return the string-concatenation of weekday, ",", the code unit 0x0020
// (SPACE), day, the code unit 0x0020 (SPACE), month, the code unit 0x0020
// (SPACE), year, the code unit 0x0020 (SPACE), and TimeString(tv).
// Example: Mon Jul 22 2019 15:51:50 GMT
os << llvm::format(
"%s, %02d %s %04d ", weekdayNames[wd], d, monthNames[m], y);
timeString(tv, tza, buf);
}

void timeTZString(double tv, double tza, llvm::SmallVectorImpl<char> &buf) {
// Return the string-concatenation of TimeString(t) and TimeZoneString(tv).
// Example: 15:51:50 GMT-0700
timeString(tv, tza, buf);
timeZoneString(tv, tza, buf);
}

//===----------------------------------------------------------------------===//
// Date parsing

Expand Down
31 changes: 16 additions & 15 deletions test/hermes/date-constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ print(new Date(2017, 0, 0).getTime() === new Date(2016, 11, 31).getTime());
print(new Date(2017, 0, 1).getTime() === new Date(2016, 12, 1).getTime());
// CHECK-NEXT: true
print(Date());
// CHECK-NEXT:{{[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}-07:00}}
// CHECK-NEXT: {{... ... .. .... ..:..:.. GMT.....}}
// {{... ... [0-9]{2} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} GMT[-+][0-9]{4} }}
print(typeof Date());
// CHECK-NEXT: string
var lb = new Date().toString();
Expand All @@ -56,26 +57,26 @@ try {Date.prototype.setDate(123);} catch(e) {print('caught', e.name)}
print('toString');
// CHECK-LABEL: toString
print(new Date(112).toString());
// CHECK-NEXT: 1969-12-31T17:00:00.112-07:00
// CHECK-NEXT: Wed Dec 31 1969 17:00:00 GMT-0700
print(new Date(2017, 2, 15, 15, 1, 37, 243).toString());
// CHECK-NEXT: 2017-03-15T15:01:37.243-07:00
// CHECK-NEXT: Wed Mar 15 2017 15:01:37 GMT-0700
print(new Date(123123, 2, 15, 15, 1, 37, 243).toString());
// CHECK-NEXT: +123123-03-15T15:01:37.243-07:00
// CHECK-NEXT: Thu Mar 15 123123 15:01:37 GMT-0700
print(new Date(-1, 2, 15, 15, 1, 37, 243).toString());
// CHECK-NEXT: -000001-03-15T15:01:37.243-07:00
// CHECK-NEXT: Mon Mar 15 -001 15:01:37 GMT-0700
print(new Date(NaN, 2, 15, 15, 1, 37, 243).toString());
// CHECK-NEXT: Invalid Date
print(new Date('2016T12:30').toString());
// CHECK-NEXT: 2016-01-01T05:30:00.000-07:00
// CHECK-NEXT: Fri Jan 01 2016 05:30:00 GMT-0700

print('@@toPrimitive');
// CHECK-LABEL: @@toPrimitive
print('"' + new Date(112)[Symbol.toPrimitive].name + '"');
// CHECK-NEXT: "[Symbol.toPrimitive]"
print(new Date(112)[Symbol.toPrimitive]('string'));
// CHECK-NEXT: 1969-12-31T17:00:00.112-07:00
// CHECK-NEXT: Wed Dec 31 1969 17:00:00 GMT-0700
print(new Date(112)[Symbol.toPrimitive]('default'));
// CHECK-NEXT: 1969-12-31T17:00:00.112-07:00
// CHECK-NEXT: Wed Dec 31 1969 17:00:00 GMT-0700
print(new Date(112)[Symbol.toPrimitive]('number'));
// CHECK-NEXT: 112
try {
Expand All @@ -99,7 +100,7 @@ try {
print('toUTCString');
// CHECK-LABEL: toUTCString
print(new Date(2017, 2, 15, 15, 1, 37, 243).toUTCString());
// CHECK-NEXT: 2017-03-15 22:01:37.243Z
// CHECK-NEXT: Wed, 15 Mar 2017 22:01:37 GMT

print('toJSON');
// CHECK-LABEL: toJSON
Expand All @@ -117,20 +118,20 @@ try {
print('toDateString');
// CHECK-LABEL: toDateString
print(new Date(112).toDateString());
// CHECK-NEXT: 1969-12-31
// CHECK-NEXT: Wed Dec 31 1969
print(new Date(2017, 2, 15, 15, 1, 37, 243).toDateString());
// CHECK-NEXT: 2017-03-15
// CHECK-NEXT: Wed Mar 15 2017
print(new Date(123123, 2, 15, 15, 1, 37, 243).toDateString());
// CHECK-NEXT: +123123-03-15
// CHECK-NEXT: Thu Mar 15 123123
print(new Date(-1, 2, 15, 15, 1, 37, 243).toDateString());
// CHECK-NEXT: -000001-03-15
// CHECK-NEXT: Mon Mar 15 -001

print('toTimeString');
// CHECK-LABEL: toTimeString
print(new Date(112).toTimeString());
// CHECK-NEXT: 17:00:00.112-07:00
// CHECK-NEXT: 17:00:00 GMT-0700
print(new Date(2017, 2, 15, 15, 1, 37, 243).toTimeString());
// CHECK-NEXT: 15:01:37.243-07:00
// CHECK-NEXT: 15:01:37 GMT-0700

print('UTC');
// CHECK-LABEL: UTC
Expand Down

0 comments on commit a38ec5f

Please sign in to comment.