Skip to content

Commit

Permalink
Use LocalTimeOffsetCache for UTC/Local time conversion
Browse files Browse the repository at this point in the history
Reviewed By: avp

Differential Revision: D52791285

fbshipit-source-id: f12831d68774fbe847cd45e4d280e2471039bde7
  • Loading branch information
lavenzg authored and facebook-github-bot committed Jun 26, 2024
1 parent 9e199e9 commit 59ea64c
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 67 deletions.
8 changes: 5 additions & 3 deletions include/hermes/VM/JSLib/DateUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
namespace hermes {
namespace vm {

class LocalTimeOffsetCache;

//===----------------------------------------------------------------------===//
// Conversion constants

Expand Down Expand Up @@ -122,11 +124,11 @@ int32_t equivalentTime(int64_t epochSecs);
// ES5.1 15.9.1.9

/// Conversion from UTC to local time.
double localTime(double t);
double localTime(double t, LocalTimeOffsetCache &);

/// Conversion from local time to UTC.
/// Spec refers to this as the UTC() function.
double utcTime(double t);
double utcTime(double t, LocalTimeOffsetCache &);

//===----------------------------------------------------------------------===//
// ES5.1 15.9.1.10
Expand Down Expand Up @@ -273,7 +275,7 @@ void timeTZString(double t, double tza, llvh::SmallVectorImpl<char> &buf);
/// - Date.parse(x.toISOString())
/// We can extend this to support other formats as well, when the given str
/// does not conform to the 15.9.1.15 format.
double parseDate(StringView str);
double parseDate(StringView str, LocalTimeOffsetCache &);

} // namespace vm
} // namespace hermes
Expand Down
75 changes: 49 additions & 26 deletions lib/VM/JSLib/Date.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "hermes/Support/OSCompat.h"
#include "hermes/VM/HermesValue.h"
#include "hermes/VM/JSLib/DateUtil.h"
#include "hermes/VM/JSLib/JSLibStorage.h"
#include "hermes/VM/Operations.h"
#pragma GCC diagnostic push

