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