| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RuntimeLibraryPch.h"
- #include "DateImplementationData.h"
- #include "CharClassifier.h"
- namespace Js {
- static double ConvertToInteger(double dbl)
- {
- Assert(Js::NumberUtilities::IsFinite(dbl));
- if (Js::NumberUtilities::LuHiDbl(dbl) & 0x80000000)
- {
- Js::NumberUtilities::LuHiDbl(dbl) &= 0x7FFFFFFF;
- dbl = floor(dbl);
- Js::NumberUtilities::LuHiDbl(dbl) |= 0x80000000;
- }
- else
- {
- dbl = floor(dbl);
- // We have to do this because some implementations map 0.5 to -0.
- Js::NumberUtilities::LuHiDbl(dbl) &= 0x7FFFFFFF;
- }
- return dbl;
- }
- static const double kdblHalfSecond = 0.5 / 86400.0;
- struct SZS
- {
- const char16 *psz; // string
- short cch; // length of string
- short szst; // type of entry
- int32 lwVal; // value
- };
- BEGIN_ENUM_BYTE(ParseStringTokenType)
- AmPm,
- Month,
- Day,
- Zone,
- BcAd,
- END_ENUM_BYTE()
- const static SZS g_rgszs[] =
- {
- #define Szs(sz, val) { _u(sz), _countof(_u(sz)) - 1, ParseStringTokenType::##szst, val }
- // bc and ad
- #undef szst
- #define szst BcAd
- Szs("bc", -1),
- Szs("b.c", -1),
- Szs("ad", +1),
- Szs("a.d", +1),
- // am and pm
- #undef szst
- #define szst AmPm
- Szs("am", -1),
- Szs("a.m", -1),
- Szs("pm", +1),
- Szs("p.m", +1),
- // time zones
- #undef szst
- #define szst Zone
- Szs("est", -5 * 60),
- Szs("edt", -4 * 60),
- Szs("cst", -6 * 60),
- Szs("cdt", -5 * 60),
- Szs("mst", -7 * 60),
- Szs("mdt", -6 * 60),
- Szs("pst", -8 * 60),
- Szs("pdt", -7 * 60),
- Szs("gmt", 0),
- Szs("utc", 0),
- // week days
- #undef szst
- #define szst Day
- Szs("sunday", 0),
- Szs("monday", 1),
- Szs("tuesday", 2),
- Szs("wednesday", 3),
- Szs("thursday", 4),
- Szs("friday", 5),
- Szs("saturday", 6),
- // months
- #undef szst
- #define szst Month
- Szs("january", 0),
- Szs("february", 1),
- Szs("march", 2),
- Szs("april", 3),
- Szs("may", 4),
- Szs("june", 5),
- Szs("july", 6),
- Szs("august", 7),
- Szs("september", 8),
- Szs("october", 9),
- Szs("november", 10),
- Szs("december", 11),
- #undef szst
- #undef Szs
- };
- const int32 kcszs = sizeof(g_rgszs) / sizeof(SZS);
- ///----------------------------------------------------------------------------
- ///----------------------------------------------------------------------------
- ///
- /// class DateImplementation
- ///
- ///----------------------------------------------------------------------------
- ///----------------------------------------------------------------------------
- DateImplementation::DateImplementation(double value)
- {
- // Assume DateImplementation is allocated in the recycler and is zero initialized
- // Do not stack allocate of this struct, as it doesn't initialize all fields.
- // If the stack allocated struct is copied in to recycler allocated ones, it
- // many introduce false reference from the stack
- Assert(!ThreadContext::IsOnStack(this));
- AssertValue<byte>(this, 0, sizeof(DateImplementation));
- m_modified = false;
- SetTvUtc(value);
- };
- double
- DateImplementation::GetMilliSeconds()
- {
- return m_tvUtc;
- }
- double
- DateImplementation::NowFromHiResTimer(ScriptContext* scriptContext)
- {
- // Use current time.
- return scriptContext->GetThreadContext()->GetHiResTimer()->Now();
- }
- double
- DateImplementation::NowInMilliSeconds(ScriptContext * scriptContext)
- {
- return DoubleToTvUtc(DateImplementation::NowFromHiResTimer(scriptContext));
- }
- JavascriptString*
- DateImplementation::GetString(DateStringFormat dsf,
- ScriptContext* requestContext, DateTimeFlag noDateTime)
- {
- if (JavascriptNumber::IsNan(m_tvUtc))
- {
- return requestContext->GetLibrary()->GetInvalidDateString();
- }
- switch (dsf)
- {
- default:
- EnsureYmdLcl(requestContext);
- return GetDateDefaultString(&m_ymdLcl, &m_tzd, noDateTime, requestContext);
- #ifdef ENABLE_GLOBALIZATION
- case DateStringFormat::Locale:
- EnsureYmdLcl(requestContext);
- if( m_ymdLcl.year > 1600 && m_ymdLcl.year < 10000 )
- {
- // The year falls in the range which can be handled by both the Win32
- // function GetDateFormat and the COM+ date type
- // - the latter is for forward compatibility with JS 7.
- JavascriptString *bs = GetDateLocaleString(&m_ymdLcl, &m_tzd, noDateTime, requestContext);
- if (bs != nullptr)
- {
- return bs;
- }
- else
- {
- return GetDateDefaultString(&m_ymdLcl, &m_tzd, noDateTime, requestContext);
- }
- }
- else
- {
- return GetDateDefaultString(&m_ymdLcl, &m_tzd, noDateTime, requestContext);
- }
- #endif
- case DateStringFormat::GMT:
- EnsureYmdUtc();
- return GetDateGmtString(&m_ymdUtc, requestContext);
- }
- }
- JavascriptString*
- DateImplementation::GetISOString(ScriptContext* requestContext)
- {
- // ES5 15.9.5.43: throw RangeError if time value is not a finite number
- if (!Js::NumberUtilities::IsFinite(m_tvUtc))
- {
- JavascriptError::ThrowRangeError(requestContext, JSERR_NeedNumber);
- }
- CompoundString *const bs = CompoundString::NewWithCharCapacity(30, requestContext->GetLibrary());
- GetDateComponent(bs, DateData::FullYear, 0, requestContext);
- bs->AppendChars(_u('-'));
- // month
- GetDateComponent(bs, DateData::Month, 1/*adjustment*/, requestContext);
- bs->AppendChars(_u('-'));
- // date
- GetDateComponent(bs, DateData::Date, 0, requestContext);
- bs->AppendChars(_u('T'));
- // hours
- GetDateComponent(bs, DateData::Hours, 0, requestContext);
- bs->AppendChars(_u(':'));
- // minutes
- GetDateComponent(bs, DateData::Minutes, 0, requestContext);
- bs->AppendChars(_u(':'));
- // seconds
- GetDateComponent(bs, DateData::Seconds, 0, requestContext);
- // ES5 fill in milliseconds but v5.8 does not
- bs->AppendChars(_u('.'));
- // milliseconds
- GetDateComponent(bs, DateData::Milliseconds, 0, requestContext);
- bs->AppendChars(_u('Z'));
- return bs;
- }
- void
- DateImplementation::GetDateComponent(CompoundString *bs, DateData componentType, int adjust,
- ScriptContext* requestContext)
- {
- double value = this->GetDateData(componentType, true /* fUTC */, requestContext);
- if(Js::NumberUtilities::IsFinite(value))
- {
- const int ival = (int)value + adjust;
- const int ivalAbs = ival < 0 ? -ival : ival;
- switch(componentType)
- {
- case DateData::FullYear:
- if(ival < 0 || ival > 9999)
- {
- // ES5 spec section 15.9.1.15.1 states that for years outside the range 0-9999, the expanded year
- // representation should:
- // - always include the sign
- // - have 2 extra digits (6 digits total)
- bs->AppendChars(ival < 0 ? _u('-') : _u('+'));
- if(ivalAbs < 100000)
- {
- bs->AppendChars(_u('0'));
- if(ivalAbs < 10000)
- {
- bs->AppendChars(_u('0'));
- }
- }
- }
- // Years are zero-padded to at least 4 digits in ES5
- if(ivalAbs < 1000)
- {
- bs->AppendChars(_u('0'));
- // will fall through to next case for additional padding
- }
- else
- {
- break;
- }
- // fall through
- case DateData::Milliseconds:
- if (ivalAbs < 100)
- {
- bs->AppendChars(_u('0'));
- // will fall through to next case for additional padding
- }
- else
- {
- break;
- }
- // fall through
- default:
- if (ivalAbs < 10)
- {
- bs->AppendChars(_u('0'));
- }
- }
- // _itow_s makes use of max 12 bytes for a base-10 32-bit int (_u("-2147483648\0")), although we don't need the sign
- // and our numbers shouldn't be that big anyway
- bs->AppendChars(
- ivalAbs,
- 10,
- [](const int value, char16 *const buffer, const CharCount charCapacity)
- {
- errno_t err = _itow_s(value, buffer, charCapacity, 10);
- Assert(err == 0);
- });
- }
- }
- ///----------------------------------------------------------------------------
- ///
- /// Use tv as the UTC time.
- ///
- ///----------------------------------------------------------------------------
- void
- DateImplementation::SetTvUtc(double tv)
- {
- m_grfval = 0;
- m_tvUtc = DoubleToTvUtc(tv);
- }
- double
- DateImplementation::DoubleToTvUtc(double tv)
- {
- if (JavascriptNumber::IsNan(tv) || tv < ktvMin || tv > ktvMax)
- {
- return JavascriptNumber::NaN;
- }
- return CONFIG_FLAG(HighPrecisionDate)? tv : ConvertToInteger(tv);
- }
- ///----------------------------------------------------------------------------
- ///
- /// Use tv as the local time and set m_tvUtc appropriately.
- ///
- ///----------------------------------------------------------------------------
- void
- DateImplementation::SetTvLcl(double tv, ScriptContext* requestContext)
- {
- m_grfval = 0;
- m_tvUtc = GetTvUtc(tv, requestContext);
- }
- JavascriptString*
- DateImplementation::ConvertVariantDateToString(double dbl, ScriptContext* scriptContext)
- {
- Js::DateImplementation::TZD tzd;
- DateTime::YMD ymd;
- double tv = Js::DateImplementation::GetTvUtc(Js::DateImplementation::JsLocalTimeFromVarDate(dbl), scriptContext);
- tv = Js::DateImplementation::GetTvLcl(tv, scriptContext, &tzd);
- if (Js::JavascriptNumber::IsNan(tv))
- {
- return JavascriptNumber::ToStringNan(scriptContext);
- }
- Js::DateImplementation::GetYmdFromTv(tv, &ymd);
- return DateImplementation::GetDateDefaultString(&ymd, &tzd, 0, scriptContext);
- }
- JavascriptString*
- DateImplementation::GetDateDefaultString(DateTime::YMD *pymd, TZD *ptzd,DateTimeFlag noDateTime,ScriptContext* scriptContext)
- {
- return GetDateDefaultString<CompoundString>(pymd, ptzd, noDateTime, scriptContext,
- [=](CharCount capacity) -> CompoundString*
- {
- return CompoundString::NewWithCharCapacity(capacity, scriptContext->GetLibrary());
- });
- }
- JavascriptString*
- DateImplementation::GetDateGmtString(DateTime::YMD *pymd,ScriptContext* scriptContext)
- {
- // toUTCString() or toGMTString() will return for example:
- // "Thu, 02 Feb 2012 09:02:03 GMT" for versions IE11 or above
- CompoundString *const bs = CompoundString::NewWithCharCapacity(30, scriptContext->GetLibrary());
- 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);
- };
- bs->AppendChars(g_rgpszDay[pymd->wday]);
- 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(' '));
- bs->AppendChars(g_rgpszMonth[pymd->mon]);
- bs->AppendChars(_u(' '));
- // Add the year.
- if ((pymd->year) > 0)
- {
- bs->AppendChars(pymd->year, 10, ConvertUInt32ToString_ZeroPad_4);
- }
- else
- {
- int positiveYear = -(pymd->year); // pymd->year is negative
- bs->AppendChars(_u('-'));
- bs->AppendChars(positiveYear, 10, ConvertUInt32ToString_ZeroPad_4);
- }
- // Add the time.
- bs->AppendChars(_u(' '));
- // 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(' '));
- bs->AppendChars(_u("GMT"));
- return bs;
- }
- #ifdef ENABLE_GLOBALIZATION
- JavascriptString*
- DateImplementation::GetDateLocaleString(DateTime::YMD *pymd, TZD *ptzd, DateTimeFlag noDateTime,ScriptContext* scriptContext)
- {
- SYSTEMTIME st;
- int cch;
- int count = 0;
- const int kcchMax = 256;
- WCHAR wszBuf[kcchMax];
- WCHAR *pwszBuf, *pToBeFreed = NULL, *p;
- JavascriptString *bs = nullptr;
- // the caller of this function should ensure that the range of pymd->year is such that the following conversion works.
- st.wYear = (WORD)pymd->year;
- st.wMonth = (WORD)pymd->mon + 1;
- st.wDayOfWeek = (WORD)pymd->wday;
- st.wDay = (WORD)pymd->mday + 1;
- st.wHour = (WORD)(pymd->time / 3600000);
- st.wMinute = (WORD)((pymd->time / 60000) % 60);
- st.wSecond = (WORD)((pymd->time / 1000) % 60);
- st.wMilliseconds = (WORD)(pymd->time % 60);
- cch = 0;
- LCID lcid = GetUserDefaultLCID();
- if( !(noDateTime & DateTimeFlag::NoDate))
- {
- DWORD dwFormat = DATE_LONGDATE;
- if ((PRIMARYLANGID(LANGIDFROMLCID(lcid)) == LANG_ARABIC) ||
- (PRIMARYLANGID(LANGIDFROMLCID(lcid)) == LANG_HEBREW))
- {
- dwFormat |= DATE_RTLREADING;
- }
- int c = GetDateFormatW( lcid, dwFormat, &st, NULL, NULL, 0 );
- if( c <= 0 )
- {
- if (PRIMARYLANGID(LANGIDFROMLCID(lcid)) == LANG_HEBREW)
- {
- // Can't support some Hebrew dates - current limit is 1 Jan AD 2240
- Js::JavascriptError::ThrowRangeError(scriptContext, VBSERR_CantDisplayDate);
- }
- AssertMsg(false, "GetDateFormat failed");
- goto Error;
- }
- cch += c;
- }
- if( !(noDateTime & DateTimeFlag::NoTime))
- {
- int c = GetTimeFormatW( lcid, 0, &st, NULL, NULL, 0 );
- if( c <= 0 )
- {
- AssertMsg(false, "GetTimeFormat failed");
- goto Error;
- }
- cch += c;
- }
- cch++; // For the space between the date and the time.
- if( cch > kcchMax )
- {
- pwszBuf = pToBeFreed = (WCHAR *)malloc( cch * sizeof(WCHAR) );
- if(!pwszBuf)
- {
- Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
- }
- }
- else
- {
- wszBuf[0] = '\0';
- pwszBuf = wszBuf;
- }
- count = cch;
- p = pwszBuf;
- if( !(noDateTime & DateTimeFlag::NoDate))
- {
- DWORD dwFormat = DATE_LONGDATE;
- if ((PRIMARYLANGID(LANGIDFROMLCID(lcid)) == LANG_ARABIC) ||
- (PRIMARYLANGID(LANGIDFROMLCID(lcid)) == LANG_HEBREW))
- {
- dwFormat |= DATE_RTLREADING;
- }
- int c = GetDateFormatW( lcid, dwFormat, &st, NULL, p, cch );
- if( c <= 0 || c > cch)
- {
- if (PRIMARYLANGID(LANGIDFROMLCID(lcid)) == LANG_HEBREW)
- {
- // Can't support some Hebrew dates - current limit is 1 Jan AD 2240
- Js::JavascriptError::ThrowRangeError(scriptContext, VBSERR_CantDisplayDate);
- }
- AssertMsg(false, "GetDateFormat failed");
- goto Error;
- }
- p += (c-1);
- cch -= (c-1);
- if( !(noDateTime & DateTimeFlag::NoTime))
- {
- *p++ = _u(' ');
- cch--;
- }
- }
- if( !(noDateTime & DateTimeFlag::NoTime))
- {
- int c = GetTimeFormatW( lcid, 0, &st, NULL, p, cch );
- Assert( c > 0 );
- if( c <= 0 )
- {
- AssertMsg(false, "GetTimeFormat failed");
- goto Error;
- }
- cch -= (c-1);
- }
- bs = JavascriptString::NewCopyBuffer(pwszBuf, count-cch, scriptContext);
- Error:
- if( pToBeFreed )
- free(pToBeFreed);
- return bs;
- }
- #endif // ENABLE_GLOBALIZATION
- double
- DateImplementation::GetDateData(DateData dd, bool fUtc, ScriptContext* scriptContext)
- {
- DateTime::YMD *pymd;
- double value = 0;
- if (JavascriptNumber::IsNan(m_tvUtc))
- {
- return m_tvUtc;
- }
- if (fUtc)
- {
- EnsureYmdUtc();
- pymd = &m_ymdUtc;
- }
- else
- {
- EnsureYmdLcl(scriptContext);
- pymd = &m_ymdLcl;
- }
- switch (dd)
- {
- case DateData::Year:
- Assert(scriptContext);
- // WOOB bug 1099381: ES5 spec B.2.4: getYear() must return YearFromTime() - 1900.
- // Note that negative value is OK for the spec.
- value = pymd->year - 1900;
- break;
- case DateData::FullYear:
- value = pymd->year;
- break;
- case DateData::Month:
- value = pymd->mon;
- break;
- case DateData::Date:
- value = pymd->mday + 1;
- break;
- case DateData::Day:
- value = pymd->wday;
- break;
- case DateData::Hours:
- value = (pymd->time / 3600000);
- break;
- case DateData::Minutes:
- value = (pymd->time / 60000) % 60;
- break;
- case DateData::Seconds:
- value = (pymd->time / 1000) % 60;
- break;
- case DateData::Milliseconds:
- value = pymd->time % 1000;
- break;
- case DateData::TimezoneOffset:
- //Assert(!fUtc);
- value = (m_tvUtc - m_tvLcl) / 60000;
- break;
- default:
- // Shouldn't come here
- AssertMsg(false, "DateData type invalid");
- }
- return value;
- }
- inline bool
- DateImplementation::FBig(char16 ch)
- {
- return (unsigned int)ch >= 128;
- }
- inline bool
- DateImplementation::FDateDelimiter(char16 ch)
- {
- return (ch == '/' || ch == '-');
- }
- ///------------------------------------------------------------------------------
- // Parse a string as a date and return the number of milliseconds since
- // January 1, 1970 GMT.
- //
- // TODO: This function is ported from IE8 jscript engine. This lengthy
- // and needs a cleanup - break into smaller functions.
- ///------------------------------------------------------------------------------
- double DateImplementation::UtcTimeFromStr(ScriptContext *scriptContext, JavascriptString *pParseString)
- {
- Assert(pParseString != nullptr);
- double dbl;
- if (scriptContext->GetLastUtcTimeFromStr(pParseString, dbl))
- {
- return dbl;
- }
- unsigned int ulength = pParseString->GetLength();
- const char16 *psz = pParseString->GetSz();
- if(UtcTimeFromStrCore(psz, ulength, dbl, scriptContext))
- {
- scriptContext->SetLastUtcTimeFromStr(pParseString, dbl);
- return dbl;
- }
- Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
- }
- bool DateImplementation::TryParseDecimalDigits(
- const char16 *const str,
- const size_t length,
- const size_t startIndex,
- const size_t numDigits,
- int &value)
- {
- Assert(str);
- Assert(length);
- Assert(startIndex <= length);
- Assert(numDigits != 0 && numDigits <= 9); // will fit in an 'int'
- if(numDigits > length - startIndex)
- return false;
- size_t i = 0;
- // Skip leading zeroes
- while(str[startIndex + i] == _u('0') && ++i < numDigits);
- // Parse remaining digits
- int v = 0;
- for(; i < numDigits; ++i)
- {
- const unsigned short d = str[startIndex + i] - _u('0');
- if(d > 9)
- break;
- v = v * 10 + d;
- }
- if(i < numDigits)
- return false;
- Assert(i == numDigits);
- value = v;
- // The next character must not be a digit
- return !(i < length - startIndex && static_cast<unsigned short>(str[startIndex + i] - _u('0')) <= 9);
- }
- // Either 1 digit or 2 digits or 3 digits
- // Ignore any digits after the third
- bool DateImplementation::TryParseMilliseconds(
- const char16 *const str,
- const size_t length,
- const size_t startIndex,
- int &value,
- size_t &foundDigits)
- {
- const size_t minNumDigits = 1;
- Assert(str);
- Assert(length);
- Assert(startIndex <= length);
- size_t allDigits = length - startIndex;
- if(allDigits < minNumDigits)
- return false;
- size_t i = 0;
- // Skip leading zeroes
- while(str[startIndex + i] == _u('0') && ++i < allDigits);
- // Parse remaining digits
- int v = 0;
- for(; i < allDigits ; ++i)
- {
- const unsigned short d = str[startIndex + i] - _u('0');
- if(d > 9)
- break;
- if (i < 3) // not past the 3rd digit in the milliseconds, don't ignore
- v = v * 10 + d;
- }
- if(i < minNumDigits)
- return false;
- foundDigits = i;
- if (foundDigits == 1)
- v = v * 100;
- else if (foundDigits == 2)
- v = v * 10;
- value = v;
- // The next character must not be a digit
- return !(i < length - startIndex && static_cast<unsigned short>(str[startIndex + i] - _u('0')) <= 9);
- }
- bool DateImplementation::TryParseTwoDecimalDigits(
- const char16 *const str,
- const size_t length,
- const size_t startIndex,
- int &value,
- bool canHaveTrailingDigit /* = false */)
- {
- Assert(str);
- Assert(length);
- Assert(startIndex <= length);
- if(length - startIndex < 2)
- return false;
- unsigned short d = str[startIndex] - _u('0');
- if(d > 9)
- return false;
- short v = d * 10;
- d = str[startIndex + 1] - _u('0');
- if(d > 9)
- return false;
- value = v + d;
- // The next character must not be a digit if canHaveTrailingDigit is false
- bool hasNoTrailingDigit = !(length - startIndex > 2 && static_cast<unsigned short>(str[startIndex + 2] - _u('0')) <= 9);
- return canHaveTrailingDigit || hasNoTrailingDigit;
- }
- bool DateImplementation::TryParseIsoString(const char16 *const str, const size_t length, double &timeValue, ScriptContext *scriptContext)
- {
- Assert(str);
- size_t i = 0;
- const Js::CharClassifier *classifier = scriptContext->GetCharClassifier();
- // Skip leading whitespace (for cross-browser compatibility)
- // Also skip bidirectional characters, for Round tripping locale formatted date
- while ((classifier->IsWhiteSpace(str[i]) || classifier->IsBiDirectionalChar(str[i])) && ++i < length);
- // Minimum length must be 4 (YYYY)
- if(length - i < 4)
- return false;
- // YYYY|(+|-)YYYYYY
- int year;
- switch(str[i])
- {
- case _u('+'):
- ++i;
- if(!TryParseDecimalDigits(str, length, i, 6, year))
- return false;
- i += 6;
- break;
- case _u('-'):
- ++i;
- if(!TryParseDecimalDigits(str, length, i, 6, year) || year == 0)
- return false;
- year = -year;
- i += 6;
- break;
- case _u('0'):
- case _u('1'):
- case _u('2'):
- case _u('3'):
- case _u('4'):
- case _u('5'):
- case _u('6'):
- case _u('7'):
- case _u('8'):
- case _u('9'):
- if(!TryParseDecimalDigits(str, length, i, 4, year))
- return false;
- i += 4;
- break;
- default:
- return false;
- }
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- int month = 0,
- day = 0,
- timePortionMilliseconds = 0,
- utcOffsetMilliseconds = 0;
- bool isLocalTime = false;
- do // while(false);
- {
- do // while(false);
- {
- // -MM
- if(i >= length || str[i] != _u('-'))
- break;
- ++i;
- if(!TryParseTwoDecimalDigits(str, length, i, month))
- return false;
- --month;
- if(month < 0 || month > 11)
- return false;
- i += 2;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- // -DD
- if(i >= length || str[i] != _u('-'))
- break;
- ++i;
- if(!TryParseTwoDecimalDigits(str, length, i, day))
- return false;
- --day;
- if(day < 0 || day > 30)
- return false;
- i += 2;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- } while(false);
- // THH:mm
- if(i >= length || str[i] != _u('T'))
- break;
- ++i;
- int t;
- if(!TryParseTwoDecimalDigits(str, length, i, t) || t > 24)
- return false;
- timePortionMilliseconds += t * (60 * 60 * 1000);
- i += 2;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- if(i >= length || str[i] != _u(':'))
- return false;
- ++i;
- if(!TryParseTwoDecimalDigits(str, length, i, t) || t > 59)
- return false;
- timePortionMilliseconds += t * (60 * 1000);
- i += 2;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- do // while(false);
- {
- // :ss
- if(i >= length || str[i] != _u(':'))
- break;
- ++i;
- if(!TryParseTwoDecimalDigits(str, length, i, t) || t > 59)
- return false;
- timePortionMilliseconds += t * 1000;
- i += 2;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- // .sss
- if(i >= length || str[i] != _u('.'))
- break;
- ++i;
- // Require one or more decimal digits. Ignore digits beyond the third
- size_t foundDigits = 0;
- if(!TryParseMilliseconds(str, length, i, t, foundDigits))
- return false;
- timePortionMilliseconds += t;
- i += foundDigits;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- } while(false);
- // Z|(+|-)HH:mm
- if(i >= length)
- {
- isLocalTime = true;
- break;
- }
- const char16 utcOffsetSign = str[i];
- if(utcOffsetSign == _u('Z'))
- {
- ++i;
- break;
- }
- if(utcOffsetSign != _u('+') && utcOffsetSign != _u('-'))
- {
- isLocalTime = true;
- break;
- }
- ++i;
- // In -version:6 we allow optional colons in the timezone offset
- if (!TryParseTwoDecimalDigits(str, length, i, t, scriptContext->GetConfig()->IsES6DateParseFixEnabled() /* Timezone may be 4 sequential digits */) || t > 24)
- return false;
- utcOffsetMilliseconds += t * (60 * 60 * 1000);
- i += 2;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- if(i >= length)
- return false;
- // The ':' is optional in ISO 8601
- if (str[i] == _u(':'))
- {
- ++i;
- }
- if(!TryParseTwoDecimalDigits(str, length, i, t) || t > 59)
- return false;
- utcOffsetMilliseconds += t * (60 * 1000);
- i += 2;
- // Skip bidirectional characters, for Round tripping locale formatted date
- i += classifier->SkipBiDirectionalChars(str, i, length);
- if(utcOffsetSign == _u('-'))
- utcOffsetMilliseconds = -utcOffsetMilliseconds;
- } while(false);
- // Skip trailing whitespace (for cross-browser compatibility)
- // Skip bidirectional characters, for Round tripping locale formatted date
- while (i < length && (classifier->IsWhiteSpace(str[i]) || classifier->IsBiDirectionalChar(str[i])))
- ++i;
- // There should only have been whitespace remaining, if any
- if(i < length)
- return false;
- Assert(i == length);
- // Compute the time value
- timeValue = TvFromDate(year, month, day, timePortionMilliseconds - utcOffsetMilliseconds);
- if (isLocalTime)
- {
- // Compatibility note:
- // In ES5, it was unspecified how to handle date strings without the trailing time zone offset "Z|(+|-)HH:mm".
- // In ES5.1, an absent time zone offset defaulted to "Z", which contradicted ISO8601:2004(E).
- // This was corrected in an ES5.1 errata note. Moreover, the ES6 draft now follows ISO8601.
- timeValue = GetTvUtc(timeValue, scriptContext);
- }
- return true;
- }
- bool DateImplementation::UtcTimeFromStrCore(
- __in_ecount_z(ulength) const char16 *psz,
- unsigned int ulength,
- double &retVal,
- ScriptContext *const scriptContext)
- {
- Assert(scriptContext);
- if (ulength >= 0x7fffffff)
- {
- //Prevent unreasonable requests from causing overflows.
- return false;
- }
- if (nullptr == psz)
- {
- retVal = JavascriptNumber::NaN;
- return true;
- }
- // Try to parse the string as the ISO format first
- if(TryParseIsoString(psz, ulength, retVal, scriptContext))
- {
- return true;
- }
- enum
- {
- ssNil,
- ssMinutes,
- ssSeconds,
- ssMillisecond,
- ssAddOffset,
- ssSubOffset,
- ssDate,
- ssMonth,
- ssYear
- };
- char16 *pchBase;
- char16 *pch;
- char16 ch;
- char16 *pszSrc = nullptr;
- const int32 lwNil = 0x80000000;
- int32 cch;
- int32 depth;
- int32 lwT;
- int32 lwYear = lwNil;
- int32 lwMonth = lwNil;
- int32 lwDate = lwNil;
- int32 lwTime = lwNil;
- int32 lwMillisecond = lwNil;
- int32 lwZone = lwNil;
- int32 lwOffset = lwNil;
- int32 ss = ssNil;
- const SZS *pszs;
- bool fUtc;
- int tAmPm = 0;
- int tBcAd = 0;
- size_t numOfDigits = 0;
- double tv = JavascriptNumber::NaN; // Initialized for error handling.
- //Create a copy to analyze
- BEGIN_TEMP_ALLOCATOR(tempAllocator, scriptContext, _u("UtcTimeFromStr"));
- pszSrc = AnewArray(tempAllocator, char16, ulength + 1);
- size_t size = sizeof(char16) * (ulength + 1);
- js_memcpy_s(pszSrc, size, psz, size);
- _wcslwr_s(pszSrc,ulength+1);
- bool isDateNegativeVersion5 = false;
- bool isNextFieldDateNegativeVersion5 = false;
- bool isZeroPaddedYear = false;
- const Js::CharClassifier *classifier = scriptContext->GetCharClassifier();
- #pragma prefast(suppress: __WARNING_INCORRECT_VALIDATION, "pch is guaranteed to be null terminated by __in_z on psz and js_memcpy_s copying the null byte")
- for (pch = pszSrc; 0 != (ch = classifier->SkipBiDirectionalChars(pch));)
- {
- pch++;
- if (ch <= ' ')
- {
- continue;
- }
- switch (ch)
- {
- case '(':
- {
- // skip stuff in parens
- for (depth = 1; 0 != (ch = *pch); )
- {
- pch++;
- if (ch == '(')
- {
- depth++;
- }
- else if (ch == ')' && --depth <= 0)
- {
- break;
- }
- }
- continue;
- }
- case ',':
- case ':':
- case '/':
- {
- // ignore these
- continue;
- }
- case '+':
- {
- if (lwNil != lwTime)
- {
- ss = ssAddOffset;
- }
- continue;
- }
- case '-':
- {
- if (lwNil != lwTime)
- {
- ss = ssSubOffset;
- }
- continue;
- }
- }
- pchBase = pch - 1;
- if (!FBig(ch) && isalpha(ch))
- {
- for ( ; !FBig(*pch) && (isalpha(*pch) || '.' == *pch); pch++)
- ;
- cch = (int32)(pch - pchBase);
- if ('.' == pchBase[cch - 1])
- {
- cch--;
- }
- //Assert(cch > 0);
- // skip to the next real character
- while (0 != (*pch) && (*pch <= ' ' || classifier->IsBiDirectionalChar(*pch)))
- {
- pch++;
- }
- // have an alphabetic token - look it up
- if (cch == 1)
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- // military version of time zone
- // z = GMT
- // j isn't used
- // a to m are 1 to 12
- // n to y are -1 to -12
- if (lwNil != lwZone)
- {
- goto LError;
- }
- if (ch <= 'm')
- {
- if (ch == 'j' || ch < 'a')
- {
- goto LError;
- }
- lwZone = (int32)(ch - 'a' + (ch < 'j')) * 60;
- }
- else if (ch <= 'y')
- {
- lwZone = -(int32)(ch - 'm') * 60;
- }
- else if (ch == 'z')
- {
- lwZone = 0;
- }
- else
- {
- goto LError;
- }
- // look for a time zone offset
- ss = ('+' == *pch) ? (pch++, ssAddOffset) :
- ('-' == *pch) ? (pch++, ssSubOffset) : ssNil;
- continue;
- }
- // look for a token
- for (pszs = g_rgszs + kcszs; ; )
- {
- if (pszs-- <= g_rgszs)
- goto LError;
- if (cch <= pszs->cch &&
- 0 == memcmp(pchBase, pszs->psz, cch * sizeof(char16)))
- {
- break;
- }
- }
- switch (pszs->szst)
- {
- case ParseStringTokenType::BcAd:
- {
- if (tBcAd != 0)
- {
- goto LError;
- }
- tBcAd = (int)pszs->lwVal;
- break;
- }
- case ParseStringTokenType::AmPm:
- {
- if (tAmPm != 0)
- {
- goto LError;
- }
- tAmPm = (int)pszs->lwVal;
- break;
- }
- case ParseStringTokenType::Month:
- {
- if (lwNil != lwMonth)
- {
- goto LError;
- }
- lwMonth = pszs->lwVal;
- if ('-' == *pch)
- {
- // handle the case date is negative for "Thu, 23 Sep -0007 00:00:00 GMT"
- isDateNegativeVersion5 = true;
- pch++;
- }
- break;
- }
- case ParseStringTokenType::Zone:
- {
- if (lwNil != lwZone)
- {
- goto LError;
- }
- lwZone = pszs->lwVal;
- // look for a time zone offset
- ss = ('+' == *pch) ? (pch++, ssAddOffset) :
- ('-' == *pch) ? (pch++, ssSubOffset) : ssNil;
- break;
- }
- }
- continue;
- }
- if (FBig(ch) || !isdigit(ch))
- {
- goto LError;
- }
- for (lwT = ch - '0'; ; pch++)
- {
- // for time zone offset HH:mm, we already got HH so skip ':' and grab mm
- if (((ss == ssAddOffset) || (ss == ssSubOffset)) && (*pch == ':'))
- {
- continue;
- }
- if (FBig(*pch) || !isdigit(*pch))
- {
- break;
- }
- // to avoid overflow
- if (pch - pchBase > 6)
- {
- goto LError;
- }
- // convert string to number, e.g. 07:30 -> 730
- lwT = lwT * 10 + *pch - '0';
- }
- numOfDigits = pch - pchBase;
- // skip to the next real character
- while (0 != (ch = *pch) && (ch <= ' ' || classifier->IsBiDirectionalChar(ch)))
- {
- pch++;
- }
- switch (ss)
- {
- case ssAddOffset:
- case ssSubOffset:
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- if (lwNil != lwOffset)
- goto LError;
- // convert into minutes, e.g. 730 -> 7*60+30
- lwOffset = lwT < 24 ? lwT * 60 :
- (lwT % 100) + (lwT / 100) * 60;
- if (ssSubOffset == ss)
- lwOffset = -lwOffset;
- lwZone = 0; // An offset is always with respect to UTC
- ss = ssNil;
- break;
- }
- case ssMinutes:
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- if (lwT >= 60)
- goto LError;
- lwTime += lwT * 60;
- ss = (ch == ':') ? (pch++, ssSeconds) : ssNil;
- break;
- }
- case ssSeconds:
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- if (lwT >= 60)
- goto LError;
- lwTime += lwT;
- ss = (ch == '.') ? (pch++, ssMillisecond) : ssNil;
- break;
- }
- case ssMillisecond:
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- if (numOfDigits <= 1)
- {
- // 1 digit only, treat it as hundreds
- lwMillisecond = lwT * 100;
- }
- else if (numOfDigits <= 2)
- {
- // 2 digit only, treat it as tens
- lwMillisecond = lwT * 10;
- }
- else if (numOfDigits <= 3)
- {
- // canonical 3 digit, per EcmaScript spec
- lwMillisecond = lwT;
- }
- else
- {
- // ignore any digits beyond the third
- lwMillisecond = (pchBase[0] - '0') * 100 + (pchBase[1] - '0') * 10 + (pchBase[2] - '0');
- }
- ss = ssNil;
- break;
- }
- case ssDate:
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- if (lwNil != lwDate)
- goto LError;
- lwDate = lwT;
- if ((lwNil == lwYear) && FDateDelimiter(ch))
- {
- // We have already parsed the year if the date is specified as YYYY/MM/DD,
- // but not when it is specified as MM/DD/YYYY.
- ss = ssYear;
- pch++;
- }
- else
- {
- ss = ssNil;
- }
- break;
- }
- case ssMonth:
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- if (lwNil != lwMonth)
- {
- goto LError;
- }
- lwMonth = lwT - 1;
- if (FDateDelimiter(ch))
- {
- // Mark the next token as the date so that it won't be confused with another token.
- // For example, if the next character is '-' as in "2015-1-1", then it'd be used as
- // the time offset without this.
- ss = ssDate;
- pch++;
- }
- break;
- }
- case ssYear:
- {
- AssertMsg(isNextFieldDateNegativeVersion5 == false, "isNextFieldDateNegativeVersion5 == false");
- if (lwNil != lwYear)
- goto LError;
- AssertMsg(isDateNegativeVersion5 == false, "lwYear should be positive as pre-version:5 parsing");
- lwYear = lwT;
- ss = ssNil;
- if (lwT < 1000 && numOfDigits >= 4)
- {
- isZeroPaddedYear = true;
- }
- break;
- }
- default:
- {
- // assumptions for getting a YEAR:
- // - an absolute value greater or equal than 70 (thus not hour!)
- // - wasn't preceded by negative sign for -version:5 year format
- // - lwT has at least 4 digits (e.g. 0017 is year 17 AD)
- if (lwT >= 70 || isNextFieldDateNegativeVersion5 || numOfDigits >= 4)
- {
- // assume it's a year - this is used particularly as version:5 year parsing
- if (lwNil != lwYear)
- goto LError;
- // handle the case date is negative for "Tue Feb 02 -2012 01:02:03 GMT-0800"
- lwYear = isDateNegativeVersion5 ? -lwT : lwT;
- isNextFieldDateNegativeVersion5 = false;
- if (lwT < 1000 && numOfDigits >= 4)
- {
- isZeroPaddedYear = true;
- }
- if (FDateDelimiter(ch))
- {
- // Mark the next token as the month so that it won't be confused with another token.
- // For example, if the next character is '-' as in "2015-1-1", then it'd be used as
- // the time offset without this.
- ss = ssMonth;
- pch++;
- }
- break;
- }
- switch (ch)
- {
- case ':':
- {
- // hour
- if (lwNil != lwTime)
- goto LError;
- if (lwT >= 24)
- goto LError;
- lwTime = lwT * 3600;
- ss = ssMinutes;
- pch++;
- break;
- }
- case '/':
- case '-':
- {
- // month
- if (lwNil != lwMonth)
- {
- // can be year
- if (lwNil != lwYear)
- {
- // both were already parsed!
- goto LError;
- }
- else
- {
- // this is a day - with the negative sign for the date (version 5+)
- lwDate = lwT;
- isDateNegativeVersion5 = true;
- // mark the next field to be year (version 5+)
- isNextFieldDateNegativeVersion5 = true;
- }
- }
- else
- {
- // this is a month
- lwMonth = lwT - 1;
- ss = ssDate;
- }
- pch++;
- break;
- }
- default:
- {
- // date
- if (lwNil != lwDate)
- goto LError;
- lwDate = lwT;
- break;
- }
- }
- break;
- }
- }
- continue;
- }
- if (lwNil == lwYear ||
- lwNil == lwMonth || lwMonth > 11 ||
- lwNil == lwDate || lwDate > 31)
- {
- goto LError;
- }
- if (tBcAd != 0)
- {
- if (tBcAd < 0)
- {
- // BC. Note that 1 BC is year 0 and 2 BC is year -1.
- lwYear = -lwYear + 1;
- }
- }
- else if (lwYear < 50 && isDateNegativeVersion5 == false && isZeroPaddedYear == false)
- {
- lwYear += 2000;
- }
- else if (lwYear < 100 && isDateNegativeVersion5 == false && isZeroPaddedYear == false)
- {
- lwYear += 1900;
- }
- if (tAmPm != 0)
- {
- if (lwNil == lwTime)
- {
- goto LError;
- }
- if (lwTime >= 12 * 3600L && lwTime < 13 * 3600L)
- {
- // In the 12:00 hour. AM means subtract 12 hours and PM means
- // do nothing.
- if (tAmPm < 0)
- {
- lwTime -= 12 * 3600L;
- }
- }
- else
- {
- // Not in the 12:00 hour. AM means do nothing and PM means
- // add 12 hours.
- if (tAmPm > 0)
- {
- if (lwTime >= 12 * 3600L)
- {
- goto LError;
- }
- lwTime += 12 * 3600L;
- }
- }
- }
- else if (lwNil == lwTime)
- {
- lwTime = 0;
- }
- if (lwNil == lwMillisecond)
- {
- lwMillisecond = 0;
- }
- if (lwNil != lwZone)
- {
- lwTime -= lwZone * 60;
- fUtc = TRUE;
- }
- else
- {
- fUtc = FALSE;
- }
- if (lwNil != lwOffset)
- {
- lwTime -= lwOffset * 60;
- }
- // Rebuild time.
- tv = TvFromDate(lwYear, lwMonth, lwDate - 1, (double)lwTime * 1000 + lwMillisecond);
- if (!fUtc)
- {
- tv = GetTvUtc(tv, scriptContext);
- }
- LError:
- END_TEMP_ALLOCATOR(tempAllocator, scriptContext);
- retVal = tv;
- return true;
- }
- //------------------------------------
- //Convert a utc time to a variant date.
- //------------------------------------
- double DateImplementation::VarDateFromJsUtcTime(double dbl, ScriptContext * scriptContext)
- {
- Assert(scriptContext);
- // Convert to local time.
- dbl = Js::DateImplementation::GetTvLcl(dbl, scriptContext);
- if (!Js::NumberUtilities::IsFinite(dbl))
- return Js::JavascriptNumber::NaN;
- // Convert to an automation date.
- dbl = dbl / 86400000 + g_kdblJanuary1st1970;
- // dbl is the actual number of days since 0000h 12/30/1899.
- // Convert this to a true Automation-style date.
- if (dbl < 0.0)
- {
- // This works around a bug in OLE Automation.
- // If a date is negative _and_ less than 500
- // milliseconds before midnight then Automation will
- // "round" it to two days earlier. To work around this
- // bug, round dates just before midnight to midnight.
- double dblT;
- dbl = 2.0 * floor(dbl) - dbl;
- dblT = dbl - floor(dbl);
- if (dblT <= kdblHalfSecond && 0.0 < dblT)
- dbl = ceil(dbl) + 1.0;
- }
- return dbl;
- }
- double DateImplementation::JsUtcTimeFromVarDate(double dbl, ScriptContext * scriptContext)
- {
- Assert(scriptContext);
- return GetTvUtc(JsLocalTimeFromVarDate(dbl), scriptContext);
- }
- double DateImplementation::DateFncUTC(ScriptContext* scriptContext, Arguments args)
- {
- const int kcvarMax = 7;
- double rgdbl[kcvarMax];
- double tv;
- double dblT;
- uint ivar;
- // See: https://github.com/Microsoft/ChakraCore/issues/1665
- // Date.UTC should return NaN with 0 arguments.
- // args.Info.Count includes an implicit first parameter, so we check for Count <= 1.
- if (args.Info.Count <= 1)
- {
- return JavascriptNumber::NaN;
- }
- for (ivar = 0; (ivar < (args.Info.Count-1)) && ivar < kcvarMax; ++ivar)
- {
- rgdbl[ivar] = JavascriptConversion::ToNumber(args[ivar+1],scriptContext);
- }
- for (ivar = 0; ivar < kcvarMax; ivar++)
- {
- // Unspecified parameters are treated like zero, except date, which
- // is treated as 1.
- if (ivar >= (args.Info.Count - 1))
- {
- rgdbl[ivar] = (ivar == 2);
- continue;
- }
- #pragma prefast(suppress:6001, "rgdbl index ivar < args.Info.Count - 1 are initialized")
- dblT = rgdbl[ivar];
- if (!Js::NumberUtilities::IsFinite(dblT))
- {
- return JavascriptNumber::NaN;
- }
- rgdbl[ivar] = ConvertToInteger(dblT);
- }
- // adjust the year
- if (rgdbl[0] < 100 && rgdbl[0] >= 0)
- {
- rgdbl[0] += 1900;
- }
- // Get the UTC time value.
- tv = TvFromDate(rgdbl[0], rgdbl[1], rgdbl[2] - 1,
- rgdbl[3] * 3600000 + rgdbl[4] * 60000 + rgdbl[5] * 1000 + rgdbl[6]);
- if (tv < ktvMin || tv > ktvMax)
- {
- return JavascriptNumber::NaN;
- }
- else
- {
- return tv;
- }
- }
- // Maximum number of arguments used by this set operation.
- static const int mpddcvar[] =
- {
- 1, // Year
- 3, // FullYear
- 2, // Month
- 1, // Date
- 4, // Hours
- 3, // Minutes
- 2, // Seconds
- 1, // Milliseconds
- 0, // Day (shouldn't happen)
- 0, // TimezoneOffset (shouldn't happen)
- };
- double DateImplementation::SetDateData(Arguments args, DateData dd, bool fUtc, ScriptContext* scriptContext)
- {
- // This must accommodate the largest cvar in mpddcvar.
- double rgdbl[5];
- double tv = 0;
- DateTime::YMD *pymd = NULL;
- DateTime::YMD emptyYMD = {0};
- uint count = 0;
- uint cvarMax;
- uint ivar;
- // PREFAST: check limits
- if (dd < 0 || dd >= DateData::Lim)
- {
- AssertMsg(false, "DateData type invalid");
- Js::JavascriptError::ThrowError(scriptContext, VBSERR_InternalError);
- }
- // Get the parameters.
- cvarMax = mpddcvar[dd];
- __analysis_assume(cvarMax <= 4);
- //
- // arg[0] would be the date object itself
- //
- for (ivar = 0; (ivar < (args.Info.Count-1)) && ivar < cvarMax; ++ivar)
- {
- rgdbl[ivar] = JavascriptConversion::ToNumber(args[ivar+1],scriptContext);
- if (!Js::NumberUtilities::IsFinite(rgdbl[ivar]))
- goto LSetNan;
- rgdbl[ivar] = ConvertToInteger(rgdbl[ivar]);
- }
- if ((count = ivar) < 1)
- {
- goto LSetNan;
- }
- if (JavascriptNumber::IsNan(m_tvUtc))
- {
- // If the current time is not finite, the only way we can end up
- // with non-NaN is for setFullYear/setYear.
- // See ES5 15.9.5.40, ES5 B.2.5.
- if (!(DateData::FullYear == dd || DateData::Year == dd))
- {
- goto LSetNan;
- }
- pymd = &emptyYMD; // We need mon, mday, time to be 0.
- // Fall through to DateData::Year and DataData::FullYear cases below.
- }
- else
- {
- if (fUtc)
- {
- EnsureYmdUtc();
- pymd = &m_ymdUtc;
- tv = m_tvUtc;
- }
- else
- {
- EnsureYmdLcl(scriptContext);
- pymd = &m_ymdLcl;
- tv = m_tvLcl;
- }
- }
- ivar = 0;
- switch (dd)
- {
- case DateData::Year:
- if (rgdbl[0] < 100 && rgdbl[0] >= 0)
- rgdbl[0] += 1900;
- // fall-through
- case DateData::FullYear:
- LFullYear:
- if (count < 3)
- {
- // Only {year} or {year, month} is specified. Day is not specified.
- rgdbl[2] = pymd->mday + 1;
- if (count < 2)
- {
- // Month is not specified.
- rgdbl[1] = pymd->mon;
- }
- }
- tv = TvFromDate(rgdbl[0], rgdbl[1], rgdbl[2] - 1, pymd->time);
- break;
- case DateData::Month:
- memmove(rgdbl + 1, rgdbl, count * sizeof(double));
- count++;
- rgdbl[0] = pymd->year;
- goto LFullYear;
- case DateData::Date:
- tv += (rgdbl[ivar] - pymd->mday - 1) * 86400000;
- if (++ivar >= count)
- {
- break;
- }
- // fall-through
- case DateData::Hours:
- tv += (rgdbl[ivar] - (pymd->time / 3600000)) * 3600000;
- if (++ivar >= count)
- {
- break;
- }
- // fall-through
- case DateData::Minutes:
- tv += (rgdbl[ivar] - (pymd->time / 60000) % 60) * 60000;
- if (++ivar >= count)
- {
- break;
- }
- // fall-through
- case DateData::Seconds:
- tv += (rgdbl[ivar] - (pymd->time / 1000) % 60) * 1000;
- if (++ivar >= count)
- {
- break;
- }
- // fall-through
- case DateData::Milliseconds:
- tv += rgdbl[ivar] - pymd->time % 1000;
- break;
- default:
- AssertMsg(false, "DataData type invalid");
- }
- if (fUtc)
- {
- SetTvUtc(tv);
- }
- else
- {
- SetTvLcl(tv, scriptContext);
- }
- m_modified = true;
- return m_tvUtc;
- LSetNan:
- m_grfval = 0;
- m_tvUtc = JavascriptNumber::NaN;
- m_modified = true;
- return m_tvUtc;
- }
- } // namespace Js
|