Expand Down Expand Up @@ -441,8 +442,10 @@ dateConstructor_RJS(void *, Runtime &runtime, NativeArgs args) {

if (v->isString()) {
// Call the String -> Date parsing function.
finalDate = timeClip(parseDate(StringPrimitive::createStringView(
runtime, Handle<StringPrimitive>::vmcast(v))));
finalDate = timeClip(parseDate(
StringPrimitive::createStringView(
runtime, Handle<StringPrimitive>::vmcast(v)),
runtime.getJSLibStorage()->localTimeOffsetCache));
} else {
auto numRes = toNumber_RJS(runtime, v);
if (numRes == ExecutionStatus::EXCEPTION) {
Expand All @@ -461,15 +464,16 @@ dateConstructor_RJS(void *, Runtime &runtime, NativeArgs args) {
// makeTimeFromArgs interprets arguments as UTC.
// We want them as local time, so pretend that they are,
// and call utcTime to get the final UTC value we want to store.
finalDate = timeClip(utcTime(*cr));
finalDate = timeClip(
utcTime(*cr, runtime.getJSLibStorage()->localTimeOffsetCache));
}
self->setPrimitiveValue(finalDate);
return self.getHermesValue();
}

llvh::SmallString<32> str{};
double t = curTime();
double local = localTime(t);
double local = localTime(t, runtime.getJSLibStorage()->localTimeOffsetCache);
dateTimeString(local, local - t, str);
return runtime.ignoreAllocationFailure(StringPrimitive::create(runtime, str));
}
Expand All @@ -480,9 +484,10 @@ dateParse_RJS(void *, Runtime &runtime, NativeArgs args) {
if (res == ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
return HermesValue::encodeUntrustedNumberValue(
parseDate(StringPrimitive::createStringView(
runtime, runtime.makeHandle(std::move(*res)))));
return HermesValue::encodeUntrustedNumberValue(parseDate(
StringPrimitive::createStringView(
runtime, runtime.makeHandle(std::move(*res))),
runtime.getJSLibStorage()->localTimeOffsetCache));
}

CallResult<HermesValue> dateUTC_RJS(void *, Runtime &runtime, NativeArgs args) {
Expand Down Expand Up @@ -541,7 +546,8 @@ datePrototypeToStringHelper(void *ctx, Runtime &runtime, NativeArgs args) {
}
llvh::SmallString<32> str{};
if (!opts->isUTC) {
double local = localTime(t);
double local =
localTime(t, runtime.getJSLibStorage()->localTimeOffsetCache);
opts->toStringFn(local, local - t, str);
} else {
opts->toStringFn(t, 0, str);
Expand Down Expand Up @@ -647,7 +653,7 @@ datePrototypeGetterHelper(void *ctx, Runtime &runtime, NativeArgs args) {
// Store the original value of t to be used in offset calculations.
double utc = t;
if (!opts->isUTC) {
t = localTime(t);
t = localTime(t, runtime.getJSLibStorage()->localTimeOffsetCache);
}

double result{std::numeric_limits<double>::quiet_NaN()};
Expand Down Expand Up @@ -713,8 +719,9 @@ datePrototypeSetMilliseconds_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setMilliseconds() called on non-Date object");
}
double t = self->getPrimitiveValue();
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
if (!isUTC) {
t = localTime(t);
t = localTime(t, localTimeOffsetCache);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
Expand All @@ -723,7 +730,8 @@ datePrototypeSetMilliseconds_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
double ms = res->getNumber();
double date = makeDate(
day(t), makeTime(hourFromTime(t), minFromTime(t), secFromTime(t), ms));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
double utcT =
!isUTC ? timeClip(utcTime(date, localTimeOffsetCache)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeUntrustedNumberValue(utcT);
}
Expand All @@ -739,8 +747,9 @@ datePrototypeSetSeconds_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setSeconds() called on non-Date object");
}
double t = self->getPrimitiveValue();
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
if (!isUTC) {
t = localTime(t);
t = localTime(t, localTimeOffsetCache);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
Expand All @@ -760,7 +769,8 @@ datePrototypeSetSeconds_RJS(void *ctx, Runtime &runtime, NativeArgs args) {

double date =
makeDate(day(t), makeTime(hourFromTime(t), minFromTime(t), s, milli));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
double utcT =
!isUTC ? timeClip(utcTime(date, localTimeOffsetCache)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeUntrustedNumberValue(utcT);
}
Expand All @@ -776,8 +786,9 @@ datePrototypeSetMinutes_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setMinutes() called on non-Date object");
}
double t = self->getPrimitiveValue();
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
if (!isUTC) {
t = localTime(t);
t = localTime(t, localTimeOffsetCache);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
Expand Down Expand Up @@ -806,7 +817,8 @@ datePrototypeSetMinutes_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
}

double date = makeDate(day(t), makeTime(hourFromTime(t), m, s, milli));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
double utcT =
!isUTC ? timeClip(utcTime(date, localTimeOffsetCache)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeUntrustedNumberValue(utcT);
}
Expand All @@ -822,8 +834,9 @@ datePrototypeSetHours_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setHours() called on non-Date object");
}
double t = self->getPrimitiveValue();
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
if (!isUTC) {
t = localTime(t);
t = localTime(t, localTimeOffsetCache);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
Expand Down Expand Up @@ -862,7 +875,8 @@ datePrototypeSetHours_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
}

double date = makeDate(day(t), makeTime(h, m, s, milli));
double utcT = !isUTC ? timeClip(utcTime(date)) : timeClip(date);
double utcT =
!isUTC ? timeClip(utcTime(date, localTimeOffsetCache)) : timeClip(date);
self->setPrimitiveValue(utcT);
return HermesValue::encodeUntrustedNumberValue(utcT);
}
Expand All @@ -877,8 +891,9 @@ datePrototypeSetDate_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setDate() called on non-Date object");
}
double t = self->getPrimitiveValue();
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
if (!isUTC) {
t = localTime(t);
t = localTime(t, localTimeOffsetCache);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
Expand All @@ -887,7 +902,8 @@ datePrototypeSetDate_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
double dt = res->getNumber();
double newDate = makeDate(
makeDay(yearFromTime(t), monthFromTime(t), dt), timeWithinDay(t));
double utcT = !isUTC ? timeClip(utcTime(newDate)) : timeClip(newDate);
double utcT = !isUTC ? timeClip(utcTime(newDate, localTimeOffsetCache))
: timeClip(newDate);
self->setPrimitiveValue(utcT);
return HermesValue::encodeUntrustedNumberValue(utcT);
}
Expand All @@ -903,8 +919,9 @@ datePrototypeSetMonth_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setMonth() called on non-Date object");
}
double t = self->getPrimitiveValue();
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
if (!isUTC) {
t = localTime(t);
t = localTime(t, localTimeOffsetCache);
}
auto res = toNumber_RJS(runtime, args.getArgHandle(0));
if (res == ExecutionStatus::EXCEPTION) {
Expand All @@ -922,7 +939,8 @@ datePrototypeSetMonth_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
dt = dateFromTime(t);
}
double newDate = makeDate(makeDay(yearFromTime(t), m, dt), timeWithinDay(t));
double utcT = !isUTC ? timeClip(utcTime(newDate)) : timeClip(newDate);
double utcT = !isUTC ? timeClip(utcTime(newDate, localTimeOffsetCache))
: timeClip(newDate);
self->setPrimitiveValue(utcT);
return HermesValue::encodeUntrustedNumberValue(utcT);
}
Expand All @@ -938,8 +956,9 @@ datePrototypeSetFullYear_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setFullYear() called on non-Date object");
}
double t = self->getPrimitiveValue();
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
if (!isUTC) {
t = localTime(t);
t = localTime(t, localTimeOffsetCache);
}
if (std::isnan(t)) {
t = 0;
Expand Down Expand Up @@ -970,7 +989,8 @@ datePrototypeSetFullYear_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
dt = dateFromTime(t);
}
double newDate = makeDate(makeDay(y, m, dt), timeWithinDay(t));
double utcT = !isUTC ? timeClip(utcTime(newDate)) : timeClip(newDate);
double utcT = !isUTC ? timeClip(utcTime(newDate, localTimeOffsetCache))
: timeClip(newDate);
self->setPrimitiveValue(utcT);
return HermesValue::encodeUntrustedNumberValue(utcT);
}
Expand All @@ -986,7 +1006,8 @@ datePrototypeSetYear_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
"Date.prototype.setYear() called on non-Date object");
}
double t = self->getPrimitiveValue();
t = localTime(t);
auto &localTimeOffsetCache = runtime.getJSLibStorage()->localTimeOffsetCache;
t = localTime(t, localTimeOffsetCache);
if (std::isnan(t)) {
t = 0;
}
Expand All @@ -1001,8 +1022,10 @@ datePrototypeSetYear_RJS(void *ctx, Runtime &runtime, NativeArgs args) {
}
double yint = std::trunc(y);
double yr = 0 <= yint && yint <= 99 ? yint + 1900 : y;
double date = utcTime(makeDate(
makeDay(yr, monthFromTime(t), dateFromTime(t)), timeWithinDay(t)));
double date = utcTime(
makeDate(
makeDay(yr, monthFromTime(t), dateFromTime(t)), timeWithinDay(t)),
localTimeOffsetCache);
double d = timeClip(date);
self->setPrimitiveValue(d);
return HermesValue::encodeUntrustedNumberValue(d);
Expand Down
56 changes: 26 additions & 30 deletions lib/VM/JSLib/DateUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include "hermes/VM/JSLib/DateUtil.h"
#include "hermes/VM/JSLib/DateCache.h"

