parseISO.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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. var total = 0, accepted = 0, failed = 0;
  6. echo("////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////");
  7. echo("// Definitely valid ISO strings");
  8. echo("");
  9. echo("// Auto-generated");
  10. echo("");
  11. initializeGenerateDateStrings();
  12. var yearDigits = 0, monthDigits = 0, dayDigits = 0, hourMinuteDigits = 0, secondDigits = 0, millisecondDigits = 0;
  13. for (yearDigits = 4; yearDigits <= 6; yearDigits += 2) {
  14. dayDigits = monthDigits = 0;
  15. runGenerateTestWithValidTime();
  16. monthDigits = 2;
  17. runGenerateTestWithValidTime();
  18. dayDigits = 2;
  19. runGenerateTestWithValidTime();
  20. }
  21. function runGenerateTestWithValidTime() {
  22. millisecondDigits = secondDigits = hourMinuteDigits = 0;
  23. runGenerateTest();
  24. hourMinuteDigits = 2;
  25. runGenerateTest();
  26. secondDigits = 2;
  27. runGenerateTest();
  28. millisecondDigits = 3;
  29. runGenerateTest();
  30. }
  31. writeStats();
  32. echo("////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////");
  33. echo("// Definitely invalid ISO strings");
  34. echo("");
  35. echo("// Field value outside valid range");
  36. echo("");
  37. runTest("0001-00-01T01:01:01.001Z");
  38. runTest("0001-13-01T01:01:01.001Z");
  39. runTest("0001-01-00T01:01:01.001Z");
  40. runTest("0001-01-32T01:01:01.001Z");
  41. runTest("0001-01-01T25:01:01.001Z");
  42. runTest("0001-01-01T01:01:01.001+25:00");
  43. runTest("0001-01-01T01:60:01.001Z");
  44. runTest("0001-01-01T01:01:01.001+00:60");
  45. runTest("0001-01-01T01:01:60.001Z");
  46. echo("// Time value outside valid range");
  47. echo("");
  48. runTest("-300000-01-01T01:01:01.001Z");
  49. runTest("+300000-01-01T01:01:01.001Z");
  50. // Many other invalid ISO strings are tested in "potential cross-browser compatibility issues" section
  51. writeStats();
  52. echo("////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////");
  53. echo("// Potential cross-browser compatilibity issues");
  54. echo("");
  55. echo("// Leading and trailing whitespace, nulls, or non-whitespace non-nulls");
  56. echo("");
  57. var s = "0001-01-01T01:01:01.001Z";
  58. var spaceNulls = ["", "\0", "\t", "\n", "\v", "\f", "\r", " ", "\u00a0", "\u2028", "\u2029", "\ufeff"];
  59. for (var i = 0; i < spaceNulls.length; ++i) {
  60. if (s !== "") {
  61. runTest(spaceNulls[i] + s);
  62. runTest(s + spaceNulls[i]);
  63. }
  64. runTest(s + spaceNulls[i] + "x");
  65. }
  66. echo("// Less and more digits per field");
  67. echo("");
  68. runTest("001-01-01T01:01:01.001Z");
  69. runTest("00001-01-01T01:01:01.001Z");
  70. runTest("0001-1-01T01:01:01.001Z");
  71. runTest("0001-001-01T01:01:01.001Z");
  72. runTest("0001-01-1T01:01:01.001Z");
  73. runTest("0001-01-001T01:01:01.001Z");
  74. runTest("0001-01-01T1:01:01.001Z");
  75. runTest("0001-01-01T001:01:01.001Z");
  76. runTest("0001-01-01T01:1:01.001Z");
  77. runTest("0001-01-01T01:001:01.001Z");
  78. runTest("0001-01-01T01:01:1.001Z");
  79. runTest("0001-01-01T01:01:001.001Z");
  80. runTest("0001-01-01T01:01:01.01Z");
  81. runTest("0001-01-01T01:01:01.0001Z");
  82. echo("// Date-only forms with UTC offset");
  83. echo("");
  84. runTest("0001Z");
  85. runTest("0001-01Z");
  86. runTest("0001-01-01Z"); // note: this is rejected by the ISO parser as it should be, but it's accepted by the fallback parser
  87. echo("// Optionality of minutes");
  88. echo("");
  89. runTest("0001-01-01T01Z");
  90. runTest("0001-01-01T01:01:01.001+01");
  91. echo("// Time-only forms");
  92. echo("");
  93. runTest("T01:01Z");
  94. runTest("T01:01:01Z");
  95. runTest("T01:01:01.001Z");
  96. echo("// Field before missing optional field ending with separator");
  97. echo("");
  98. runTest("0001-");
  99. runTest("0001-01-");
  100. runTest("0001-T01:01:01.001Z");
  101. runTest("0001-01-T01:01:01.001Z");
  102. runTest("0001-01-01T01:01:Z");
  103. runTest("0001-01-01T01:01:01.Z");
  104. echo("// Optionality and type of sign on years");
  105. echo("");
  106. runTest("+0001-01-01T01:01:01.001Z");
  107. runTest("-0001-01-01T01:01:01.001Z");
  108. runTest("010000-01-01T01:01:01.001Z");
  109. runTest("-000000-01-01T01:01:01.001Z");
  110. echo("// Test support for zones without colons (DEVDIV2: 481975)");
  111. echo("");
  112. runTest("2012-02-22T03:08:26+0000");
  113. echo("// Test support for zones(Issue#1402:OS8026281)");
  114. echo("");
  115. runTest("Wed Jul 22 16:04:54 2016 +0000");
  116. runTest("Wed Jul 22 16:04:54 +0000 2016");
  117. runTest("Wed Jul 22 +0000 16:04:54 2016");
  118. runTest("Wed Jul +0000 22 16:04:54 2016");
  119. runTest("Wed +0000 Jul 22 16:04:54 2016");
  120. runTest("+0000 Wed Jul 22 16:04:54 2016");
  121. runTest("Wed Jul 22 16:04:54 2016");
  122. writeStats();
  123. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  124. // Test-specific helpers
  125. function runTest(s) {
  126. ++total;
  127. echo(s);
  128. safeCall(function () {
  129. var iso = new Date(s);
  130. var timeValue1 = iso.getTime();
  131. if (isNaN(timeValue1)) {
  132. echo(iso);
  133. } else {
  134. iso = iso.toISOString();
  135. echo(iso);
  136. var timeValue2 = new Date(iso).getTime();
  137. echo(timeValue1 + " " + (timeValue1 === timeValue2 ? "===" : "!==") + " " + timeValue2);
  138. if (iso.indexOf("Invalid", 0) === -1) {
  139. if (timeValue1 === timeValue2)
  140. ++accepted;
  141. else
  142. ++failed;
  143. }
  144. }
  145. });
  146. echo("");
  147. }
  148. function runGenerateTest() {
  149. var s =
  150. generateDateStrings(
  151. yearDigits,
  152. monthDigits,
  153. dayDigits,
  154. hourMinuteDigits,
  155. hourMinuteDigits,
  156. secondDigits,
  157. millisecondDigits);
  158. for (var i = 0; i < s.length; ++i)
  159. runTest(s[i]);
  160. }
  161. var signs, zones;
  162. function initializeGenerateDateStrings() {
  163. signs = ["+", "-"];
  164. zones = ["", "Z"];
  165. var zoneDigitCombinations = ["00", "01", "10"];
  166. for (var i = 0; i < zoneDigitCombinations.length; ++i)
  167. for (var j = 0; j < zoneDigitCombinations.length; ++j)
  168. for (var k = 0; k < signs.length; ++k)
  169. zones.push(signs[k] + zoneDigitCombinations[i] + ":" + zoneDigitCombinations[j]);
  170. }
  171. // Generates date strings in the following format:
  172. // date format: "[+|-]YYYYYY[-MM[-DD]]"
  173. // separator: "T| "
  174. // time format: "HH:mm[:ss[.sss]]"
  175. // time zone: "Z|(+|-)HH:mm"
  176. // - The separator is required only if both the date and time portions are included in the string.
  177. // - Zero-padding is optional
  178. // - Positive sign (+) is optional when the year is nonnegative
  179. // - Negative sign (-) is optional when the year is zero
  180. // - Time zone is optional
  181. //
  182. // The function will return an array of strings to test against, based on the parameters.
  183. function generateDateStrings(
  184. yearDigits, // number of digits to include for the year (0-6), 0 - exclude the year (monthDigits must also be 0)
  185. monthDigits, // number of digits to include for the month (0-2), 0 - exclude the month (dayDigits must also be 0)
  186. dayDigits, // number of digits to include for the day (0-2), 0 - exclude the day
  187. hourDigits, // number of digits to include for the hour (0-2), 0 - exclude the hour (minuteDigits must also be 0)
  188. minuteDigits, // number of digits to include for the minute (0-2), 0 - exclude the minute (hourDigits and secondDigits must also be 0)
  189. secondDigits, // number of digits to include for the second (0-2), 0 - exclude the second (millisecondDigits must also be 0)
  190. millisecondDigits) // number of digits to include for the millisecond (0-3), 0 - exclude the millisecond
  191. {
  192. if (yearDigits === 0 && monthDigits !== 0 ||
  193. monthDigits === 0 && dayDigits !== 0 ||
  194. hourDigits === 0 && minuteDigits !== 0 ||
  195. minuteDigits === 0 && (hourDigits !== 0 || secondDigits !== 0) ||
  196. secondDigits === 0 && millisecondDigits !== 0 ||
  197. yearDigits === 0 && (hourDigits === 0 || minuteDigits === 0))
  198. return [];
  199. var s = [""];
  200. if (yearDigits !== 0) {
  201. appendDigits(s, yearDigits, true);
  202. if (monthDigits !== 0) {
  203. append(s, ["-"]);
  204. appendDigits(s, monthDigits, false);
  205. if (dayDigits !== 0) {
  206. append(s, ["-"]);
  207. appendDigits(s, dayDigits, false);
  208. }
  209. }
  210. }
  211. if (hourDigits !== 0 && minuteDigits !== 0) {
  212. append(s, ["T"]);
  213. appendDigits(s, hourDigits, true);
  214. append(s, [":"]);
  215. appendDigits(s, minuteDigits, true);
  216. if (secondDigits !== 0) {
  217. append(s, [":"]);
  218. appendDigits(s, secondDigits, true);
  219. if (millisecondDigits !== 0) {
  220. append(s, ["."]);
  221. appendDigits(s, millisecondDigits, true);
  222. }
  223. }
  224. }
  225. if (yearDigits !== 0 && hourDigits !== 0 && minuteDigits !== 0)
  226. s = applyToEach(s, zones, function (str, zone) { return str + zone; });
  227. if(yearDigits === 6) {
  228. s =
  229. applyToEach(
  230. s,
  231. signs,
  232. function (str, sign) {
  233. if(sign === "-" && str.length >= 6 && str.substring(0, 6) === "000000")
  234. return undefined; // "-000000" is not allowed
  235. return sign + str;
  236. });
  237. }
  238. return s;
  239. }
  240. // Appends interesting combinations of n digits to the string array
  241. function appendDigits(a, n, includeZero) {
  242. var d = [];
  243. switch (n) {
  244. case 0:
  245. break;
  246. case 1:
  247. if (includeZero)
  248. d.push("0");
  249. d.push("1");
  250. append(a, d);
  251. break;
  252. case 3:
  253. case 6:
  254. if (n === 3)
  255. d.push("010");
  256. else
  257. d.push("010010");
  258. default:
  259. var z = zeroes(n - 1);
  260. if (includeZero)
  261. d.push(z + "0");
  262. d.push(z + "1");
  263. d.push("1" + z);
  264. append(a, d);
  265. break;
  266. }
  267. }
  268. // Returns a string of n zeroes
  269. function zeroes(n) {
  270. var s = "";
  271. while (n-- > 0)
  272. s += "0";
  273. return s;
  274. }
  275. // Appends patterns to the string array. The array is extended to acommodate the number of patterns, and the patterns are
  276. // repeated to acommodate the length of the array.
  277. function append(a, p) {
  278. extend(a, p.length);
  279. for (var i = 0; i < a.length; ++i)
  280. a[i] += p[i % p.length];
  281. }
  282. // Applies the function 'f' to each combination of elements in 'a' and 'p'. 'f' will receive the element of 'a' on which it
  283. // should apply the pattern from 'p' and it should return the modified string. The string returned by 'f' will be pushed onto a
  284. // new array, which will be returned.
  285. function applyToEach(a, p, f) {
  286. var a2 = [];
  287. for(var i = 0; i < a.length; ++i) {
  288. for(var j = 0; j < p.length; ++j) {
  289. var transformed = f(a[i], p[j]);
  290. if(transformed !== undefined)
  291. a2.push(transformed);
  292. }
  293. }
  294. return a2;
  295. }
  296. // Extends an array to have length n, by copying the last element as necessary
  297. function extend(a, n) {
  298. var originalLength = a.length;
  299. for (var i = originalLength; i < n; ++i)
  300. a.push(a[originalLength - 1]);
  301. }
  302. function writeStats() {
  303. echo("Total: " + total);
  304. echo("Accepted: " + accepted);
  305. echo("Rejected: " + (total - accepted - failed));
  306. echo("Failed: " + failed);
  307. echo("");
  308. failed = accepted = total = 0;
  309. }
  310. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  311. // General helpers
  312. function toString(o, quoteStrings) {
  313. switch (o) {
  314. case null:
  315. case undefined:
  316. return "" + o;
  317. }
  318. switch (typeof o) {
  319. case "boolean":
  320. case "number":
  321. return "" + o;
  322. case "string":
  323. {
  324. var hex = "0123456789abcdef";
  325. var s = "";
  326. for (var i = 0; i < o.length; ++i) {
  327. var c = o.charCodeAt(i);
  328. if (c === 0)
  329. s += "\\0";
  330. else if (c >= 0x20 && c < 0x7f)
  331. s += quoteStrings && o.charAt(i) === "\"" ? "\\\"" : o.charAt(i);
  332. else if (c <= 0xff)
  333. s += "\\x" + hex.charAt((c >> 4) & 0xf) + hex.charAt(c & 0xf);
  334. else
  335. s += "\\u" + hex.charAt((c >> 12) & 0xf) + hex.charAt((c >> 8) & 0xf) + hex.charAt((c >> 4) & 0xf) + hex.charAt(c & 0xf);
  336. }
  337. if (quoteStrings)
  338. s = "\"" + s + "\"";
  339. return s;
  340. }
  341. case "object":
  342. case "function":
  343. break;
  344. default:
  345. return "<unknown type '" + typeof o + "'>";
  346. }
  347. if (o instanceof Array) {
  348. var s = "[";
  349. for (var i = 0; i < o.length; ++i) {
  350. if (i)
  351. s += ", ";
  352. s += this.toString(o[i], true);
  353. }
  354. return s + "]";
  355. }
  356. if (o instanceof Error)
  357. return o.name + ": " + o.message;
  358. if (o instanceof RegExp)
  359. return o.toString() + (o.lastIndex === 0 ? "" : " (lastIndex: " + o.lastIndex + ")");
  360. return "" + o;
  361. }
  362. function echo(o) {
  363. var s = this.toString(o);
  364. try {
  365. document.write(s + "<br/>");
  366. } catch (ex) {
  367. try {
  368. WScript.Echo(s);
  369. } catch (ex2) {
  370. print(s);
  371. }
  372. }
  373. }
  374. function safeCall(f) {
  375. var args = [];
  376. for (var a = 1; a < arguments.length; ++a)
  377. args.push(arguments[a]);
  378. try {
  379. return f.apply(this, args);
  380. } catch (ex) {
  381. echo(ex);
  382. }
  383. }