shell.js (10258B)
1 /*--- 2 defines: [msPerHour, TZ_ADJUST, UTC_01_JAN_1900, UTC_01_JAN_2000, UTC_29_FEB_2000, UTC_01_JAN_2005, inTimeZone, withLocale, Month, assertDateTime, runDSTOffsetCachingTestsFraction] 3 allow_unused: True 4 ---*/ 5 6 /** 7 * Date functions used by tests in Date suite 8 */ 9 (function(global) { 10 const msPerDay = 1000 * 60 * 60 * 24; 11 const msPerHour = 1000 * 60 * 60; 12 global.msPerHour = msPerHour; 13 14 // Offset of tester's time zone from UTC. 15 const TZ_DIFF = GetRawTimezoneOffset(); 16 global.TZ_ADJUST = TZ_DIFF * msPerHour; 17 18 const UTC_01_JAN_1900 = -2208988800000; 19 const UTC_01_JAN_2000 = 946684800000; 20 const UTC_29_FEB_2000 = UTC_01_JAN_2000 + 31 * msPerDay + 28 * msPerDay; 21 const UTC_01_JAN_2005 = UTC_01_JAN_2000 + TimeInYear(2000) + TimeInYear(2001) + 22 TimeInYear(2002) + TimeInYear(2003) + TimeInYear(2004); 23 global.UTC_01_JAN_1900 = UTC_01_JAN_1900; 24 global.UTC_01_JAN_2000 = UTC_01_JAN_2000; 25 global.UTC_29_FEB_2000 = UTC_29_FEB_2000; 26 global.UTC_01_JAN_2005 = UTC_01_JAN_2005; 27 28 /* 29 * Originally, the test suite used a hard-coded value TZ_DIFF = -8. 30 * But that was only valid for testers in the Pacific Standard Time Zone! 31 * We calculate the proper number dynamically for any tester. We just 32 * have to be careful not to use a date subject to Daylight Savings Time... 33 */ 34 function GetRawTimezoneOffset() { 35 let t1 = new Date(2000, 1, 1).getTimezoneOffset(); 36 let t2 = new Date(2000, 1 + 6, 1).getTimezoneOffset(); 37 38 // 1) Time zone without daylight saving time. 39 // 2) Northern hemisphere with daylight saving time. 40 if ((t1 - t2) >= 0) 41 return -t1 / 60; 42 43 // 3) Southern hemisphere with daylight saving time. 44 return -t2 / 60; 45 } 46 47 function DaysInYear(y) { 48 return y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 366 : 365; 49 } 50 51 function TimeInYear(y) { 52 return DaysInYear(y) * msPerDay; 53 } 54 55 function getDefaultTimeZone() { 56 var tz = getTimeZone(); 57 switch (tz) { 58 case "EST": 59 case "EDT": 60 return "EST5EDT"; 61 62 case "CST": 63 case "CDT": 64 return "CST6CDT"; 65 66 case "MST": 67 case "MDT": 68 return "MST7MDT"; 69 70 case "PST": 71 case "PDT": 72 return "PST8PDT"; 73 74 default: 75 // Other time zones abbrevations are not supported. 76 return tz; 77 } 78 } 79 80 function getDefaultLocale() { 81 // If the default locale looks like a BCP-47 language tag, return it. 82 var locale = global.getDefaultLocale(); 83 if (locale.match(/^[a-z][a-z0-9\-]+$/i)) 84 return locale; 85 86 // Otherwise use undefined to reset to the default locale. 87 return undefined; 88 } 89 90 let defaultTimeZone = null; 91 let defaultLocale = null; 92 93 // Run the given test in the requested time zone. 94 function inTimeZone(tzname, fn) { 95 if (defaultTimeZone === null) 96 defaultTimeZone = getDefaultTimeZone(); 97 98 setTimeZone(tzname); 99 try { 100 fn(); 101 } finally { 102 setTimeZone(defaultTimeZone); 103 } 104 } 105 global.inTimeZone = inTimeZone; 106 107 // Run the given test with the requested locale. 108 function withLocale(locale, fn) { 109 if (defaultLocale === null) 110 defaultLocale = getDefaultLocale(); 111 112 setDefaultLocale(locale); 113 try { 114 fn(); 115 } finally { 116 setDefaultLocale(defaultLocale); 117 } 118 } 119 global.withLocale = withLocale; 120 121 const Month = { 122 January: 0, 123 February: 1, 124 March: 2, 125 April: 3, 126 May: 4, 127 June: 5, 128 July: 6, 129 August: 7, 130 September: 8, 131 October: 9, 132 November: 10, 133 December: 11, 134 }; 135 global.Month = Month; 136 137 const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].join("|"); 138 const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].join("|"); 139 const datePart = String.raw `(?:${weekdays}) (?:${months}) \d{2} -?\d{4,6}`; 140 const timePart = String.raw `\d{2}:\d{2}:\d{2} GMT[+-]\d{4}`; 141 const dateTimeRE = new RegExp(String.raw `^(${datePart} ${timePart})(?: \((.+)\))?$`); 142 143 function assertDateTime(date, expected, ...alternativeTimeZones) { 144 let actual = date.toString(); 145 assertEq(dateTimeRE.test(expected), true, `${expected}`); 146 assertEq(dateTimeRE.test(actual), true, `${actual}`); 147 148 let [, expectedDateTime, expectedTimeZone] = dateTimeRE.exec(expected); 149 let [, actualDateTime, actualTimeZone] = dateTimeRE.exec(actual); 150 151 assertEq(actualDateTime, expectedDateTime); 152 153 // The time zone identifier is optional, so only compare its value if 154 // it's present in |actual| and |expected|. 155 if (expectedTimeZone !== undefined && actualTimeZone !== undefined) { 156 // Test against the alternative time zone identifiers if necessary. 157 if (actualTimeZone !== expectedTimeZone) { 158 for (let alternativeTimeZone of alternativeTimeZones) { 159 if (actualTimeZone === alternativeTimeZone) { 160 expectedTimeZone = alternativeTimeZone; 161 break; 162 } 163 } 164 } 165 assertEq(actualTimeZone, expectedTimeZone); 166 } 167 } 168 global.assertDateTime = assertDateTime; 169 })(this); 170 171 172 function runDSTOffsetCachingTestsFraction(part, parts) 173 { 174 var BUGNUMBER = 563938; 175 var summary = 'Cache DST offsets to improve SunSpider score'; 176 177 print(BUGNUMBER + ": " + summary); 178 179 var MAX_UNIX_TIMET = 2145859200; // "2037-12-31T08:00:00.000Z" (PST8PDT based!) 180 var RANGE_EXPANSION_AMOUNT = 30 * 24 * 60 * 60; 181 182 /** 183 * Computes the time zone offset in minutes at the given timestamp. 184 */ 185 function tzOffsetFromUnixTimestamp(timestamp) 186 { 187 var d = new Date(NaN); 188 d.setTime(timestamp); // local slot = NaN, UTC slot = timestamp 189 return d.getTimezoneOffset(); // get UTC, calculate local => diff in minutes 190 } 191 192 /** 193 * Clear the DST offset cache, leaving it initialized to include a timestamp 194 * completely unlike the provided one (i.e. one very, very far away in time 195 * from it). Thus an immediately following lookup for the provided timestamp 196 * will cache-miss and compute a clean value. 197 */ 198 function clearDSTOffsetCache(undesiredTimestamp) 199 { 200 var opposite = (undesiredTimestamp + MAX_UNIX_TIMET / 2) % MAX_UNIX_TIMET; 201 202 // Generic purge to known, but not necessarily desired, state 203 tzOffsetFromUnixTimestamp(0); 204 tzOffsetFromUnixTimestamp(MAX_UNIX_TIMET); 205 206 // Purge to desired state. Cycle 2x in case opposite or undesiredTimestamp 207 // is close to 0 or MAX_UNIX_TIMET. 208 tzOffsetFromUnixTimestamp(opposite); 209 tzOffsetFromUnixTimestamp(undesiredTimestamp); 210 tzOffsetFromUnixTimestamp(opposite); 211 tzOffsetFromUnixTimestamp(undesiredTimestamp); 212 } 213 214 function computeCanonicalTZOffset(timestamp) 215 { 216 clearDSTOffsetCache(timestamp); 217 return tzOffsetFromUnixTimestamp(timestamp); 218 } 219 220 var TEST_TIMESTAMPS_SECONDS = 221 [ 222 // Special-ish timestamps 223 0, 224 RANGE_EXPANSION_AMOUNT, 225 MAX_UNIX_TIMET, 226 ]; 227 228 var ONE_DAY = 24 * 60 * 60; 229 var EIGHTY_THREE_HOURS = 83 * 60 * 60; 230 var NINETY_EIGHT_HOURS = 98 * 60 * 60; 231 function nextIncrement(i) 232 { 233 return i === EIGHTY_THREE_HOURS ? NINETY_EIGHT_HOURS : EIGHTY_THREE_HOURS; 234 } 235 236 // Now add a long sequence of non-special timestamps, from a fixed range, that 237 // overlaps a DST change by "a bit" on each side. 67 days should be enough 238 // displacement that we can occasionally exercise the implementation's 239 // thirty-day expansion and the DST-offset-change logic. Use two different 240 // increments just to be safe and catch something a single increment might not. 241 var DST_CHANGE_DATE = 1268553600; // March 14, 2010 242 for (var t = DST_CHANGE_DATE - 67 * ONE_DAY, 243 i = nextIncrement(NINETY_EIGHT_HOURS), 244 end = DST_CHANGE_DATE + 67 * ONE_DAY; 245 t < end; 246 i = nextIncrement(i), t += i) 247 { 248 TEST_TIMESTAMPS_SECONDS.push(t); 249 } 250 251 var TEST_TIMESTAMPS = 252 TEST_TIMESTAMPS_SECONDS.map(function(v) { return v * 1000; }); 253 254 /************** 255 * BEGIN TEST * 256 **************/ 257 258 // Compute the correct time zone offsets for all timestamps to be tested. 259 var CORRECT_TZOFFSETS = TEST_TIMESTAMPS.map(computeCanonicalTZOffset); 260 261 // Intentionally and knowingly invoking every single logic path in the cache 262 // isn't easy for a human to get right (and know he's gotten it right), so 263 // let's do it the easy way: exhaustively try all possible four-date sequences 264 // selecting from our array of possible timestamps. 265 266 var sz = TEST_TIMESTAMPS.length; 267 var start = Math.floor((part - 1) / parts * sz); 268 var end = Math.floor(part / parts * sz); 269 270 print("Exhaustively testing timestamps " + 271 "[" + start + ", " + end + ") of " + sz + "..."); 272 273 try 274 { 275 for (var i = start; i < end; i++) 276 { 277 print("Testing timestamp " + i + "..."); 278 279 var t1 = TEST_TIMESTAMPS[i]; 280 for (var j = 0; j < sz; j++) 281 { 282 var t2 = TEST_TIMESTAMPS[j]; 283 for (var k = 0; k < sz; k++) 284 { 285 var t3 = TEST_TIMESTAMPS[k]; 286 for (var w = 0; w < sz; w++) 287 { 288 var t4 = TEST_TIMESTAMPS[w]; 289 290 clearDSTOffsetCache(t1); 291 292 var tzo1 = tzOffsetFromUnixTimestamp(t1); 293 var tzo2 = tzOffsetFromUnixTimestamp(t2); 294 var tzo3 = tzOffsetFromUnixTimestamp(t3); 295 var tzo4 = tzOffsetFromUnixTimestamp(t4); 296 297 assertEq(tzo1, CORRECT_TZOFFSETS[i]); 298 assertEq(tzo2, CORRECT_TZOFFSETS[j]); 299 assertEq(tzo3, CORRECT_TZOFFSETS[k]); 300 assertEq(tzo4, CORRECT_TZOFFSETS[w]); 301 } 302 } 303 } 304 } 305 } 306 catch (e) 307 { 308 assertEq(true, false, 309 "Error when testing with timestamps " + 310 i + ", " + j + ", " + k + ", " + w + 311 " (" + t1 + ", " + t2 + ", " + t3 + ", " + t4 + ")!"); 312 } 313 314 reportCompare(true, true); 315 print("All tests passed!"); 316 }