#include "hermes/Platform/Unicode/PlatformUnicode.h"
#include "hermes/Support/Compiler.h"
Expand Down Expand Up @@ -418,12 +419,18 @@ double daylightSavingTA(double t) {
//===----------------------------------------------------------------------===//
// ES5.1 15.9.1.9

/// https://tc39.es/ecma262/#sec-localtime
/// Conversion from UTC to local time.
double localTime(double t) {
return t + localTZA() + daylightSavingTA(t);
double localTime(double t, LocalTimeOffsetCache &localTimeOffsetCache) {
// The spec requires that localTime() accepts only finite time value, but for
// simplicity, we do the check here instead of the caller site.
if (!std::isfinite(t)) {
return std::numeric_limits<double>::quiet_NaN();
}
return t + localTimeOffsetCache.getLocalTimeOffset(t, TimeType::Utc);
}

/// https://tc39.es/ecma262/#sec-localtime
/// https://tc39.es/ecma262/#sec-utc-t
/// Conversion from local time to UTC.
///
/// There is time ambiguity when converting local time to UTC time. For example,
Expand All @@ -438,26 +445,11 @@ double localTime(double t) {
/// 12 March 2017 is skipped, it should be interpreted as 2:30 AM UTC-05
/// instead of 3:30 AM UTC-04. However, in this case, both have the same UTC
/// epoch.
double utcTime(double t) {
double ltza = localTZA();
// To compute the DST offset, we need to use UTC time (as required by
// daylightSavingTA()). However, getting the exact UTC time is not possible
// since that would be circular. Therefore, we approximate the UTC time by
// subtracting the standard time adjustment and then subtracting an additional
// hour to comply with the spec's requirements as noted in the doc-comment.
//
// For example, imagine a transition to DST that goes from UTC+0 to UTC+1,
// moving 00:00 to 01:00. Any time in the skipped hour gets mapped to a
// UTC time before the transition when we subtract an hour (e.g., 00:30 ->
// 23:30), which will correctly result in DST not being in effect.
//
// Similarly, during a transition from DST back to standard time, the hour
// from 00:00 to 01:00 is repeated. A local time in the repeated hour
// similarly gets mapped to a UTC time before the transition.
//
// Note that this will not work if the timezone offset has historical/future
// changes (which generates a different ltza than the one obtained here).
return t - ltza - daylightSavingTA(t - ltza - MS_PER_HOUR);
double utcTime(double t, LocalTimeOffsetCache &localTimeOffsetCache) {
if (!std::isfinite(t)) {
return std::numeric_limits<double>::quiet_NaN();
}
return t - localTimeOffsetCache.getLocalTimeOffset(t, TimeType::Local);
}

//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -787,7 +779,9 @@ static bool scanInt(InputIter &it, const InputIter end, int32_t &x) {
return !ref.getAsInteger(10, x);
}

static double parseISODate(StringView u16str) {
static double parseISODate(
StringView u16str,
LocalTimeOffsetCache &localTimeOffsetCache) {
constexpr double nan = std::numeric_limits<double>::quiet_NaN();

auto it = u16str.begin();
Expand Down Expand Up @@ -872,7 +866,7 @@ static double parseISODate(StringView u16str) {
// forms are interpreted as a UTC time and date-time forms are interpreted
// as a local time.
double t = makeDate(makeDay(y, m - 1, d), makeTime(h, min, s, ms));
t = utcTime(t);
t = utcTime(t, localTimeOffsetCache);
return t;
}

Expand Down Expand Up @@ -917,7 +911,9 @@ static double parseISODate(StringView u16str) {
return makeDate(makeDay(y, m - 1, d), makeTime(h - tzh, min - tzm, s, ms));
}

static double parseESDate(StringView str) {
static double parseESDate(
StringView str,
LocalTimeOffsetCache &localTimeOffsetCache) {
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
StringView tok = str;

Expand Down Expand Up @@ -1081,7 +1077,7 @@ static double parseESDate(StringView str) {
if (it == end) {
// Default to local time zone if no time zone provided
double t = makeDate(makeDay(y, m - 1, d), makeTime(h, min, s, ms));
t = utcTime(t);
t = utcTime(t, localTimeOffsetCache);
return t;
}

Expand Down Expand Up @@ -1161,13 +1157,13 @@ static double parseESDate(StringView str) {
return makeDate(makeDay(y, m - 1, d), makeTime(h - tzh, min - tzm, s, ms));
}

double parseDate(StringView str) {
double result = parseISODate(str);
double parseDate(StringView str, LocalTimeOffsetCache &localTimeOffsetCache) {
double result = parseISODate(str, localTimeOffsetCache);
if (!std::isnan(result)) {
return result;
}

return parseESDate(str);
return parseESDate(str, localTimeOffsetCache);
}

} // namespace vm
Expand Down
Loading

0 comments on commit 59ea64c

Please sign in to comment.