Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / lang / Date.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.Date
17  * A set of useful static methods to deal with date
18  * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
19  * this object for convenience
20  *
21  * The date parsing and formatting syntax contains a subset of
22  * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
23  * supported will provide results equivalent to their PHP versions.
24  *
25  * The following is a list of all currently supported formats:
26  * <pre class="">
27 Format  Description                                                               Example returned values
28 ------  -----------------------------------------------------------------------   -----------------------
29   d     Day of the month, 2 digits with leading zeros                             01 to 31
30   D     A short textual representation of the day of the week                     Mon to Sun
31   j     Day of the month without leading zeros                                    1 to 31
32   l     A full textual representation of the day of the week                      Sunday to Saturday
33   N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
34   S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
35   w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
36   z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
37   W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
38   F     A full textual representation of a month, such as January or March        January to December
39   m     Numeric representation of a month, with leading zeros                     01 to 12
40   M     A short textual representation of a month                                 Jan to Dec
41   n     Numeric representation of a month, without leading zeros                  1 to 12
42   t     Number of days in the given month                                         28 to 31
43   L     Whether it&#39;s a leap year                                                  1 if it is a leap year, 0 otherwise.
44   o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
45         belongs to the previous or next year, that year is used instead)
46   Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
47   y     A two digit representation of a year                                      Examples: 99 or 03
48   a     Lowercase Ante meridiem and Post meridiem                                 am or pm
49   A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
50   g     12-hour format of an hour without leading zeros                           1 to 12
51   G     24-hour format of an hour without leading zeros                           0 to 23
52   h     12-hour format of an hour with leading zeros                              01 to 12
53   H     24-hour format of an hour with leading zeros                              00 to 23
54   i     Minutes, with leading zeros                                               00 to 59
55   s     Seconds, with leading zeros                                               00 to 59
56   u     Decimal fraction of a second                                              Examples:
57         (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
58                                                                                   100 (i.e. 0.100s) or
59                                                                                   999 (i.e. 0.999s) or
60                                                                                   999876543210 (i.e. 0.999876543210s)
61   O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
62   P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
63   T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
64   Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
65   c     ISO 8601 date
66         Notes:                                                                    Examples:
67         1) If unspecified, the month / day defaults to the current month / day,   1991 or
68            the time defaults to midnight, while the timezone defaults to the      1992-10 or
69            browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
70            and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
71            are optional.                                                          1995-07-18T17:21:28-02:00 or
72         2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
73            least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
74            of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
75         Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
76         date-time granularity which are supported, or see                         2000-02-13T21:25:33
77         http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
78   U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
79   MS    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
80                                                                                   \/Date(1238606590509+0800)\/
81 </pre>
82  *
83  * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
84  * <pre><code>
85 // Sample date:
86 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
87
88 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
89 console.log(Ext.Date.format(dt, 'Y-m-d'));                          // 2007-01-10
90 console.log(Ext.Date.format(dt, 'F j, Y, g:i a'));                  // January 10, 2007, 3:05 pm
91 console.log(Ext.Date.format(dt, 'l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
92 </code></pre>
93  *
94  * Here are some standard date/time patterns that you might find helpful.  They
95  * are not part of the source of Ext.Date, but to use them you can simply copy this
96  * block of code into any script that is included after Ext.Date and they will also become
97  * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
98  * <pre><code>
99 Ext.Date.patterns = {
100     ISO8601Long:"Y-m-d H:i:s",
101     ISO8601Short:"Y-m-d",
102     ShortDate: "n/j/Y",
103     LongDate: "l, F d, Y",
104     FullDateTime: "l, F d, Y g:i:s A",
105     MonthDay: "F d",
106     ShortTime: "g:i A",
107     LongTime: "g:i:s A",
108     SortableDateTime: "Y-m-d\\TH:i:s",
109     UniversalSortableDateTime: "Y-m-d H:i:sO",
110     YearMonth: "F, Y"
111 };
112 </code></pre>
113  *
114  * Example usage:
115  * <pre><code>
116 var dt = new Date();
117 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
118 </code></pre>
119  * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
120  * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
121  * @singleton
122  */
123
124 /*
125  * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
126  * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
127  * They generate precompiled functions from format patterns instead of parsing and
128  * processing each pattern every time a date is formatted. These functions are available
129  * on every Date object.
130  */
131
132 (function() {
133
134 // create private copy of Ext's Ext.util.Format.format() method
135 // - to remove unnecessary dependency
136 // - to resolve namespace conflict with MS-Ajax's implementation
137 function xf(format) {
138     var args = Array.prototype.slice.call(arguments, 1);
139     return format.replace(/\{(\d+)\}/g, function(m, i) {
140         return args[i];
141     });
142 }
143
144 Ext.Date = {
145     /**
146      * Returns the current timestamp
147      * @return {Date} The current timestamp
148      * @method
149      */
150     now: Date.now || function() {
151         return +new Date();
152     },
153
154     /**
155      * @private
156      * Private for now
157      */
158     toString: function(date) {
159         var pad = Ext.String.leftPad;
160
161         return date.getFullYear() + "-"
162             + pad(date.getMonth() + 1, 2, '0') + "-"
163             + pad(date.getDate(), 2, '0') + "T"
164             + pad(date.getHours(), 2, '0') + ":"
165             + pad(date.getMinutes(), 2, '0') + ":"
166             + pad(date.getSeconds(), 2, '0');
167     },
168
169     /**
170      * Returns the number of milliseconds between two dates
171      * @param {Date} dateA The first date
172      * @param {Date} dateB (optional) The second date, defaults to now
173      * @return {Number} The difference in milliseconds
174      */
175     getElapsed: function(dateA, dateB) {
176         return Math.abs(dateA - (dateB || new Date()));
177     },
178
179     /**
180      * Global flag which determines if strict date parsing should be used.
181      * Strict date parsing will not roll-over invalid dates, which is the
182      * default behaviour of javascript Date objects.
183      * (see {@link #parse} for more information)
184      * Defaults to <tt>false</tt>.
185      * @type Boolean
186     */
187     useStrict: false,
188
189     // private
190     formatCodeToRegex: function(character, currentGroup) {
191         // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
192         var p = utilDate.parseCodes[character];
193
194         if (p) {
195           p = typeof p == 'function'? p() : p;
196           utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
197         }
198
199         return p ? Ext.applyIf({
200           c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
201         }, p) : {
202             g: 0,
203             c: null,
204             s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
205         };
206     },
207
208     /**
209      * <p>An object hash in which each property is a date parsing function. The property name is the
210      * format string which that function parses.</p>
211      * <p>This object is automatically populated with date parsing functions as
212      * date formats are requested for Ext standard formatting strings.</p>
213      * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
214      * may be used as a format string to {@link #parse}.<p>
215      * <p>Example:</p><pre><code>
216 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
217 </code></pre>
218      * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
219      * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
220      * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
221      * (i.e. prevent javascript Date "rollover") (The default must be false).
222      * Invalid date strings should return null when parsed.</div></li>
223      * </ul></div></p>
224      * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
225      * formatting function must be placed into the {@link #formatFunctions} property.
226      * @property parseFunctions
227      * @type Object
228      */
229     parseFunctions: {
230         "MS": function(input, strict) {
231             // note: the timezone offset is ignored since the MS Ajax server sends
232             // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
233             var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
234             var r = (input || '').match(re);
235             return r? new Date(((r[1] || '') + r[2]) * 1) : null;
236         }
237     },
238     parseRegexes: [],
239
240     /**
241      * <p>An object hash in which each property is a date formatting function. The property name is the
242      * format string which corresponds to the produced formatted date string.</p>
243      * <p>This object is automatically populated with date formatting functions as
244      * date formats are requested for Ext standard formatting strings.</p>
245      * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
246      * may be used as a format string to {@link #format}. Example:</p><pre><code>
247 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
248 </code></pre>
249      * <p>A formatting function should return a string representation of the passed Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
250      * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
251      * </ul></div></p>
252      * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
253      * parsing function must be placed into the {@link #parseFunctions} property.
254      * @property formatFunctions
255      * @type Object
256      */
257     formatFunctions: {
258         "MS": function() {
259             // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
260             return '\\/Date(' + this.getTime() + ')\\/';
261         }
262     },
263
264     y2kYear : 50,
265
266     /**
267      * Date interval constant
268      * @type String
269      */
270     MILLI : "ms",
271
272     /**
273      * Date interval constant
274      * @type String
275      */
276     SECOND : "s",
277
278     /**
279      * Date interval constant
280      * @type String
281      */
282     MINUTE : "mi",
283
284     /** Date interval constant
285      * @type String
286      */
287     HOUR : "h",
288
289     /**
290      * Date interval constant
291      * @type String
292      */
293     DAY : "d",
294
295     /**
296      * Date interval constant
297      * @type String
298      */
299     MONTH : "mo",
300
301     /**
302      * Date interval constant
303      * @type String
304      */
305     YEAR : "y",
306
307     /**
308      * <p>An object hash containing default date values used during date parsing.</p>
309      * <p>The following properties are available:<div class="mdetail-params"><ul>
310      * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
311      * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
312      * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
313      * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
314      * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
315      * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
316      * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
317      * </ul></div></p>
318      * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
319      * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
320      * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
321      * It is the responsiblity of the developer to account for this.</b></p>
322      * Example Usage:
323      * <pre><code>
324 // set default day value to the first day of the month
325 Ext.Date.defaults.d = 1;
326
327 // parse a February date string containing only year and month values.
328 // setting the default day value to 1 prevents weird date rollover issues
329 // when attempting to parse the following date string on, for example, March 31st 2009.
330 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
331 </code></pre>
332      * @property defaults
333      * @type Object
334      */
335     defaults: {},
336
337     /**
338      * @property {String[]} dayNames
339      * An array of textual day names.
340      * Override these values for international dates.
341      * Example:
342      * <pre><code>
343 Ext.Date.dayNames = [
344     'SundayInYourLang',
345     'MondayInYourLang',
346     ...
347 ];
348 </code></pre>
349      */
350     dayNames : [
351         "Sunday",
352         "Monday",
353         "Tuesday",
354         "Wednesday",
355         "Thursday",
356         "Friday",
357         "Saturday"
358     ],
359
360     /**
361      * @property {String[]} monthNames
362      * An array of textual month names.
363      * Override these values for international dates.
364      * Example:
365      * <pre><code>
366 Ext.Date.monthNames = [
367     'JanInYourLang',
368     'FebInYourLang',
369     ...
370 ];
371 </code></pre>
372      */
373     monthNames : [
374         "January",
375         "February",
376         "March",
377         "April",
378         "May",
379         "June",
380         "July",
381         "August",
382         "September",
383         "October",
384         "November",
385         "December"
386     ],
387
388     /**
389      * @property {Object} monthNumbers
390      * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
391      * Override these values for international dates.
392      * Example:
393      * <pre><code>
394 Ext.Date.monthNumbers = {
395     'ShortJanNameInYourLang':0,
396     'ShortFebNameInYourLang':1,
397     ...
398 };
399 </code></pre>
400      */
401     monthNumbers : {
402         Jan:0,
403         Feb:1,
404         Mar:2,
405         Apr:3,
406         May:4,
407         Jun:5,
408         Jul:6,
409         Aug:7,
410         Sep:8,
411         Oct:9,
412         Nov:10,
413         Dec:11
414     },
415     /**
416      * @property {String} defaultFormat
417      * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
418      * and {@link Ext.util.Format#date} functions use.  See {@link Ext.Date} for details.</p>
419      * <p>This may be overridden in a locale file.</p>
420      */
421     defaultFormat : "m/d/Y",
422     /**
423      * Get the short month name for the given month number.
424      * Override this function for international dates.
425      * @param {Number} month A zero-based javascript month number.
426      * @return {String} The short month name.
427      */
428     getShortMonthName : function(month) {
429         return utilDate.monthNames[month].substring(0, 3);
430     },
431
432     /**
433      * Get the short day name for the given day number.
434      * Override this function for international dates.
435      * @param {Number} day A zero-based javascript day number.
436      * @return {String} The short day name.
437      */
438     getShortDayName : function(day) {
439         return utilDate.dayNames[day].substring(0, 3);
440     },
441
442     /**
443      * Get the zero-based javascript month number for the given short/full month name.
444      * Override this function for international dates.
445      * @param {String} name The short/full month name.
446      * @return {Number} The zero-based javascript month number.
447      */
448     getMonthNumber : function(name) {
449         // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
450         return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
451     },
452
453     /**
454      * Checks if the specified format contains hour information
455      * @param {String} format The format to check
456      * @return {Boolean} True if the format contains hour information
457      * @method
458      */
459     formatContainsHourInfo : (function(){
460         var stripEscapeRe = /(\\.)/g,
461             hourInfoRe = /([gGhHisucUOPZ]|MS)/;
462         return function(format){
463             return hourInfoRe.test(format.replace(stripEscapeRe, ''));
464         };
465     })(),
466
467     /**
468      * Checks if the specified format contains information about
469      * anything other than the time.
470      * @param {String} format The format to check
471      * @return {Boolean} True if the format contains information about
472      * date/day information.
473      * @method
474      */
475     formatContainsDateInfo : (function(){
476         var stripEscapeRe = /(\\.)/g,
477             dateInfoRe = /([djzmnYycU]|MS)/;
478
479         return function(format){
480             return dateInfoRe.test(format.replace(stripEscapeRe, ''));
481         };
482     })(),
483
484     /**
485      * The base format-code to formatting-function hashmap used by the {@link #format} method.
486      * Formatting functions are strings (or functions which return strings) which
487      * will return the appropriate value when evaluated in the context of the Date object
488      * from which the {@link #format} method is called.
489      * Add to / override these mappings for custom date formatting.
490      * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
491      * Example:
492      * <pre><code>
493 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
494 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
495 </code></pre>
496      * @type Object
497      */
498     formatCodes : {
499         d: "Ext.String.leftPad(this.getDate(), 2, '0')",
500         D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
501         j: "this.getDate()",
502         l: "Ext.Date.dayNames[this.getDay()]",
503         N: "(this.getDay() ? this.getDay() : 7)",
504         S: "Ext.Date.getSuffix(this)",
505         w: "this.getDay()",
506         z: "Ext.Date.getDayOfYear(this)",
507         W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
508         F: "Ext.Date.monthNames[this.getMonth()]",
509         m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
510         M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
511         n: "(this.getMonth() + 1)",
512         t: "Ext.Date.getDaysInMonth(this)",
513         L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
514         o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
515         Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
516         y: "('' + this.getFullYear()).substring(2, 4)",
517         a: "(this.getHours() < 12 ? 'am' : 'pm')",
518         A: "(this.getHours() < 12 ? 'AM' : 'PM')",
519         g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
520         G: "this.getHours()",
521         h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
522         H: "Ext.String.leftPad(this.getHours(), 2, '0')",
523         i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
524         s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
525         u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
526         O: "Ext.Date.getGMTOffset(this)",
527         P: "Ext.Date.getGMTOffset(this, true)",
528         T: "Ext.Date.getTimezone(this)",
529         Z: "(this.getTimezoneOffset() * -60)",
530
531         c: function() { // ISO-8601 -- GMT format
532             for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
533                 var e = c.charAt(i);
534                 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
535             }
536             return code.join(" + ");
537         },
538         /*
539         c: function() { // ISO-8601 -- UTC format
540             return [
541               "this.getUTCFullYear()", "'-'",
542               "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
543               "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
544               "'T'",
545               "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
546               "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
547               "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
548               "'Z'"
549             ].join(" + ");
550         },
551         */
552
553         U: "Math.round(this.getTime() / 1000)"
554     },
555
556     /**
557      * Checks if the passed Date parameters will cause a javascript Date "rollover".
558      * @param {Number} year 4-digit year
559      * @param {Number} month 1-based month-of-year
560      * @param {Number} day Day of month
561      * @param {Number} hour (optional) Hour
562      * @param {Number} minute (optional) Minute
563      * @param {Number} second (optional) Second
564      * @param {Number} millisecond (optional) Millisecond
565      * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
566      */
567     isValid : function(y, m, d, h, i, s, ms) {
568         // setup defaults
569         h = h || 0;
570         i = i || 0;
571         s = s || 0;
572         ms = ms || 0;
573
574         // Special handling for year < 100
575         var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
576
577         return y == dt.getFullYear() &&
578             m == dt.getMonth() + 1 &&
579             d == dt.getDate() &&
580             h == dt.getHours() &&
581             i == dt.getMinutes() &&
582             s == dt.getSeconds() &&
583             ms == dt.getMilliseconds();
584     },
585
586     /**
587      * Parses the passed string using the specified date format.
588      * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
589      * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
590      * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
591      * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
592      * Keep in mind that the input date string must precisely match the specified format string
593      * in order for the parse operation to be successful (failed parse operations return a null value).
594      * <p>Example:</p><pre><code>
595 //dt = Fri May 25 2007 (current date)
596 var dt = new Date();
597
598 //dt = Thu May 25 2006 (today&#39;s month/day in 2006)
599 dt = Ext.Date.parse("2006", "Y");
600
601 //dt = Sun Jan 15 2006 (all date parts specified)
602 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
603
604 //dt = Sun Jan 15 2006 15:20:01
605 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
606
607 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
608 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
609 </code></pre>
610      * @param {String} input The raw date string.
611      * @param {String} format The expected date string format.
612      * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
613                         (defaults to false). Invalid date strings will return null when parsed.
614      * @return {Date} The parsed Date.
615      */
616     parse : function(input, format, strict) {
617         var p = utilDate.parseFunctions;
618         if (p[format] == null) {
619             utilDate.createParser(format);
620         }
621         return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
622     },
623
624     // Backwards compat
625     parseDate: function(input, format, strict){
626         return utilDate.parse(input, format, strict);
627     },
628
629
630     // private
631     getFormatCode : function(character) {
632         var f = utilDate.formatCodes[character];
633
634         if (f) {
635           f = typeof f == 'function'? f() : f;
636           utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
637         }
638
639         // note: unknown characters are treated as literals
640         return f || ("'" + Ext.String.escape(character) + "'");
641     },
642
643     // private
644     createFormat : function(format) {
645         var code = [],
646             special = false,
647             ch = '';
648
649         for (var i = 0; i < format.length; ++i) {
650             ch = format.charAt(i);
651             if (!special && ch == "\\") {
652                 special = true;
653             } else if (special) {
654                 special = false;
655                 code.push("'" + Ext.String.escape(ch) + "'");
656             } else {
657                 code.push(utilDate.getFormatCode(ch));
658             }
659         }
660         utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
661     },
662
663     // private
664     createParser : (function() {
665         var code = [
666             "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
667                 "def = Ext.Date.defaults,",
668                 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
669
670             "if(results){",
671                 "{1}",
672
673                 "if(u != null){", // i.e. unix time is defined
674                     "v = new Date(u * 1000);", // give top priority to UNIX time
675                 "}else{",
676                     // create Date object representing midnight of the current day;
677                     // this will provide us with our date defaults
678                     // (note: clearTime() handles Daylight Saving Time automatically)
679                     "dt = Ext.Date.clearTime(new Date);",
680
681                     // date calculations (note: these calculations create a dependency on Ext.Number.from())
682                     "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
683                     "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
684                     "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
685
686                     // time calculations (note: these calculations create a dependency on Ext.Number.from())
687                     "h  = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
688                     "i  = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
689                     "s  = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
690                     "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
691
692                     "if(z >= 0 && y >= 0){",
693                         // both the year and zero-based day of year are defined and >= 0.
694                         // these 2 values alone provide sufficient info to create a full date object
695
696                         // create Date object representing January 1st for the given year
697                         // handle years < 100 appropriately
698                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
699
700                         // then add day of year, checking for Date "rollover" if necessary
701                         "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
702                     "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
703                         "v = null;", // invalid date, so return null
704                     "}else{",
705                         // plain old Date object
706                         // handle years < 100 properly
707                         "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
708                     "}",
709                 "}",
710             "}",
711
712             "if(v){",
713                 // favour UTC offset over GMT offset
714                 "if(zz != null){",
715                     // reset to UTC, then add offset
716                     "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
717                 "}else if(o){",
718                     // reset to GMT, then add offset
719                     "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
720                 "}",
721             "}",
722
723             "return v;"
724         ].join('\n');
725
726         return function(format) {
727             var regexNum = utilDate.parseRegexes.length,
728                 currentGroup = 1,
729                 calc = [],
730                 regex = [],
731                 special = false,
732                 ch = "";
733
734             for (var i = 0; i < format.length; ++i) {
735                 ch = format.charAt(i);
736                 if (!special && ch == "\\") {
737                     special = true;
738                 } else if (special) {
739                     special = false;
740                     regex.push(Ext.String.escape(ch));
741                 } else {
742                     var obj = utilDate.formatCodeToRegex(ch, currentGroup);
743                     currentGroup += obj.g;
744                     regex.push(obj.s);
745                     if (obj.g && obj.c) {
746                         calc.push(obj.c);
747                     }
748                 }
749             }
750
751             utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
752             utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
753         };
754     })(),
755
756     // private
757     parseCodes : {
758         /*
759          * Notes:
760          * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
761          * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
762          * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
763          */
764         d: {
765             g:1,
766             c:"d = parseInt(results[{0}], 10);\n",
767             s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
768         },
769         j: {
770             g:1,
771             c:"d = parseInt(results[{0}], 10);\n",
772             s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
773         },
774         D: function() {
775             for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
776             return {
777                 g:0,
778                 c:null,
779                 s:"(?:" + a.join("|") +")"
780             };
781         },
782         l: function() {
783             return {
784                 g:0,
785                 c:null,
786                 s:"(?:" + utilDate.dayNames.join("|") + ")"
787             };
788         },
789         N: {
790             g:0,
791             c:null,
792             s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
793         },
794         S: {
795             g:0,
796             c:null,
797             s:"(?:st|nd|rd|th)"
798         },
799         w: {
800             g:0,
801             c:null,
802             s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
803         },
804         z: {
805             g:1,
806             c:"z = parseInt(results[{0}], 10);\n",
807             s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
808         },
809         W: {
810             g:0,
811             c:null,
812             s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
813         },
814         F: function() {
815             return {
816                 g:1,
817                 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
818                 s:"(" + utilDate.monthNames.join("|") + ")"
819             };
820         },
821         M: function() {
822             for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
823             return Ext.applyIf({
824                 s:"(" + a.join("|") + ")"
825             }, utilDate.formatCodeToRegex("F"));
826         },
827         m: {
828             g:1,
829             c:"m = parseInt(results[{0}], 10) - 1;\n",
830             s:"(\\d{2})" // month number with leading zeros (01 - 12)
831         },
832         n: {
833             g:1,
834             c:"m = parseInt(results[{0}], 10) - 1;\n",
835             s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
836         },
837         t: {
838             g:0,
839             c:null,
840             s:"(?:\\d{2})" // no. of days in the month (28 - 31)
841         },
842         L: {
843             g:0,
844             c:null,
845             s:"(?:1|0)"
846         },
847         o: function() {
848             return utilDate.formatCodeToRegex("Y");
849         },
850         Y: {
851             g:1,
852             c:"y = parseInt(results[{0}], 10);\n",
853             s:"(\\d{4})" // 4-digit year
854         },
855         y: {
856             g:1,
857             c:"var ty = parseInt(results[{0}], 10);\n"
858                 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
859             s:"(\\d{1,2})"
860         },
861         /*
862          * In the am/pm parsing routines, we allow both upper and lower case
863          * even though it doesn't exactly match the spec. It gives much more flexibility
864          * in being able to specify case insensitive regexes.
865          */
866         a: {
867             g:1,
868             c:"if (/(am)/i.test(results[{0}])) {\n"
869                 + "if (!h || h == 12) { h = 0; }\n"
870                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
871             s:"(am|pm|AM|PM)"
872         },
873         A: {
874             g:1,
875             c:"if (/(am)/i.test(results[{0}])) {\n"
876                 + "if (!h || h == 12) { h = 0; }\n"
877                 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
878             s:"(AM|PM|am|pm)"
879         },
880         g: function() {
881             return utilDate.formatCodeToRegex("G");
882         },
883         G: {
884             g:1,
885             c:"h = parseInt(results[{0}], 10);\n",
886             s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
887         },
888         h: function() {
889             return utilDate.formatCodeToRegex("H");
890         },
891         H: {
892             g:1,
893             c:"h = parseInt(results[{0}], 10);\n",
894             s:"(\\d{2})" //  24-hr format of an hour with leading zeroes (00 - 23)
895         },
896         i: {
897             g:1,
898             c:"i = parseInt(results[{0}], 10);\n",
899             s:"(\\d{2})" // minutes with leading zeros (00 - 59)
900         },
901         s: {
902             g:1,
903             c:"s = parseInt(results[{0}], 10);\n",
904             s:"(\\d{2})" // seconds with leading zeros (00 - 59)
905         },
906         u: {
907             g:1,
908             c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
909             s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
910         },
911         O: {
912             g:1,
913             c:[
914                 "o = results[{0}];",
915                 "var sn = o.substring(0,1),", // get + / - sign
916                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
917                     "mn = o.substring(3,5) % 60;", // get minutes
918                 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
919             ].join("\n"),
920             s: "([+\-]\\d{4})" // GMT offset in hrs and mins
921         },
922         P: {
923             g:1,
924             c:[
925                 "o = results[{0}];",
926                 "var sn = o.substring(0,1),", // get + / - sign
927                     "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
928                     "mn = o.substring(4,6) % 60;", // get minutes
929                 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
930             ].join("\n"),
931             s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
932         },
933         T: {
934             g:0,
935             c:null,
936             s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
937         },
938         Z: {
939             g:1,
940             c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
941                   + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
942             s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
943         },
944         c: function() {
945             var calc = [],
946                 arr = [
947                     utilDate.formatCodeToRegex("Y", 1), // year
948                     utilDate.formatCodeToRegex("m", 2), // month
949                     utilDate.formatCodeToRegex("d", 3), // day
950                     utilDate.formatCodeToRegex("h", 4), // hour
951                     utilDate.formatCodeToRegex("i", 5), // minute
952                     utilDate.formatCodeToRegex("s", 6), // second
953                     {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
954                     {c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
955                         "if(results[8]) {", // timezone specified
956                             "if(results[8] == 'Z'){",
957                                 "zz = 0;", // UTC
958                             "}else if (results[8].indexOf(':') > -1){",
959                                 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
960                             "}else{",
961                                 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
962                             "}",
963                         "}"
964                     ].join('\n')}
965                 ];
966
967             for (var i = 0, l = arr.length; i < l; ++i) {
968                 calc.push(arr[i].c);
969             }
970
971             return {
972                 g:1,
973                 c:calc.join(""),
974                 s:[
975                     arr[0].s, // year (required)
976                     "(?:", "-", arr[1].s, // month (optional)
977                         "(?:", "-", arr[2].s, // day (optional)
978                             "(?:",
979                                 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
980                                 arr[3].s, ":", arr[4].s,  // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
981                                 "(?::", arr[5].s, ")?", // seconds (optional)
982                                 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
983                                 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
984                             ")?",
985                         ")?",
986                     ")?"
987                 ].join("")
988             };
989         },
990         U: {
991             g:1,
992             c:"u = parseInt(results[{0}], 10);\n",
993             s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
994         }
995     },
996
997     //Old Ext.Date prototype methods.
998     // private
999     dateFormat: function(date, format) {
1000         return utilDate.format(date, format);
1001     },
1002
1003     /**
1004      * Formats a date given the supplied format string.
1005      * @param {Date} date The date to format
1006      * @param {String} format The format string
1007      * @return {String} The formatted date
1008      */
1009     format: function(date, format) {
1010         if (utilDate.formatFunctions[format] == null) {
1011             utilDate.createFormat(format);
1012         }
1013         var result = utilDate.formatFunctions[format].call(date);
1014         return result + '';
1015     },
1016
1017     /**
1018      * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
1019      *
1020      * Note: The date string returned by the javascript Date object's toString() method varies
1021      * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
1022      * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
1023      * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
1024      * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
1025      * from the GMT offset portion of the date string.
1026      * @param {Date} date The date
1027      * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
1028      */
1029     getTimezone : function(date) {
1030         // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
1031         //
1032         // Opera  : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
1033         // Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
1034         // FF     : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
1035         // IE     : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
1036         // IE     : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
1037         //
1038         // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
1039         // step 1: (?:\((.*)\) -- find timezone in parentheses
1040         // step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
1041         // step 3: remove all non uppercase characters found in step 1 and 2
1042         return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
1043     },
1044
1045     /**
1046      * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
1047      * @param {Date} date The date
1048      * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
1049      * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
1050      */
1051     getGMTOffset : function(date, colon) {
1052         var offset = date.getTimezoneOffset();
1053         return (offset > 0 ? "-" : "+")
1054             + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
1055             + (colon ? ":" : "")
1056             + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
1057     },
1058
1059     /**
1060      * Get the numeric day number of the year, adjusted for leap year.
1061      * @param {Date} date The date
1062      * @return {Number} 0 to 364 (365 in leap years).
1063      */
1064     getDayOfYear: function(date) {
1065         var num = 0,
1066             d = Ext.Date.clone(date),
1067             m = date.getMonth(),
1068             i;
1069
1070         for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
1071             num += utilDate.getDaysInMonth(d);
1072         }
1073         return num + date.getDate() - 1;
1074     },
1075
1076     /**
1077      * Get the numeric ISO-8601 week number of the year.
1078      * (equivalent to the format specifier 'W', but without a leading zero).
1079      * @param {Date} date The date
1080      * @return {Number} 1 to 53
1081      * @method
1082      */
1083     getWeekOfYear : (function() {
1084         // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
1085         var ms1d = 864e5, // milliseconds in a day
1086             ms7d = 7 * ms1d; // milliseconds in a week
1087
1088         return function(date) { // return a closure so constants get calculated only once
1089             var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
1090                 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
1091                 Wyr = new Date(AWN * ms7d).getUTCFullYear();
1092
1093             return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
1094         };
1095     })(),
1096
1097     /**
1098      * Checks if the current date falls within a leap year.
1099      * @param {Date} date The date
1100      * @return {Boolean} True if the current date falls within a leap year, false otherwise.
1101      */
1102     isLeapYear : function(date) {
1103         var year = date.getFullYear();
1104         return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
1105     },
1106
1107     /**
1108      * Get the first day of the current month, adjusted for leap year.  The returned value
1109      * is the numeric day index within the week (0-6) which can be used in conjunction with
1110      * the {@link #monthNames} array to retrieve the textual day name.
1111      * Example:
1112      * <pre><code>
1113 var dt = new Date('1/10/2007'),
1114     firstDay = Ext.Date.getFirstDayOfMonth(dt);
1115 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
1116      * </code></pre>
1117      * @param {Date} date The date
1118      * @return {Number} The day number (0-6).
1119      */
1120     getFirstDayOfMonth : function(date) {
1121         var day = (date.getDay() - (date.getDate() - 1)) % 7;
1122         return (day < 0) ? (day + 7) : day;
1123     },
1124
1125     /**
1126      * Get the last day of the current month, adjusted for leap year.  The returned value
1127      * is the numeric day index within the week (0-6) which can be used in conjunction with
1128      * the {@link #monthNames} array to retrieve the textual day name.
1129      * Example:
1130      * <pre><code>
1131 var dt = new Date('1/10/2007'),
1132     lastDay = Ext.Date.getLastDayOfMonth(dt);
1133 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
1134      * </code></pre>
1135      * @param {Date} date The date
1136      * @return {Number} The day number (0-6).
1137      */
1138     getLastDayOfMonth : function(date) {
1139         return utilDate.getLastDateOfMonth(date).getDay();
1140     },
1141
1142
1143     /**
1144      * Get the date of the first day of the month in which this date resides.
1145      * @param {Date} date The date
1146      * @return {Date}
1147      */
1148     getFirstDateOfMonth : function(date) {
1149         return new Date(date.getFullYear(), date.getMonth(), 1);
1150     },
1151
1152     /**
1153      * Get the date of the last day of the month in which this date resides.
1154      * @param {Date} date The date
1155      * @return {Date}
1156      */
1157     getLastDateOfMonth : function(date) {
1158         return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
1159     },
1160
1161     /**
1162      * Get the number of days in the current month, adjusted for leap year.
1163      * @param {Date} date The date
1164      * @return {Number} The number of days in the month.
1165      * @method
1166      */
1167     getDaysInMonth: (function() {
1168         var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1169
1170         return function(date) { // return a closure for efficiency
1171             var m = date.getMonth();
1172
1173             return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
1174         };
1175     })(),
1176
1177     /**
1178      * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
1179      * @param {Date} date The date
1180      * @return {String} 'st, 'nd', 'rd' or 'th'.
1181      */
1182     getSuffix : function(date) {
1183         switch (date.getDate()) {
1184             case 1:
1185             case 21:
1186             case 31:
1187                 return "st";
1188             case 2:
1189             case 22:
1190                 return "nd";
1191             case 3:
1192             case 23:
1193                 return "rd";
1194             default:
1195                 return "th";
1196         }
1197     },
1198
1199     /**
1200      * Creates and returns a new Date instance with the exact same date value as the called instance.
1201      * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
1202      * variable will also be changed.  When the intention is to create a new variable that will not
1203      * modify the original instance, you should create a clone.
1204      *
1205      * Example of correctly cloning a date:
1206      * <pre><code>
1207 //wrong way:
1208 var orig = new Date('10/1/2006');
1209 var copy = orig;
1210 copy.setDate(5);
1211 console.log(orig);  //returns 'Thu Oct 05 2006'!
1212
1213 //correct way:
1214 var orig = new Date('10/1/2006'),
1215     copy = Ext.Date.clone(orig);
1216 copy.setDate(5);
1217 console.log(orig);  //returns 'Thu Oct 01 2006'
1218      * </code></pre>
1219      * @param {Date} date The date
1220      * @return {Date} The new Date instance.
1221      */
1222     clone : function(date) {
1223         return new Date(date.getTime());
1224     },
1225
1226     /**
1227      * Checks if the current date is affected by Daylight Saving Time (DST).
1228      * @param {Date} date The date
1229      * @return {Boolean} True if the current date is affected by DST.
1230      */
1231     isDST : function(date) {
1232         // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
1233         // courtesy of @geoffrey.mcgill
1234         return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
1235     },
1236
1237     /**
1238      * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
1239      * automatically adjusting for Daylight Saving Time (DST) where applicable.
1240      * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
1241      * @param {Date} date The date
1242      * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
1243      * @return {Date} this or the clone.
1244      */
1245     clearTime : function(date, clone) {
1246         if (clone) {
1247             return Ext.Date.clearTime(Ext.Date.clone(date));
1248         }
1249
1250         // get current date before clearing time
1251         var d = date.getDate();
1252
1253         // clear time
1254         date.setHours(0);
1255         date.setMinutes(0);
1256         date.setSeconds(0);
1257         date.setMilliseconds(0);
1258
1259         if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
1260             // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
1261             // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
1262
1263             // increment hour until cloned date == current date
1264             for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
1265
1266             date.setDate(d);
1267             date.setHours(c.getHours());
1268         }
1269
1270         return date;
1271     },
1272
1273     /**
1274      * Provides a convenient method for performing basic date arithmetic. This method
1275      * does not modify the Date instance being called - it creates and returns
1276      * a new Date instance containing the resulting date value.
1277      *
1278      * Examples:
1279      * <pre><code>
1280 // Basic usage:
1281 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
1282 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
1283
1284 // Negative values will be subtracted:
1285 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
1286 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
1287
1288      * </code></pre>
1289      *
1290      * @param {Date} date The date to modify
1291      * @param {String} interval A valid date interval enum value.
1292      * @param {Number} value The amount to add to the current date.
1293      * @return {Date} The new Date instance.
1294      */
1295     add : function(date, interval, value) {
1296         var d = Ext.Date.clone(date),
1297             Date = Ext.Date;
1298         if (!interval || value === 0) return d;
1299
1300         switch(interval.toLowerCase()) {
1301             case Ext.Date.MILLI:
1302                 d.setMilliseconds(d.getMilliseconds() + value);
1303                 break;
1304             case Ext.Date.SECOND:
1305                 d.setSeconds(d.getSeconds() + value);
1306                 break;
1307             case Ext.Date.MINUTE:
1308                 d.setMinutes(d.getMinutes() + value);
1309                 break;
1310             case Ext.Date.HOUR:
1311                 d.setHours(d.getHours() + value);
1312                 break;
1313             case Ext.Date.DAY:
1314                 d.setDate(d.getDate() + value);
1315                 break;
1316             case Ext.Date.MONTH:
1317                 var day = date.getDate();
1318                 if (day > 28) {
1319                     day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
1320                 }
1321                 d.setDate(day);
1322                 d.setMonth(date.getMonth() + value);
1323                 break;
1324             case Ext.Date.YEAR:
1325                 d.setFullYear(date.getFullYear() + value);
1326                 break;
1327         }
1328         return d;
1329     },
1330
1331     /**
1332      * Checks if a date falls on or between the given start and end dates.
1333      * @param {Date} date The date to check
1334      * @param {Date} start Start date
1335      * @param {Date} end End date
1336      * @return {Boolean} true if this date falls on or between the given start and end dates.
1337      */
1338     between : function(date, start, end) {
1339         var t = date.getTime();
1340         return start.getTime() <= t && t <= end.getTime();
1341     },
1342
1343     //Maintains compatibility with old static and prototype window.Date methods.
1344     compat: function() {
1345         var nativeDate = window.Date,
1346             p, u,
1347             statics = ['useStrict', 'formatCodeToRegex', 'parseFunctions', 'parseRegexes', 'formatFunctions', 'y2kYear', 'MILLI', 'SECOND', 'MINUTE', 'HOUR', 'DAY', 'MONTH', 'YEAR', 'defaults', 'dayNames', 'monthNames', 'monthNumbers', 'getShortMonthName', 'getShortDayName', 'getMonthNumber', 'formatCodes', 'isValid', 'parseDate', 'getFormatCode', 'createFormat', 'createParser', 'parseCodes'],
1348             proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
1349
1350         //Append statics
1351         Ext.Array.forEach(statics, function(s) {
1352             nativeDate[s] = utilDate[s];
1353         });
1354
1355         //Append to prototype
1356         Ext.Array.forEach(proto, function(s) {
1357             nativeDate.prototype[s] = function() {
1358                 var args = Array.prototype.slice.call(arguments);
1359                 args.unshift(this);
1360                 return utilDate[s].apply(utilDate, args);
1361             };
1362         });
1363     }
1364 };
1365
1366 var utilDate = Ext.Date;
1367
1368 })();
1369