PDA

View Full Version : [1.0.1a / 1.1b1] Ext Date class bugs + some proposed fixes



mystix
22 Jun 2007, 9:45 AM
I'm consolidating previously reported and unresolved Ext Date class bugs in this thread, as well as some new bugs i discovered while investigating bug 3:



Datefield: Validating localized dates [forum thread] (http://extjs.com/forum/showthread.php?t=6345)
"T" format date pattern fails on linux FF [forum thread] (http://extjs.com/forum/showthread.php?t=5906)
Date.parseDate format code O doesn't work [forum thread] (http://extjs.com/forum/showthread.php?t=8026)
getGMTOffset() method returns incorrect offset string
getTimezone() method returns full date string instead of timezone abbreviation
Date.parseDate() Z option bug [forum thread] (http://extjs.com/forum/showthread.php?t=8263)

[bug 1]
Can't seem to reproduce the bug on my system. Anyone with a non-English locale willing to take this up?

[bugs 2 - 5]
proposed fixes follow (refer to comments for fixes):
Ext.override(Date, {
getTimezone : function() {
// timezone abbreviations may contain 1 - 4 chars (http://www.timeanddate.com/library/abbreviations/timezones/)
return this.toString().replace(/^.*? ([A-Z]{1,4})[\-+][0-9]{4} .*$/, "$1");
},

getGMTOffset : function() {
return (this.getTimezoneOffset() > 0 ? "-" : "+") // leading + / - sign is already handled here
+ String.leftPad(Math.abs(Math.floor(this.getTimezoneOffset() / 60)), 2, "0") // remove leading + / - sign before left-padding with zeroes
+ String.leftPad(this.getTimezoneOffset() % 60, 2, "0");
}
});

Ext.apply(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, o, z;\n"
+ "var d = new Date();\n"
+ "y = d.getFullYear();\n"
+ "m = d.getMonth();\n"
+ "d = d.getDate();\n"
+ "var v = null, 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)\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);
},

formatCodeToRegex : function(character, currentGroup) {
switch (character) {
case "D":
return {g:0,
c:null,
s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
case "j":
case "d":
return {g:1,
c:"d = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,2})"};
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":
case "m":
return {g:1,
c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
s:"(\\d{1,2})"};
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":
case "h":
case "H":
return {g:1,
c:"h = parseInt(results[" + currentGroup + "], 10);\n",
s:"(\\d{1,2})"};
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 "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 contain 1 - 4 chars
case "Z":
return {g:1,
c:"z = results[" + currentGroup + "];\n" // -43200 < UTC offset < 50400 (http://www.php.net/date)
+ "z = (-43200 <= z*1 && z*1 <= 50400)? z : null;\n",
s:"([+\-]?\\d{1,5})"}; // leading '+' sign is optional for positive offsets
default:
return {g:0,
c:null,
s:String.escape(character)};
}
}
});


test cases (note: returned Date objects are applicable for my locale only (GMT+0800). adjust the expected results for your own locale) :
// before applying fixes
(new Date()).getGMTOffset(); // returns "+-800"
(new Date()).getTimezone(); // returns "Sat Jun 23 2007 01:28:44 GMT+0800 (Malay Peninsula Standard Time)"
Date.parseDate('2007-06-13T12:19:58-0700', 'Y-m-d\\TH:i:sO'); // returns Wed Jun 13 2007 12:19:58 GMT-0400 (Eastern Daylight Time)
Date.parseDate((new Date()).format('Y-m-d H:i:s Z'), 'Y-m-d H:i:s Z'); // returns null

// after applying fixes
(new Date()).getGMTOffset(); // returns "+0800"
(new Date()).getTimezone(); // returns "GMT"
Date.parseDate('2007-06-13T12:19:58-0700', 'Y-m-d\\TH:i:sO'); // returns Thu Jun 14 2007 03:19:58 GMT+0800 (Malay Peninsula Standard Time)
Date.parseDate((new Date()).format('Y-m-d H:i:s Z'), 'Y-m-d H:i:s Z'); // returns "Mon Jul 23 2007 03:16:46 GMT+0800 (Malay Peninsula Standard Time)"

// other test cases
Date.parseDate('2007-06-13T12:19:58-3600', 'Y-m-d\\TH:i:sZ'); // returns Wed Jun 13 2007 05:19:58 GMT+0800 (Malay Peninsula Standard Time)
Date.parseDate('2007-06-13T12:19:58-0700-3600', 'Y-m-d\\TH:i:sOZ'); // returns Wed Jun 13 2007 05:19:58 GMT+0800 (Malay Peninsula Standard Time)

mystix
26 Jun 2007, 9:05 AM
[edit 1]
adjusted fix to address latest bug reported here (http://extjs.com/forum/showthread.php?t=8263)

[edit 2]
fixed UTC offset calculation bug

[edit 3]
fixed variable leak

[edit 4]
fixed in Ext 1.1 beta 2 (http://extjs.com/deploy/ext-1.1-beta2.zip)