/** * Date functions used by tests in Date suite */ (function(global) { const msPerDay = 1000 * 60 * 60 * 24; const msPerHour = 1000 * 60 * 60; global.msPerHour = msPerHour; // Offset of tester's time zone from UTC. const TZ_DIFF = GetRawTimezoneOffset(); global.TZ_ADJUST = TZ_DIFF * msPerHour; const UTC_01_JAN_1900 = -2208988800000; const UTC_01_JAN_2000 = 946684800000; const UTC_29_FEB_2000 = UTC_01_JAN_2000 + 31 * msPerDay + 28 * msPerDay; const UTC_01_JAN_2005 = UTC_01_JAN_2000 + TimeInYear(2000) + TimeInYear(2001) + TimeInYear(2002) + TimeInYear(2003) + TimeInYear(2004); global.UTC_01_JAN_1900 = UTC_01_JAN_1900; global.UTC_01_JAN_2000 = UTC_01_JAN_2000; global.UTC_29_FEB_2000 = UTC_29_FEB_2000; global.UTC_01_JAN_2005 = UTC_01_JAN_2005; /* * Originally, the test suite used a hard-coded value TZ_DIFF = -8. * But that was only valid for testers in the Pacific Standard Time Zone! * We calculate the proper number dynamically for any tester. We just * have to be careful not to use a date subject to Daylight Savings Time... */ function GetRawTimezoneOffset() { let t1 = new Date(2000, 1, 1).getTimezoneOffset(); let t2 = new Date(2000, 1 + 6, 1).getTimezoneOffset(); // 1) Time zone without daylight saving time. // 2) Northern hemisphere with daylight saving time. if ((t1 - t2) >= 0) return -t1 / 60; // 3) Southern hemisphere with daylight saving time. return -t2 / 60; } function DaysInYear(y) { return y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 366 : 365; } function TimeInYear(y) { return DaysInYear(y) * msPerDay; } function getDefaultTimeZone() { var tz = getTimeZone(); switch (tz) { case "EST": case "EDT": return "EST5EDT"; case "CST": case "CDT": return "CST6CDT"; case "MST": case "MDT": return "MST7MDT"; case "PST": case "PDT": return "PST8PDT"; default: // Other time zones abbrevations are not supported. return tz; } } function getDefaultLocale() { // If the default locale looks like a BCP-47 language tag, return it. var locale = global.getDefaultLocale(); if (locale.match(/^[a-z][a-z0-9\-]+$/i)) return locale; // Otherwise use undefined to reset to the default locale. return undefined; } let defaultTimeZone = null; let defaultLocale = null; // Run the given test in the requested time zone. function inTimeZone(tzname, fn) { if (defaultTimeZone === null) defaultTimeZone = getDefaultTimeZone(); setTimeZone(tzname); try { fn(); } finally { setTimeZone(defaultTimeZone); } } global.inTimeZone = inTimeZone; // Run the given test with the requested locale. function withLocale(locale, fn) { if (defaultLocale === null) defaultLocale = getDefaultLocale(); setDefaultLocale(locale); try { fn(); } finally { setDefaultLocale(defaultLocale); } } global.withLocale = withLocale; const Month = { January: 0, February: 1, March: 2, April: 3, May: 4, June: 5, July: 6, August: 7, September: 8, October: 9, November: 10, December: 11, }; global.Month = Month; const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].join("|"); const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].join("|"); const datePart = String.raw `(?:${weekdays}) (?:${months}) \d{2}`; const timePart = String.raw `\d{4,6} \d{2}:\d{2}:\d{2} GMT[+-]\d{4}`; const dateTimeRE = new RegExp(String.raw `^(${datePart} ${timePart})(?: \((.+)\))?$`); function assertDateTime(date, expected, ...alternativeTimeZones) { let actual = date.toString(); assertEq(dateTimeRE.test(expected), true, `${expected}`); assertEq(dateTimeRE.test(actual), true, `${actual}`); let [, expectedDateTime, expectedTimeZone] = dateTimeRE.exec(expected); let [, actualDateTime, actualTimeZone] = dateTimeRE.exec(actual); assertEq(actualDateTime, expectedDateTime); // The time zone identifier is optional, so only compare its value if // it's present in |actual| and |expected|. if (expectedTimeZone !== undefined && actualTimeZone !== undefined) { // Test against the alternative time zone identifiers if necessary. if (actualTimeZone !== expectedTimeZone) { for (let alternativeTimeZone of alternativeTimeZones) { if (actualTimeZone === alternativeTimeZone) { expectedTimeZone = alternativeTimeZone; break; } } } assertEq(actualTimeZone, expectedTimeZone); } } global.assertDateTime = assertDateTime; })(this); function runDSTOffsetCachingTestsFraction(part, parts) { var BUGNUMBER = 563938; var summary = 'Cache DST offsets to improve SunSpider score'; print(BUGNUMBER + ": " + summary); var MAX_UNIX_TIMET = 2145859200; // "2037-12-31T08:00:00.000Z" (PST8PDT based!) var RANGE_EXPANSION_AMOUNT = 30 * 24 * 60 * 60; /** * Computes the time zone offset in minutes at the given timestamp. */ function tzOffsetFromUnixTimestamp(timestamp) { var d = new Date(NaN); d.setTime(timestamp); // local slot = NaN, UTC slot = timestamp return d.getTimezoneOffset(); // get UTC, calculate local => diff in minutes } /** * Clear the DST offset cache, leaving it initialized to include a timestamp * completely unlike the provided one (i.e. one very, very far away in time * from it). Thus an immediately following lookup for the provided timestamp * will cache-miss and compute a clean value. */ function clearDSTOffsetCache(undesiredTimestamp) { var opposite = (undesiredTimestamp + MAX_UNIX_TIMET / 2) % MAX_UNIX_TIMET; // Generic purge to known, but not necessarily desired, state tzOffsetFromUnixTimestamp(0); tzOffsetFromUnixTimestamp(MAX_UNIX_TIMET); // Purge to desired state. Cycle 2x in case opposite or undesiredTimestamp // is close to 0 or MAX_UNIX_TIMET. tzOffsetFromUnixTimestamp(opposite); tzOffsetFromUnixTimestamp(undesiredTimestamp); tzOffsetFromUnixTimestamp(opposite); tzOffsetFromUnixTimestamp(undesiredTimestamp); } function computeCanonicalTZOffset(timestamp) { clearDSTOffsetCache(timestamp); return tzOffsetFromUnixTimestamp(timestamp); } var TEST_TIMESTAMPS_SECONDS = [ // Special-ish timestamps 0, RANGE_EXPANSION_AMOUNT, MAX_UNIX_TIMET, ]; var ONE_DAY = 24 * 60 * 60; var EIGHTY_THREE_HOURS = 83 * 60 * 60; var NINETY_EIGHT_HOURS = 98 * 60 * 60; function nextIncrement(i) { return i === EIGHTY_THREE_HOURS ? NINETY_EIGHT_HOURS : EIGHTY_THREE_HOURS; } // Now add a long sequence of non-special timestamps, from a fixed range, that // overlaps a DST change by "a bit" on each side. 67 days should be enough // displacement that we can occasionally exercise the implementation's // thirty-day expansion and the DST-offset-change logic. Use two different // increments just to be safe and catch something a single increment might not. var DST_CHANGE_DATE = 1268553600; // March 14, 2010 for (var t = DST_CHANGE_DATE - 67 * ONE_DAY, i = nextIncrement(NINETY_EIGHT_HOURS), end = DST_CHANGE_DATE + 67 * ONE_DAY; t < end; i = nextIncrement(i), t += i) { TEST_TIMESTAMPS_SECONDS.push(t); } var TEST_TIMESTAMPS = TEST_TIMESTAMPS_SECONDS.map(function(v) { return v * 1000; }); /************** * BEGIN TEST * **************/ // Compute the correct time zone offsets for all timestamps to be tested. var CORRECT_TZOFFSETS = TEST_TIMESTAMPS.map(computeCanonicalTZOffset); // Intentionally and knowingly invoking every single logic path in the cache // isn't easy for a human to get right (and know he's gotten it right), so // let's do it the easy way: exhaustively try all possible four-date sequences // selecting from our array of possible timestamps. var sz = TEST_TIMESTAMPS.length; var start = Math.floor((part - 1) / parts * sz); var end = Math.floor(part / parts * sz); print("Exhaustively testing timestamps " + "[" + start + ", " + end + ") of " + sz + "..."); try { for (var i = start; i < end; i++) { print("Testing timestamp " + i + "..."); var t1 = TEST_TIMESTAMPS[i]; for (var j = 0; j < sz; j++) { var t2 = TEST_TIMESTAMPS[j]; for (var k = 0; k < sz; k++) { var t3 = TEST_TIMESTAMPS[k]; for (var w = 0; w < sz; w++) { var t4 = TEST_TIMESTAMPS[w]; clearDSTOffsetCache(t1); var tzo1 = tzOffsetFromUnixTimestamp(t1); var tzo2 = tzOffsetFromUnixTimestamp(t2); var tzo3 = tzOffsetFromUnixTimestamp(t3); var tzo4 = tzOffsetFromUnixTimestamp(t4); assertEq(tzo1, CORRECT_TZOFFSETS[i]); assertEq(tzo2, CORRECT_TZOFFSETS[j]); assertEq(tzo3, CORRECT_TZOFFSETS[k]); assertEq(tzo4, CORRECT_TZOFFSETS[w]); } } } } } catch (e) { assertEq(true, false, "Error when testing with timestamps " + i + ", " + j + ", " + k + ", " + w + " (" + t1 + ", " + t2 + ", " + t3 + ", " + t4 + ")!"); } reportCompare(true, true); print("All tests passed!"); }