blob: ef46cb6d0859617efa17f05bd5ae6da520ec5c7d [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once
struct _SYSTEMTIME;
using namespace PlatformAgnostic;
namespace Js {
struct SZS;
class DateImplementation:
public DateUtilities
{
friend class JavascriptDate;
friend class JavascriptVariantDate;
typedef struct tm TM;
static const short kpstDstRuleChangeYear = 2007;
static const double ktvMax;
static const double ktvMin;
static const int g_mpytyear[14];
static const int g_mpytyearpost2006[14] ;
double GetMilliSeconds();
static double NowInMilliSeconds(ScriptContext* scriptContext);
static double NowFromHiResTimer(ScriptContext * scriptContext);
static double UtcTimeFromStr(ScriptContext *scriptContext, JavascriptString *pParseString);
static double DoubleToTvUtc(double tv);
private:
DateImplementation(VirtualTableInfoCtorEnum) { m_modified = false; }
DateImplementation(double value);
BEGIN_ENUM_BYTE(DateStringFormat)
Default,
Locale,
GMT,
Lim,
END_ENUM_BYTE()
BEGIN_ENUM_BYTE(DateValueType)
Local = 1, // Whether tvLcl and tzd are valid.
YearMonthDayLocal = 2,
YearMonthDayUTC = 4,
NotNaN = 8,
END_ENUM_BYTE()
// date data
BEGIN_ENUM_BYTE(DateData)
// WARNING: There are tables that depend on this order.
Year,
FullYear,
Month,
Date,
Hours,
Minutes,
Seconds,
Milliseconds,
Day,
TimezoneOffset,
Lim,
END_ENUM_BYTE()
public:
// Flags which control if date or time or both need to be included.
BEGIN_ENUM_BYTE(DateTimeFlag)
None = 0x00,
NoTime = 0x01,
NoDate = 0x02,
END_ENUM_BYTE()
// Time zone descriptor.
struct TZD
{
Field(int) minutes;
Field(int) offset;
// Indicates whether Daylight savings
Field(bool) fDst;
};
template <class ScriptContext>
static double GetTvLcl(double tv, ScriptContext * scriptContext, TZD *ptzd = nullptr);
template <class ScriptContext>
static double GetTvUtc(double tv, ScriptContext * scriptContext);
static bool UtcTimeFromStrCore(
__in_ecount_z(ulength) const char16 *psz,
unsigned int ulength,
double &retVal,
ScriptContext * const scriptContext);
// Used for VT_DATE conversions
//------------------------------------
//Convert a utc time to a variant date.
//------------------------------------
static double VarDateFromJsUtcTime(double dbl, ScriptContext * scriptContext);
static double JsUtcTimeFromVarDate(double dbl, ScriptContext *scriptContext);
void SetTvUtc(double tv);
bool IsModified() { return m_modified; }
void ClearModified() { m_modified = false; }
private:
JavascriptString* GetString(DateStringFormat dsf, ScriptContext* requestContext,
DateTimeFlag noDateTime = DateTimeFlag::None);
JavascriptString* GetISOString(ScriptContext* requestContext);
void GetDateComponent(CompoundString *bs, DateData componentType, int adjust,
ScriptContext* requestContext);
void SetTvLcl(double tv, ScriptContext* requestContext);
double GetDateData(DateData dd, bool fUtc, ScriptContext* scriptContext);
double SetDateData(Arguments args, DateData dd, bool fUtc, ScriptContext* scriptContext);
static bool TryParseDecimalDigits(
const char16 *const str,
const size_t length,
const size_t startIndex,
size_t numDigits,
int &value);
static bool TryParseMilliseconds(
const char16 *const str,
const size_t length,
const size_t startIndex,
int &value,
size_t &foundDigits);
static bool TryParseTwoDecimalDigits(
const char16 *const str,
const size_t length,
const size_t startIndex,
int &value,
bool canHaveTrailingDigit = false);
// Tries to parse the string as an ISO-format date/time as defined in the spec. Returns false if the string is not in
// ISO format.
static bool TryParseIsoString(const char16 *const str, const size_t length, double &timeValue, ScriptContext *scriptContext);
static JavascriptString* ConvertVariantDateToString(double variantDateDouble, ScriptContext* scriptContext);
static JavascriptString* GetDateDefaultString(DateTime::YMD *pymd, TZD *ptzd,DateTimeFlag noDateTime,ScriptContext* scriptContext);
static JavascriptString* GetDateGmtString(DateTime::YMD *pymd,ScriptContext* scriptContext);
#ifdef ENABLE_GLOBALIZATION // todo-xplat: Implement this ICU?
static JavascriptString* GetDateLocaleString(DateTime::YMD *pymd, TZD *ptzd, DateTimeFlag noDateTime,ScriptContext* scriptContext);
#endif
static double DateFncUTC(ScriptContext* scriptContext, Arguments args);
static bool FBig(char16 ch);
static bool FDateDelimiter(char16 ch);
bool IsNaN()
{
if (m_grfval & DateValueType::NotNaN)
{
return false;
}
if (JavascriptNumber::IsNan(m_tvUtc))
{
return true;
}
m_grfval |= DateValueType::NotNaN;
return false;
}
///------------------------------------------------------------------------------
/// Make sure m_tvLcl is valid. (Shared with hybrid debugging, which may use a fake scriptContext.)
///------------------------------------------------------------------------------
template <class ScriptContext>
inline void EnsureTvLcl(ScriptContext* scriptContext)
{
if (!(m_grfval & DateValueType::Local))
{
m_tvLcl = GetTvLcl(m_tvUtc, scriptContext, &m_tzd);
m_grfval |= DateValueType::Local;
}
}
///------------------------------------------------------------------------------
/// Make sure m_ymdLcl is valid. (Shared with hybrid debugging, which may use a fake scriptContext.)
///------------------------------------------------------------------------------
template <class ScriptContext>
inline void EnsureYmdLcl(ScriptContext* scriptContext)
{
if (m_grfval & DateValueType::YearMonthDayLocal)
{
return;
}
EnsureTvLcl(scriptContext);
GetYmdFromTv(m_tvLcl, &m_ymdLcl);
m_grfval |= DateValueType::YearMonthDayLocal;
}
///------------------------------------------------------------------------------
/// Make sure m_ymdUtc is valid.
///------------------------------------------------------------------------------
inline void EnsureYmdUtc(void)
{
if (m_grfval & DateValueType::YearMonthDayUTC)
{
return;
}
GetYmdFromTv(m_tvUtc, &m_ymdUtc);
m_grfval |= DateValueType::YearMonthDayUTC;
}
inline Var GetFullYear(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar(m_ymdLcl.year, requestContext);
}
inline Var GetYear(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
// WOOB bug 1099381: ES5 spec B.2.4: getYear() must return YearFromTime() - 1900.
// Note that negative value is OK for the spec.
int value = m_ymdLcl.year - 1900;
return JavascriptNumber::ToVar(value, requestContext);
}
inline Var GetMonth(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar(m_ymdLcl.mon, requestContext);
}
inline Var GetDate(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar(m_ymdLcl.mday + 1, requestContext);
}
inline Var GetDay(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar(m_ymdLcl.wday, requestContext);
}
inline Var GetHours(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar((m_ymdLcl.time / 3600000)%24, requestContext);
}
inline Var GetMinutes(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar((m_ymdLcl.time / 60000) % 60, requestContext);
}
inline Var GetSeconds(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar((m_ymdLcl.time / 1000) % 60, requestContext);
}
inline Var GetDateMilliSeconds(ScriptContext* requestContext)
{
EnsureYmdLcl(requestContext);
return JavascriptNumber::ToVar(m_ymdLcl.time % 1000, requestContext);
}
template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
StringBuilder* GetDiagValueString(ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder);
template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
static StringBuilder* ConvertVariantDateToString(double dbl, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder);
template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
static StringBuilder* GetDateDefaultString(DateTime::YMD *pymd, TZD *ptzd, DateTimeFlag noDateTime, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder);
private:
Field(double) m_tvUtc;
Field(double) m_tvLcl;
FieldNoBarrier(DateTime::YMD) m_ymdUtc;
FieldNoBarrier(DateTime::YMD) m_ymdLcl;
Field(TZD) m_tzd;
Field(uint32) m_grfval; // Which fields are valid. m_tvUtc is always valid.
Field(bool) m_modified : 1; // Whether SetDateData was called on this class
friend JavascriptDate;
};
///
/// Use tv as the UTC time and return the corresponding local time. (Shared with hybrid debugging, which may use a fake scriptContext.)
///
template <class ScriptContext>
double DateImplementation::GetTvLcl(double tv, ScriptContext *scriptContext, TZD *ptzd)
{
Assert(scriptContext);
double tvLcl;
if (nullptr != ptzd)
{
ptzd->minutes = 0;
ptzd->fDst = FALSE;
}
// See if we're out of range before conversion (UTC time value must be within this range)
if (JavascriptNumber::IsNan(tv) || tv < ktvMin || tv > ktvMax)
{
return JavascriptNumber::NaN;
}
int bias;
int offset;
bool isDaylightSavings;
tvLcl = scriptContext->GetDaylightTimeHelper()->UtcToLocal(tv, bias, offset, isDaylightSavings);
if (nullptr != ptzd)
{
ptzd->minutes = -bias;
ptzd->offset = offset;
ptzd->fDst = isDaylightSavings;
}
return tvLcl;
}
///
/// Use tv as the local time and return the corresponding UTC time. (Shared with hybrid debugging, which may use a fake scriptContext.)
///
template <class ScriptContext>
double DateImplementation::GetTvUtc(double tv, ScriptContext *scriptContext)
{
Assert(scriptContext);
double tvUtc;
if (JavascriptNumber::IsNan(tv) || !NumberUtilities::IsFinite(tv))
{
return JavascriptNumber::NaN;
}
tvUtc = scriptContext->GetDaylightTimeHelper()->LocalToUtc(tv);
// See if we're out of range after conversion (UTC time value must be within this range)
if (JavascriptNumber::IsNan(tvUtc) || !NumberUtilities::IsFinite(tv) || tvUtc < ktvMin || tvUtc > ktvMax)
{
return JavascriptNumber::NaN;
}
return tvUtc;
}
//
// Get diag value display for hybrid debugging.
// StringBuilder: A Js::StringBuilder/CompoundString like class, used to build strings.
// ScriptContext: A Js::ScriptContext like class, which provides fields needed by Date stringify.
// NewStringBuilderFunc: A function that returns a StringBuilder*, used to create a StringBuilder.
//
template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
StringBuilder* DateImplementation::GetDiagValueString(ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder)
{
if (JavascriptNumber::IsNan(m_tvUtc))
{
StringBuilder* bs = newStringBuilder(0);
bs->Append(JS_DISPLAY_STRING_INVALID_DATE);
return bs;
}
EnsureYmdLcl(scriptContext);
return GetDateDefaultString<StringBuilder>(&m_ymdLcl, &m_tzd, DateTimeFlag::None, scriptContext, newStringBuilder);
}
template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
StringBuilder* DateImplementation::ConvertVariantDateToString(double dbl, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder)
{
TZD tzd;
DateTime::YMD ymd;
double tv = GetTvUtc(JsLocalTimeFromVarDate(dbl), scriptContext);
tv = GetTvLcl(tv, scriptContext, &tzd);
if (JavascriptNumber::IsNan(tv))
{
StringBuilder* bs = newStringBuilder(0);
bs->Append(JS_DISPLAY_STRING_NAN);
return bs;
}
GetYmdFromTv(tv, &ymd);
return GetDateDefaultString<StringBuilder>(&ymd, &tzd, DateTimeFlag::None, scriptContext, newStringBuilder);
}
//
// Get default date string, shared with hybrid debugging.
// StringBuilder: A Js::StringBuilder/CompoundString like class, used to build strings.
// ScriptContext: A Js::ScriptContext like class, which provides fields needed by Date stringify.
// NewStringBuilderFunc: A function that returns a StringBuilder*, used to create a StringBuilder.
//
template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
StringBuilder* DateImplementation::GetDateDefaultString(DateTime::YMD *pymd, TZD *ptzd, DateTimeFlag noDateTime, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder)
{
int hour, min;
StringBuilder* const bs = newStringBuilder(72);
const auto ConvertUInt16ToString_ZeroPad_2 = [](const uint16 value, char16 *const buffer, const CharCount charCapacity)
{
const charcount_t cchWritten = NumberUtilities::UInt16ToString(value, buffer, charCapacity, 2);
Assert(cchWritten != 0);
};
const auto ConvertLongToString = [](const int32 value, char16 *const buffer, const CharCount charCapacity)
{
const errno_t err = _ltow_s(value, buffer, charCapacity, 10);
Assert(err == 0);
};
// PART 1 - DATE part
if( !(noDateTime & DateTimeFlag::NoDate))
{
bs->AppendChars(g_rgpszDay[pymd->wday]);
bs->AppendChars(_u(' '));
bs->AppendChars(g_rgpszMonth[pymd->mon]);
bs->AppendChars(_u(' '));
// sz - as %02d - output is "01" to "31"
bs->AppendChars(static_cast<WORD>(pymd->mday + 1), 2, ConvertUInt16ToString_ZeroPad_2);
bs->AppendChars(_u(' '));
// year is directly after day, month, daydigit for IE11+
bs->AppendChars(pymd->year, 10, ConvertLongToString);
if(!(noDateTime & DateTimeFlag::NoTime))
{
// append a space to delimit PART 2 - if to be outputted
bs->AppendChars(_u(' '));
}
}
// PART 2 - TIME part
if(!(noDateTime & DateTimeFlag::NoTime))
{
// sz - as %02d - HOUR
bs->AppendChars(static_cast<WORD>(pymd->time / 3600000), 2, ConvertUInt16ToString_ZeroPad_2);
bs->AppendChars(_u(':'));
// sz - as %02d - MINUTE
bs->AppendChars(static_cast<WORD>((pymd->time / 60000) % 60), 2, ConvertUInt16ToString_ZeroPad_2);
bs->AppendChars(_u(':'));
// sz - as %02d - SECOND
bs->AppendChars(static_cast<WORD>((pymd->time / 1000) % 60), 2, ConvertUInt16ToString_ZeroPad_2);
bs->AppendChars(_u(" GMT"));
// IE11+
min = ptzd->offset;
if (min < 0)
{
bs->AppendChars(_u('-'));
min = -min;
}
else
{
bs->AppendChars(_u('+'));
}
hour = min / 60;
min %= 60;
// sz - as %02d - HOUR
bs->AppendChars(static_cast<WORD>(hour), 2, ConvertUInt16ToString_ZeroPad_2);
// sz - as %02d - MIN
bs->AppendChars(static_cast<WORD>(min), 2, ConvertUInt16ToString_ZeroPad_2);
bs->AppendChars(_u(" ("));
// check the IsDaylightSavings?
if (ptzd->fDst == false)
{
size_t nameLength;
const WCHAR *const standardName = scriptContext->GetStandardName(&nameLength, pymd);
bs->AppendChars(standardName, static_cast<CharCount>(nameLength));
}
else
{
size_t nameLength;
const WCHAR *const daylightName = scriptContext->GetDaylightName(&nameLength, pymd);
bs->AppendChars(daylightName, static_cast<CharCount>(nameLength));
}
bs->AppendChars(_u(')'));
}
return bs;
}
} // namespace Js