DateImplementation.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #pragma once
  6. struct _SYSTEMTIME;
  7. using namespace PlatformAgnostic;
  8. namespace Js {
  9. struct SZS;
  10. class DateImplementation:
  11. public DateUtilities
  12. {
  13. friend class JavascriptDate;
  14. friend class JavascriptVariantDate;
  15. typedef struct tm TM;
  16. static const short kpstDstRuleChangeYear = 2007;
  17. static const double ktvMax;
  18. static const double ktvMin;
  19. static const int g_mpytyear[14];
  20. static const int g_mpytyearpost2006[14] ;
  21. double GetMilliSeconds();
  22. static double NowInMilliSeconds(ScriptContext* scriptContext);
  23. static double NowFromHiResTimer(ScriptContext * scriptContext);
  24. static double UtcTimeFromStr(ScriptContext *scriptContext, JavascriptString *pParseString);
  25. static double DoubleToTvUtc(double tv);
  26. private:
  27. DateImplementation(VirtualTableInfoCtorEnum) { m_modified = false; }
  28. DateImplementation(double value);
  29. BEGIN_ENUM_BYTE(DateStringFormat)
  30. Default,
  31. Locale,
  32. GMT,
  33. Lim,
  34. END_ENUM_BYTE()
  35. BEGIN_ENUM_BYTE(DateValueType)
  36. Local = 1, // Whether tvLcl and tzd are valid.
  37. YearMonthDayLocal = 2,
  38. YearMonthDayUTC = 4,
  39. NotNaN = 8,
  40. END_ENUM_BYTE()
  41. // date data
  42. BEGIN_ENUM_BYTE(DateData)
  43. // WARNING: There are tables that depend on this order.
  44. Year,
  45. FullYear,
  46. Month,
  47. Date,
  48. Hours,
  49. Minutes,
  50. Seconds,
  51. Milliseconds,
  52. Day,
  53. TimezoneOffset,
  54. Lim,
  55. END_ENUM_BYTE()
  56. public:
  57. // Flags which control if date or time or both need to be included.
  58. BEGIN_ENUM_BYTE(DateTimeFlag)
  59. None = 0x00,
  60. NoTime = 0x01,
  61. NoDate = 0x02,
  62. END_ENUM_BYTE()
  63. // Time zone descriptor.
  64. struct TZD
  65. {
  66. Field(int) minutes;
  67. Field(int) offset;
  68. // Indicates whether Daylight savings
  69. Field(bool) fDst;
  70. };
  71. template <class ScriptContext>
  72. static double GetTvLcl(double tv, ScriptContext * scriptContext, TZD *ptzd = nullptr);
  73. template <class ScriptContext>
  74. static double GetTvUtc(double tv, ScriptContext * scriptContext);
  75. static bool UtcTimeFromStrCore(
  76. __in_ecount_z(ulength) const char16 *psz,
  77. unsigned int ulength,
  78. double &retVal,
  79. ScriptContext * const scriptContext);
  80. // Used for VT_DATE conversions
  81. //------------------------------------
  82. //Convert a utc time to a variant date.
  83. //------------------------------------
  84. static double VarDateFromJsUtcTime(double dbl, ScriptContext * scriptContext);
  85. static double JsUtcTimeFromVarDate(double dbl, ScriptContext *scriptContext);
  86. void SetTvUtc(double tv);
  87. bool IsModified() { return m_modified; }
  88. void ClearModified() { m_modified = false; }
  89. private:
  90. JavascriptString* GetString(DateStringFormat dsf, ScriptContext* requestContext,
  91. DateTimeFlag noDateTime = DateTimeFlag::None);
  92. JavascriptString* GetISOString(ScriptContext* requestContext);
  93. void GetDateComponent(CompoundString *bs, DateData componentType, int adjust,
  94. ScriptContext* requestContext);
  95. void SetTvLcl(double tv, ScriptContext* requestContext);
  96. double GetDateData(DateData dd, bool fUtc, ScriptContext* scriptContext);
  97. double SetDateData(Arguments args, DateData dd, bool fUtc, ScriptContext* scriptContext);
  98. static bool TryParseDecimalDigits(
  99. const char16 *const str,
  100. const size_t length,
  101. const size_t startIndex,
  102. size_t numDigits,
  103. int &value);
  104. static bool TryParseMilliseconds(
  105. const char16 *const str,
  106. const size_t length,
  107. const size_t startIndex,
  108. int &value,
  109. size_t &foundDigits);
  110. static bool TryParseTwoDecimalDigits(
  111. const char16 *const str,
  112. const size_t length,
  113. const size_t startIndex,
  114. int &value,
  115. bool canHaveTrailingDigit = false);
  116. // Tries to parse the string as an ISO-format date/time as defined in the spec. Returns false if the string is not in
  117. // ISO format.
  118. static bool TryParseIsoString(const char16 *const str, const size_t length, double &timeValue, ScriptContext *scriptContext);
  119. static JavascriptString* ConvertVariantDateToString(double variantDateDouble, ScriptContext* scriptContext);
  120. static JavascriptString* GetDateDefaultString(DateTime::YMD *pymd, TZD *ptzd,DateTimeFlag noDateTime,ScriptContext* scriptContext);
  121. static JavascriptString* GetDateGmtString(DateTime::YMD *pymd,ScriptContext* scriptContext);
  122. #ifdef ENABLE_GLOBALIZATION // todo-xplat: Implement this ICU?
  123. static JavascriptString* GetDateLocaleString(DateTime::YMD *pymd, TZD *ptzd, DateTimeFlag noDateTime,ScriptContext* scriptContext);
  124. #endif
  125. static double DateFncUTC(ScriptContext* scriptContext, Arguments args);
  126. static bool FBig(char16 ch);
  127. static bool FDateDelimiter(char16 ch);
  128. bool IsNaN()
  129. {
  130. if (m_grfval & DateValueType::NotNaN)
  131. {
  132. return false;
  133. }
  134. if (JavascriptNumber::IsNan(m_tvUtc))
  135. {
  136. return true;
  137. }
  138. m_grfval |= DateValueType::NotNaN;
  139. return false;
  140. }
  141. ///------------------------------------------------------------------------------
  142. /// Make sure m_tvLcl is valid. (Shared with hybrid debugging, which may use a fake scriptContext.)
  143. ///------------------------------------------------------------------------------
  144. template <class ScriptContext>
  145. inline void EnsureTvLcl(ScriptContext* scriptContext)
  146. {
  147. if (!(m_grfval & DateValueType::Local))
  148. {
  149. m_tvLcl = GetTvLcl(m_tvUtc, scriptContext, &m_tzd);
  150. m_grfval |= DateValueType::Local;
  151. }
  152. }
  153. ///------------------------------------------------------------------------------
  154. /// Make sure m_ymdLcl is valid. (Shared with hybrid debugging, which may use a fake scriptContext.)
  155. ///------------------------------------------------------------------------------
  156. template <class ScriptContext>
  157. inline void EnsureYmdLcl(ScriptContext* scriptContext)
  158. {
  159. if (m_grfval & DateValueType::YearMonthDayLocal)
  160. {
  161. return;
  162. }
  163. EnsureTvLcl(scriptContext);
  164. GetYmdFromTv(m_tvLcl, &m_ymdLcl);
  165. m_grfval |= DateValueType::YearMonthDayLocal;
  166. }
  167. ///------------------------------------------------------------------------------
  168. /// Make sure m_ymdUtc is valid.
  169. ///------------------------------------------------------------------------------
  170. inline void EnsureYmdUtc(void)
  171. {
  172. if (m_grfval & DateValueType::YearMonthDayUTC)
  173. {
  174. return;
  175. }
  176. GetYmdFromTv(m_tvUtc, &m_ymdUtc);
  177. m_grfval |= DateValueType::YearMonthDayUTC;
  178. }
  179. inline Var GetFullYear(ScriptContext* requestContext)
  180. {
  181. EnsureYmdLcl(requestContext);
  182. return JavascriptNumber::ToVar(m_ymdLcl.year, requestContext);
  183. }
  184. inline Var GetYear(ScriptContext* requestContext)
  185. {
  186. EnsureYmdLcl(requestContext);
  187. // WOOB bug 1099381: ES5 spec B.2.4: getYear() must return YearFromTime() - 1900.
  188. // Note that negative value is OK for the spec.
  189. int value = m_ymdLcl.year - 1900;
  190. return JavascriptNumber::ToVar(value, requestContext);
  191. }
  192. inline Var GetMonth(ScriptContext* requestContext)
  193. {
  194. EnsureYmdLcl(requestContext);
  195. return JavascriptNumber::ToVar(m_ymdLcl.mon, requestContext);
  196. }
  197. inline Var GetDate(ScriptContext* requestContext)
  198. {
  199. EnsureYmdLcl(requestContext);
  200. return JavascriptNumber::ToVar(m_ymdLcl.mday + 1, requestContext);
  201. }
  202. inline Var GetDay(ScriptContext* requestContext)
  203. {
  204. EnsureYmdLcl(requestContext);
  205. return JavascriptNumber::ToVar(m_ymdLcl.wday, requestContext);
  206. }
  207. inline Var GetHours(ScriptContext* requestContext)
  208. {
  209. EnsureYmdLcl(requestContext);
  210. return JavascriptNumber::ToVar((m_ymdLcl.time / 3600000)%24, requestContext);
  211. }
  212. inline Var GetMinutes(ScriptContext* requestContext)
  213. {
  214. EnsureYmdLcl(requestContext);
  215. return JavascriptNumber::ToVar((m_ymdLcl.time / 60000) % 60, requestContext);
  216. }
  217. inline Var GetSeconds(ScriptContext* requestContext)
  218. {
  219. EnsureYmdLcl(requestContext);
  220. return JavascriptNumber::ToVar((m_ymdLcl.time / 1000) % 60, requestContext);
  221. }
  222. inline Var GetDateMilliSeconds(ScriptContext* requestContext)
  223. {
  224. EnsureYmdLcl(requestContext);
  225. return JavascriptNumber::ToVar(m_ymdLcl.time % 1000, requestContext);
  226. }
  227. template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
  228. StringBuilder* GetDiagValueString(ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder);
  229. template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
  230. static StringBuilder* ConvertVariantDateToString(double dbl, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder);
  231. template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
  232. static StringBuilder* GetDateDefaultString(DateTime::YMD *pymd, TZD *ptzd, DateTimeFlag noDateTime, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder);
  233. private:
  234. Field(double) m_tvUtc;
  235. Field(double) m_tvLcl;
  236. FieldNoBarrier(DateTime::YMD) m_ymdUtc;
  237. FieldNoBarrier(DateTime::YMD) m_ymdLcl;
  238. Field(TZD) m_tzd;
  239. Field(uint32) m_grfval; // Which fields are valid. m_tvUtc is always valid.
  240. Field(bool) m_modified : 1; // Whether SetDateData was called on this class
  241. friend JavascriptDate;
  242. };
  243. ///
  244. /// Use tv as the UTC time and return the corresponding local time. (Shared with hybrid debugging, which may use a fake scriptContext.)
  245. ///
  246. template <class ScriptContext>
  247. double DateImplementation::GetTvLcl(double tv, ScriptContext *scriptContext, TZD *ptzd)
  248. {
  249. Assert(scriptContext);
  250. double tvLcl;
  251. if (nullptr != ptzd)
  252. {
  253. ptzd->minutes = 0;
  254. ptzd->fDst = FALSE;
  255. }
  256. // See if we're out of range before conversion (UTC time value must be within this range)
  257. if (JavascriptNumber::IsNan(tv) || tv < ktvMin || tv > ktvMax)
  258. {
  259. return JavascriptNumber::NaN;
  260. }
  261. int bias;
  262. int offset;
  263. bool isDaylightSavings;
  264. tvLcl = scriptContext->GetDaylightTimeHelper()->UtcToLocal(tv, bias, offset, isDaylightSavings);
  265. if (nullptr != ptzd)
  266. {
  267. ptzd->minutes = -bias;
  268. ptzd->offset = offset;
  269. ptzd->fDst = isDaylightSavings;
  270. }
  271. return tvLcl;
  272. }
  273. ///
  274. /// Use tv as the local time and return the corresponding UTC time. (Shared with hybrid debugging, which may use a fake scriptContext.)
  275. ///
  276. template <class ScriptContext>
  277. double DateImplementation::GetTvUtc(double tv, ScriptContext *scriptContext)
  278. {
  279. Assert(scriptContext);
  280. double tvUtc;
  281. if (JavascriptNumber::IsNan(tv) || !NumberUtilities::IsFinite(tv))
  282. {
  283. return JavascriptNumber::NaN;
  284. }
  285. tvUtc = scriptContext->GetDaylightTimeHelper()->LocalToUtc(tv);
  286. // See if we're out of range after conversion (UTC time value must be within this range)
  287. if (JavascriptNumber::IsNan(tvUtc) || !NumberUtilities::IsFinite(tv) || tvUtc < ktvMin || tvUtc > ktvMax)
  288. {
  289. return JavascriptNumber::NaN;
  290. }
  291. return tvUtc;
  292. }
  293. //
  294. // Get diag value display for hybrid debugging.
  295. // StringBuilder: A Js::StringBuilder/CompoundString like class, used to build strings.
  296. // ScriptContext: A Js::ScriptContext like class, which provides fields needed by Date stringify.
  297. // NewStringBuilderFunc: A function that returns a StringBuilder*, used to create a StringBuilder.
  298. //
  299. template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
  300. StringBuilder* DateImplementation::GetDiagValueString(ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder)
  301. {
  302. if (JavascriptNumber::IsNan(m_tvUtc))
  303. {
  304. StringBuilder* bs = newStringBuilder(0);
  305. bs->Append(JS_DISPLAY_STRING_INVALID_DATE);
  306. return bs;
  307. }
  308. EnsureYmdLcl(scriptContext);
  309. return GetDateDefaultString<StringBuilder>(&m_ymdLcl, &m_tzd, DateTimeFlag::None, scriptContext, newStringBuilder);
  310. }
  311. template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
  312. StringBuilder* DateImplementation::ConvertVariantDateToString(double dbl, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder)
  313. {
  314. TZD tzd;
  315. DateTime::YMD ymd;
  316. double tv = GetTvUtc(JsLocalTimeFromVarDate(dbl), scriptContext);
  317. tv = GetTvLcl(tv, scriptContext, &tzd);
  318. if (JavascriptNumber::IsNan(tv))
  319. {
  320. StringBuilder* bs = newStringBuilder(0);
  321. bs->Append(JS_DISPLAY_STRING_NAN);
  322. return bs;
  323. }
  324. GetYmdFromTv(tv, &ymd);
  325. return GetDateDefaultString<StringBuilder>(&ymd, &tzd, DateTimeFlag::None, scriptContext, newStringBuilder);
  326. }
  327. //
  328. // Get default date string, shared with hybrid debugging.
  329. // StringBuilder: A Js::StringBuilder/CompoundString like class, used to build strings.
  330. // ScriptContext: A Js::ScriptContext like class, which provides fields needed by Date stringify.
  331. // NewStringBuilderFunc: A function that returns a StringBuilder*, used to create a StringBuilder.
  332. //
  333. template <class StringBuilder, class ScriptContext, class NewStringBuilderFunc>
  334. StringBuilder* DateImplementation::GetDateDefaultString(DateTime::YMD *pymd, TZD *ptzd, DateTimeFlag noDateTime, ScriptContext* scriptContext, NewStringBuilderFunc newStringBuilder)
  335. {
  336. int hour, min;
  337. StringBuilder* const bs = newStringBuilder(72);
  338. const auto ConvertUInt16ToString_ZeroPad_2 = [](const uint16 value, char16 *const buffer, const CharCount charCapacity)
  339. {
  340. const charcount_t cchWritten = NumberUtilities::UInt16ToString(value, buffer, charCapacity, 2);
  341. Assert(cchWritten != 0);
  342. };
  343. const auto ConvertLongToString = [](const int32 value, char16 *const buffer, const CharCount charCapacity)
  344. {
  345. const errno_t err = _ltow_s(value, buffer, charCapacity, 10);
  346. Assert(err == 0);
  347. };
  348. // PART 1 - DATE part
  349. if( !(noDateTime & DateTimeFlag::NoDate))
  350. {
  351. bs->AppendChars(g_rgpszDay[pymd->wday]);
  352. bs->AppendChars(_u(' '));
  353. bs->AppendChars(g_rgpszMonth[pymd->mon]);
  354. bs->AppendChars(_u(' '));
  355. // sz - as %02d - output is "01" to "31"
  356. bs->AppendChars(static_cast<WORD>(pymd->mday + 1), 2, ConvertUInt16ToString_ZeroPad_2);
  357. bs->AppendChars(_u(' '));
  358. // year is directly after day, month, daydigit for IE11+
  359. bs->AppendChars(pymd->year, 10, ConvertLongToString);
  360. if(!(noDateTime & DateTimeFlag::NoTime))
  361. {
  362. // append a space to delimit PART 2 - if to be outputted
  363. bs->AppendChars(_u(' '));
  364. }
  365. }
  366. // PART 2 - TIME part
  367. if(!(noDateTime & DateTimeFlag::NoTime))
  368. {
  369. // sz - as %02d - HOUR
  370. bs->AppendChars(static_cast<WORD>(pymd->time / 3600000), 2, ConvertUInt16ToString_ZeroPad_2);
  371. bs->AppendChars(_u(':'));
  372. // sz - as %02d - MINUTE
  373. bs->AppendChars(static_cast<WORD>((pymd->time / 60000) % 60), 2, ConvertUInt16ToString_ZeroPad_2);
  374. bs->AppendChars(_u(':'));
  375. // sz - as %02d - SECOND
  376. bs->AppendChars(static_cast<WORD>((pymd->time / 1000) % 60), 2, ConvertUInt16ToString_ZeroPad_2);
  377. bs->AppendChars(_u(" GMT"));
  378. // IE11+
  379. min = ptzd->offset;
  380. if (min < 0)
  381. {
  382. bs->AppendChars(_u('-'));
  383. min = -min;
  384. }
  385. else
  386. {
  387. bs->AppendChars(_u('+'));
  388. }
  389. hour = min / 60;
  390. min %= 60;
  391. // sz - as %02d - HOUR
  392. bs->AppendChars(static_cast<WORD>(hour), 2, ConvertUInt16ToString_ZeroPad_2);
  393. // sz - as %02d - MIN
  394. bs->AppendChars(static_cast<WORD>(min), 2, ConvertUInt16ToString_ZeroPad_2);
  395. bs->AppendChars(_u(" ("));
  396. // check the IsDaylightSavings?
  397. if (ptzd->fDst == false)
  398. {
  399. size_t nameLength;
  400. const WCHAR *const standardName = scriptContext->GetStandardName(&nameLength, pymd);
  401. bs->AppendChars(standardName, static_cast<CharCount>(nameLength));
  402. }
  403. else
  404. {
  405. size_t nameLength;
  406. const WCHAR *const daylightName = scriptContext->GetDaylightName(&nameLength, pymd);
  407. bs->AppendChars(daylightName, static_cast<CharCount>(nameLength));
  408. }
  409. bs->AppendChars(_u(')'));
  410. }
  411. return bs;
  412. }
  413. } // namespace Js