Green
2 Oct 2007, 7:48 AM
ExtJS: 1.1
Not a bug per se, but a shortcoming for certain, and a explicit suggestion for sure.
While developing a PHP application which must be able to use SQLite, MySQL and MSSQL as backend via PDO, it became clear that there is no way to use the same general SQL for all three with the ExtJS Date.parseDate. Problem is MSSQL always gives the date back as 'Y-m-d h:i: s.xxx', where xxx is the milliseconds. This obviously mess up all datetime columns and pickers in ExtJS as it can not convert the raw date data.
After using up a day to find a way to change this default format for MSSQL for all eternity, I gave up and decided ExtJS must be able to handle these raw date values from MSSQL as well. Following is the resulting minor changes to Date.createParser and Date.formatCodeToRegex which allows ExtJS to parse the millisecond format as well:
Date.createParser = function(format) {
var funcName = "parse" + Date.parseFunctions.count++;
var regexNum = Date.parseRegexes.length;
var currentGroup = 1;
Date.parseFunctions[format] = funcName;
var code = "Date." + funcName + " = function(input){\n"
+ "var y = -1, m = -1, d = -1, h = -1, i = -1, s = -1, ms = -1, o, z, v;\n"
+ "var d = new Date();\n"
+ "y = d.getFullYear();\n"
+ "m = d.getMonth();\n"
+ "d = d.getDate();\n"
+ "var results = input.match(Date.parseRegexes[" + regexNum + "]);\n"
+ "if (results && results.length > 0) {";
var regex = "";
var special = false;
var ch = '';
for (var i = 0; i < format.length; ++i) {
ch = format.charAt(i);
if (!special && ch == "\\") {
special = true;
}
else if (special) {
special = false;
regex += String.escape(ch);
}
else {
var obj = Date.formatCodeToRegex(ch, currentGroup);
currentGroup += obj.g;
regex += obj.s;
if (obj.g && obj.c) {
code += obj.c;
}
}
}
code += "if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0 && ms >= 0)\n"
+ "{v = new Date(y, m, d, h, i, s, ms);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
+ "{v = new Date(y, m, d, h, i, s);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
+ "{v = new Date(y, m, d, h, i);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0)\n"
+ "{v = new Date(y, m, d, h);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0)\n"
+ "{v = new Date(y, m, d);}\n"
+ "else if (y >= 0 && m >= 0)\n"
+ "{v = new Date(y, m);}\n"
+ "else if (y >= 0)\n"
+ "{v = new Date(y);}\n"
+ "}return (v && (z || o))?\n" // favour UTC offset over GMT offset
+ " ((z)? v.add(Date.SECOND, (v.getTimezoneOffset() * 60) + (z*1)) :\n" // reset to UTC, then add offset
+ " v.add(Date.HOUR, (v.getGMTOffset() / 100) + (o / -100))) : v\n" // reset to GMT, then add offset
+ ";}";
Date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
eval(code);
};
// private
Date.formatCodeToRegex = function(character, currentGroup) {
switch (character) {
case "D":
return {g:0,
c:null,
s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
case "j":
return {g:1,
c:"d = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,2})"}; // day of month without leading zeroes
case "d":
return {g:1,
c:"d = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"}; // day of month with leading zeroes
case "l":
return {g:0,
c:null,
s:"(?:" + Date.dayNames.join("|") + ")"};
case "S":
return {g:0,
c:null,
s:"(?:st|nd|rd|th)"};
case "w":
return {g:0,
c:null,
s:"\\d"};
case "z":
return {g:0,
c:null,
s:"(?:\\d{1,3})"};
case "W":
return {g:0,
c:null,
s:"(?:\\d{2})"};
case "F":
return {g:1,
c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "].substring(0, 3)], 10);\n",
s:"(" + Date.monthNames.join("|") + ")"};
case "M":
return {g:1,
c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "]], 10);\n",
s:"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"};
case "n":
return {g:1,
c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
s:"(\\d{1,2})"}; // Numeric representation of a month, without leading zeros
case "m":
return {g:1,
c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
s:"(\\d{2})"}; // Numeric representation of a month, with leading zeros
case "t":
return {g:0,
c:null,
s:"\\d{1,2}"};
case "L":
return {g:0,
c:null,
s:"(?:1|0)"};
case "Y":
return {g:1,
c:"y = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{4})"};
case "y":
return {g:1,
c:"var ty = parseInt(results[" + currentGroup + "], 10);\n"
+ "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
s:"(\\d{1,2})"};
case "a":
return {g:1,
c:"if (results[" + currentGroup + "] == 'am') {\n"
+ "if (h == 12) { h = 0; }\n"
+ "} else { if (h < 12) { h += 12; }}",
s:"(am|pm)"};
case "A":
return {g:1,
c:"if (results[" + currentGroup + "] == 'AM') {\n"
+ "if (h == 12) { h = 0; }\n"
+ "} else { if (h < 12) { h += 12; }}",
s:"(AM|PM)"};
case "g":
case "G":
return {g:1,
c:"h = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,2})"}; // 12/24-hr format format of an hour without leading zeroes
case "h":
case "H":
return {g:1,
c:"h = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"}; // 12/24-hr format format of an hour with leading zeroes
case "i":
return {g:1,
c:"i = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"};
case "s":
return {g:1,
c:"s = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"};
case "x":
return {g:1,
c:"ms = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,3})"};
case "O":
return {g:1,
c:[
"o = results[", currentGroup, "];\n",
"var sn = o.substring(0,1);\n", // get + / - sign
"var hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60);\n", // get hours (performs minutes-to-hour conversion also)
"var mn = o.substring(3,5) % 60;\n", // get minutes
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))?\n", // -12hrs <= GMT offset <= 14hrs
" (sn + String.leftPad(hr, 2, 0) + String.leftPad(mn, 2, 0)) : null;\n"
].join(""),
s:"([+\-]\\d{4})"};
case "T":
return {g:0,
c:null,
s:"[A-Z]{1,4}"}; // timezone abbrev. may be between 1 - 4 chars
case "Z":
return {g:1,
c:"z = results[" + currentGroup + "];\n" // -43200 <= UTC offset <= 50400
+ "z = (-43200 <= z*1 && z*1 <= 50400)? z : null;\n",
s:"([+\-]?\\d{1,5})"}; // leading '+' sign is optional for UTC offset
default:
return {g:0,
c:null,
s:String.escape(character)};
}
};
For lack of an identifier as well as direction from PHP's date function manual for an identifier, I simply used x, i.e. 'Y-m-d h:i: s.x', as it is currently not used.
Now all works perfect :)
The change is so minor and the override so huge, that it would be a pity if I had to distribute my override code everywhere simply because ExtJS does not include these 3 lines of JS. Something which ExtJS should actually provide for as milliseconds is a standard parameter on JS 1.3 for a date object. Thus, this forum post.
Not a bug per se, but a shortcoming for certain, and a explicit suggestion for sure.
While developing a PHP application which must be able to use SQLite, MySQL and MSSQL as backend via PDO, it became clear that there is no way to use the same general SQL for all three with the ExtJS Date.parseDate. Problem is MSSQL always gives the date back as 'Y-m-d h:i: s.xxx', where xxx is the milliseconds. This obviously mess up all datetime columns and pickers in ExtJS as it can not convert the raw date data.
After using up a day to find a way to change this default format for MSSQL for all eternity, I gave up and decided ExtJS must be able to handle these raw date values from MSSQL as well. Following is the resulting minor changes to Date.createParser and Date.formatCodeToRegex which allows ExtJS to parse the millisecond format as well:
Date.createParser = function(format) {
var funcName = "parse" + Date.parseFunctions.count++;
var regexNum = Date.parseRegexes.length;
var currentGroup = 1;
Date.parseFunctions[format] = funcName;
var code = "Date." + funcName + " = function(input){\n"
+ "var y = -1, m = -1, d = -1, h = -1, i = -1, s = -1, ms = -1, o, z, v;\n"
+ "var d = new Date();\n"
+ "y = d.getFullYear();\n"
+ "m = d.getMonth();\n"
+ "d = d.getDate();\n"
+ "var results = input.match(Date.parseRegexes[" + regexNum + "]);\n"
+ "if (results && results.length > 0) {";
var regex = "";
var special = false;
var ch = '';
for (var i = 0; i < format.length; ++i) {
ch = format.charAt(i);
if (!special && ch == "\\") {
special = true;
}
else if (special) {
special = false;
regex += String.escape(ch);
}
else {
var obj = Date.formatCodeToRegex(ch, currentGroup);
currentGroup += obj.g;
regex += obj.s;
if (obj.g && obj.c) {
code += obj.c;
}
}
}
code += "if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0 && ms >= 0)\n"
+ "{v = new Date(y, m, d, h, i, s, ms);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
+ "{v = new Date(y, m, d, h, i, s);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
+ "{v = new Date(y, m, d, h, i);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0)\n"
+ "{v = new Date(y, m, d, h);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0)\n"
+ "{v = new Date(y, m, d);}\n"
+ "else if (y >= 0 && m >= 0)\n"
+ "{v = new Date(y, m);}\n"
+ "else if (y >= 0)\n"
+ "{v = new Date(y);}\n"
+ "}return (v && (z || o))?\n" // favour UTC offset over GMT offset
+ " ((z)? v.add(Date.SECOND, (v.getTimezoneOffset() * 60) + (z*1)) :\n" // reset to UTC, then add offset
+ " v.add(Date.HOUR, (v.getGMTOffset() / 100) + (o / -100))) : v\n" // reset to GMT, then add offset
+ ";}";
Date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
eval(code);
};
// private
Date.formatCodeToRegex = function(character, currentGroup) {
switch (character) {
case "D":
return {g:0,
c:null,
s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
case "j":
return {g:1,
c:"d = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,2})"}; // day of month without leading zeroes
case "d":
return {g:1,
c:"d = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"}; // day of month with leading zeroes
case "l":
return {g:0,
c:null,
s:"(?:" + Date.dayNames.join("|") + ")"};
case "S":
return {g:0,
c:null,
s:"(?:st|nd|rd|th)"};
case "w":
return {g:0,
c:null,
s:"\\d"};
case "z":
return {g:0,
c:null,
s:"(?:\\d{1,3})"};
case "W":
return {g:0,
c:null,
s:"(?:\\d{2})"};
case "F":
return {g:1,
c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "].substring(0, 3)], 10);\n",
s:"(" + Date.monthNames.join("|") + ")"};
case "M":
return {g:1,
c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "]], 10);\n",
s:"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"};
case "n":
return {g:1,
c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
s:"(\\d{1,2})"}; // Numeric representation of a month, without leading zeros
case "m":
return {g:1,
c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
s:"(\\d{2})"}; // Numeric representation of a month, with leading zeros
case "t":
return {g:0,
c:null,
s:"\\d{1,2}"};
case "L":
return {g:0,
c:null,
s:"(?:1|0)"};
case "Y":
return {g:1,
c:"y = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{4})"};
case "y":
return {g:1,
c:"var ty = parseInt(results[" + currentGroup + "], 10);\n"
+ "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
s:"(\\d{1,2})"};
case "a":
return {g:1,
c:"if (results[" + currentGroup + "] == 'am') {\n"
+ "if (h == 12) { h = 0; }\n"
+ "} else { if (h < 12) { h += 12; }}",
s:"(am|pm)"};
case "A":
return {g:1,
c:"if (results[" + currentGroup + "] == 'AM') {\n"
+ "if (h == 12) { h = 0; }\n"
+ "} else { if (h < 12) { h += 12; }}",
s:"(AM|PM)"};
case "g":
case "G":
return {g:1,
c:"h = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,2})"}; // 12/24-hr format format of an hour without leading zeroes
case "h":
case "H":
return {g:1,
c:"h = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"}; // 12/24-hr format format of an hour with leading zeroes
case "i":
return {g:1,
c:"i = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"};
case "s":
return {g:1,
c:"s = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{2})"};
case "x":
return {g:1,
c:"ms = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,3})"};
case "O":
return {g:1,
c:[
"o = results[", currentGroup, "];\n",
"var sn = o.substring(0,1);\n", // get + / - sign
"var hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60);\n", // get hours (performs minutes-to-hour conversion also)
"var mn = o.substring(3,5) % 60;\n", // get minutes
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))?\n", // -12hrs <= GMT offset <= 14hrs
" (sn + String.leftPad(hr, 2, 0) + String.leftPad(mn, 2, 0)) : null;\n"
].join(""),
s:"([+\-]\\d{4})"};
case "T":
return {g:0,
c:null,
s:"[A-Z]{1,4}"}; // timezone abbrev. may be between 1 - 4 chars
case "Z":
return {g:1,
c:"z = results[" + currentGroup + "];\n" // -43200 <= UTC offset <= 50400
+ "z = (-43200 <= z*1 && z*1 <= 50400)? z : null;\n",
s:"([+\-]?\\d{1,5})"}; // leading '+' sign is optional for UTC offset
default:
return {g:0,
c:null,
s:String.escape(character)};
}
};
For lack of an identifier as well as direction from PHP's date function manual for an identifier, I simply used x, i.e. 'Y-m-d h:i: s.x', as it is currently not used.
Now all works perfect :)
The change is so minor and the override so huge, that it would be a pity if I had to distribute my override code everywhere simply because ExtJS does not include these 3 lines of JS. Something which ExtJS should actually provide for as milliseconds is a standard parameter on JS 1.3 for a date object. Thus, this forum post.