/**
 * @class Date
 *
 * The date parsing and formatting syntax contains a subset of
 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
 * supported will provide results equivalent to their PHP versions.
 *
 * The following is a list of all currently supported formats:
 * <pre>
Format  Description                                                               Example returned values
------  -----------------------------------------------------------------------   -----------------------
  d     Day of the month, 2 digits with leading zeros                             01 to 31
  D     A short textual representation of the day of the week                     Mon to Sun
  j     Day of the month without leading zeros                                    1 to 31
  l     A full textual representation of the day of the week                      Sunday to Saturday
  N     ISO-8601 numeric representation of the day of the week                    1 (for Monday) through 7 (for Sunday)
  S     English ordinal suffix for the day of the month, 2 characters             st, nd, rd or th. Works well with j
  w     Numeric representation of the day of the week                             0 (for Sunday) to 6 (for Saturday)
  z     The day of the year (starting from 0)                                     0 to 364 (365 in leap years)
  W     ISO-8601 week number of year, weeks starting on Monday                    01 to 53
  F     A full textual representation of a month, such as January or March        January to December
  m     Numeric representation of a month, with leading zeros                     01 to 12
  M     A short textual representation of a month                                 Jan to Dec
  n     Numeric representation of a month, without leading zeros                  1 to 12
  t     Number of days in the given month                                         28 to 31
  L     Whether it's a leap year                                                  1 if it is a leap year, 0 otherwise.
  o     ISO-8601 year number (identical to (Y), but if the ISO week number (W)    Examples: 1998 or 2004
  belongs to the previous or next year, that year is used instead)
  Y     A full numeric representation of a year, 4 digits                         Examples: 1999 or 2003
  y     A two digit representation of a year                                      Examples: 99 or 03
  a     Lowercase Ante meridiem and Post meridiem                                 am or pm
  A     Uppercase Ante meridiem and Post meridiem                                 AM or PM
  g     12-hour format of an hour without leading zeros                           1 to 12
  G     24-hour format of an hour without leading zeros                           0 to 23
  h     12-hour format of an hour with leading zeros                              01 to 12
  H     24-hour format of an hour with leading zeros                              00 to 23
  i     Minutes, with leading zeros                                               00 to 59
  s     Seconds, with leading zeros                                               00 to 59
  u     Decimal fraction of a second                                              Examples:
        (minimum 1 digit, arbitrary number of digits allowed)                     001 (i.e. 0.001s) or
                                                                                  100 (i.e. 0.100s) or
                                                                                  999 (i.e. 0.999s) or
                                                                                  999876543210 (i.e. 0.999876543210s)
  O     Difference to Greenwich time (GMT) in hours and minutes                   Example: +1030
  P     Difference to Greenwich time (GMT) with colon between hours and minutes   Example: -08:00
  T     Timezone abbreviation of the machine running the code                     Examples: EST, MDT, PDT ...
  Z     Timezone offset in seconds (negative if west of UTC, positive if east)    -43200 to 50400
  c     ISO 8601 date
        Notes:                                                                    Examples:
        1) If unspecified, the month / day defaults to the current month / day,   1991 or
           the time defaults to midnight, while the timezone defaults to the      1992-10 or
           browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
           and minutes. The "T" delimiter, seconds, milliseconds and timezone     1994-08-19T16:20+01:00 or
           are optional.                                                          1995-07-18T17:21:28-02:00 or
        2) The decimal fraction of a second, if specified, must contain at        1996-06-17T18:22:29.98765+03:00 or
           least 1 digit (there is no limit to the maximum number                 1997-05-16T19:23:30,12345-0400 or
           of digits allowed), and may be delimited by either a '.' or a ','      1998-04-15T20:24:31.2468Z or
        Refer to the examples on the right for the various levels of              1999-03-14T20:24:32Z or
        date-time granularity which are supported, or see                         2000-02-13T21:25:33
        http://www.w3.org/TR/NOTE-datetime for more info.                         2001-01-12 22:26:34
  U     Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)                1193432466 or -2138434463
  M$    Microsoft AJAX serialized dates                                           \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
                                                                                  \/Date(1238606590509+0800)\/
</pre>
 *
 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
 * <pre><code>
// Sample date:
// 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'

var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
document.write(dt.format('Y-m-d'));                           // 2007-01-10
document.write(dt.format('F j, Y, g:i a'));                   // January 10, 2007, 3:05 pm
document.write(dt.format('l, \\t\\he jS \\of F Y h:i:s A'));  // Wednesday, the 10th of January 2007 03:05:01 PM
</code></pre>
 *
 * Here are some standard date/time patterns that you might find helpful.  They
 * are not part of the source of Date.js, but to use them you can simply copy this
 * block of code into any script that is included after Date.js and they will also become
 * globally available on the Date object.  Feel free to add or remove patterns as needed in your code.
 * <pre><code>
Date.patterns = {
    ISO8601Long:"Y-m-d H:i:s",
    ISO8601Short:"Y-m-d",
    ShortDate: "d/m/Y",
    LongDate: "l jS F Y",
    FullDateTime: "l jS F Y G:i:s",
    MonthDay: "jS F",
    ShortTime: "G:i",
    LongTime: "G:i:s",
    SortableDateTime: "Y-m-d\\TH:i:s",
    UniversalSortableDateTime: "Y-m-d H:i:sO",
    YearMonth: "F, Y"
};
</code></pre>
 *
 * Example usage:
 * <pre><code>
var dt = new Date();
document.write(dt.format(Date.patterns.ShortDate));
</code></pre>
 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
 * #core
 */
Ext.apply(Date.prototype, {
	/**
	 * Provides a convenient method for performing basic date arithmetic. This method
	 * does not modify the Date instance being called - it creates and returns
	 * a new Date instance containing the resulting date value.
	 *
	 * Examples:
	 * <pre><code>
		// Basic usage:
		var dt = new Date('10/29/2006').add(Date.DAY, 5);
		document.write(dt); //returns 'Fri Nov 03 2006 00:00:00'

		// Negative values will be subtracted:
		var dt2 = new Date('10/1/2006').add(Date.DAY, -5);
		document.write(dt2); //returns 'Tue Sep 26 2006 00:00:00'

		// You can even chain several calls together in one line:
		var dt3 = new Date('10/1/2006').add(Date.DAY, 5).add(Date.HOUR, 8).add(Date.MINUTE, -30);
		document.write(dt3); //returns 'Fri Oct 06 2006 07:30:00'
	 * </code></pre>
	 *
	 * Furthermore, changes to {@link Date#HOUR hours}, {@link Date#MINUTE minutes},
	 * {@link Date#SECOND seconds} and {@link Date#MILLI milliseconds} are treated more accurately
	 * regarding DST changes then the {@link Date#DAY days}, {@link Date#MONTH months} and {@link Date#YEAR years}
	 * changes. When changing the time the standard is applied, which means that if the DST kicks in at 2AM,
	 * and the time becomes 3AM. Doing new Date('Mar 25 2012 01:00').add(Date.HOUR, 1) will be 'Mar 25 2012 03:00'.
	 * However when changing the date, we will use the JS behavior, which means that
	 * new Date('Mar 24 2012 02:00').add(Date.DAY, 1) could become 'Mar 25 2012 01:00' as JS will not correctly
	 * move the time correctly passed the DST switch.
	 *
	 * @param {String} interval A valid date interval enum value.
	 * @param {Number} value The amount to add to the current date.
	 * @return {Date} The new Date instance.
	 */
	add : function(interval, value)
	{
		var d = this.clone();
		if (!interval || value === 0) {
			return d;
		}

		switch(interval.toLowerCase()) {
			// Changing the time is done more accuretely then
			// changing the date. This is because we have to work
			// around DST issues (which we don't care for when
			// changing the day). In JS, we have the following
			// scenario at the following date: Mar 25 2012.
			// At 2:00:00 the DST kicks in and the time will be
			//     Mar 25 2012 03:00:00
			// However, when using setMilliseconds, setSeconds,
			// setMinutes or setHours, JS decides to wrap back
			// to:
			// 	Mar 25 2012 01:00:00
			// How can this go wrong, take the following date:
			//      a = new Date('Mar 25 2012 01:45:00')
			// add 30 minutes to it
			//      a.setMinutes(a.getMinutes() + 30)
			// we expect the time to be 03:15:00 however JS
			// decides to fall back to 01:15:00.
			// To fix this correctly, we have to work using timestamps
			// as JS is able to correctly step over the DST switch.
			case Date.HOUR:
				// Convert value to minutes
				value *= 60;
				/* falls through */
			case Date.MINUTE:
				// Convert value to seconds
				value *= 60;
				/* falls through */
			case Date.SECOND:
				// Convert value to milliseconds
				value *= 1000;
				/* falls through */
			case Date.MILLI:
				d = new Date(d.getTime() + value);
				break;
			// Changing the date is done with less accuracy,
			// basically we don't care if we come at exactly
			// the same time as before. If the JS decides to
			// perform weird tricks, then so be it.
			case Date.DAY:
				d.setDate(this.getDate() + value);
				break;
			case Date.MONTH:
				var day = this.getDate();
				if (day > 28) {
					day = Math.min(day, this.getFirstDateOfMonth().add(Date.MONTH, value).getLastDateOfMonth().getDate());
				}
				d.setDate(day);
				d.setMonth(this.getMonth() + value);
				break;
			case Date.YEAR:
				d.setFullYear(this.getFullYear() + value);
				break;
		}
		return d;
	},

	/**
	 * This should be called if the Date() object represents a UTC date, and we want to obtain
	 * the time it represents in UTC shown as if it was the localtime. This implies that:
	 *
	 * 00:00 UTC (01:00 GMT+0100) will be converted to 00:00 GMT+0100 (23:00 UTC)
	 *
	 * @return {Date} The UTC date
	 */
	toUTC : function()
	{
		var utc = new Date(this.getTime() + (this.getTimezoneOffset() * 60000));

		// Obtain the DST difference which might have occured during conversion,
		// if there was a difference it must be applied to the utc date accordingly.
		utc.setMilliseconds(utc.getMilliseconds() + Date.getDSTDiff(utc, this));

		return utc;
	},

	/**
	 * This should be called if the Date() was obtained using {@link #toUTC}, and we want
	 * to convert the date back to the local representation.
	 *
	 * 00:00 GMT+0100 (23:00 UTC) with be converted to 00:00 UTC (01:00 GMT+0100)
	 *
	 * @return {Date} The local-time date
	 */
	fromUTC : function()
	{
		return new Date(this.getTime() - (this.getTimezoneOffset() * 60000));
	},

	/**
	 * Get the next given weekday starting from this date. If the current date is this weekday,
	 * then the current day will be returned.
	 * @param {Number} weekday The day in the week to skip to (0: Sunday -  6: Saturday). If
	 * not given, tomorrow will be returned.
	 * @return {Date} this or the clone
	 */
	getNextWeekDay : function(weekday)
	{
		var currentday = this.getDay();

		if (!Ext.isDefined(weekday)) {
			return this.add(Date.DAY, 1);
		} else if (weekday < currentday) {
			return this.add(Date.DAY, 7 - (currentday - weekday));
		} else {
			return this.add(Date.DAY, weekday - currentday);
		}
	},

	/**
	 * Get the previous given weekday starting from this date. If the current date is this weekday,
	 * then the current day will be returned.
	 * @param {Number} weekday The day in the week to skip to (0: Sunday -  6: Saturday). If
	 * not given, yesterday will be returned.
	 * @return {Date} this or the clone
	 */
	getPreviousWeekDay : function(weekday)
	{
		var currentday = this.getDay();

		if (!Ext.isDefined(weekday)) {
			return this.add(Date.DAY, -1);
		} else if (weekday <= currentday) {
			return this.add(Date.DAY, weekday - currentday);
		} else {
			return this.add(Date.DAY, -7 + (weekday - currentday));
		}
	},

	/**
	 * Get the next given working weekday starting from the date passed as argument or
	 * current date in case no argument was provided.
	 * @param {Date} currentDate (Optional) The date for which next working day should be returned
	 * @return {Date} Date fall on the next working day or false if no working days are defined
	 */
	getNextWorkWeekDay : function(currentDate)
	{
		currentDate = currentDate || new Date();
		var nextDate = currentDate.getNextWeekDay();
		var workingDaysList = container.getSettingsModel().get('zarafa/v1/main/working_days');
		if ( Ext.isEmpty(workingDaysList) ){
			return false;
		}

		if (workingDaysList.indexOf(nextDate.getDay()) !== -1 ) {
			return nextDate;
		} else {
			return this.getNextWorkWeekDay(nextDate);
		}
	},

	/**
	 * Attempts to clear all Second and millisecond time information from this Date by rounding the time down
	 * to the current minute.
	 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
	 * @return {Date} this or the clone.
	 */
	clearSeconds : function(clone)
	{
		if (clone) {
			return this.clone().clearSeconds();
		}

		// clear seconds
		this.setSeconds(0);
		this.setMilliseconds(0);

		return this;
	},

	/**
	 * Sets the time of the date to 12:00 am.
	 * @return {Date} this
	 */
	setToNoon : function()
	{
		this.clearTime().setHours(12);

		return this;
	},

	/**
	 * Checks if the given date is in the same week.
	 * @param {Date} date The date to compare
	 * @return {Boolean} true if the date is in the same week as this date, false otherwise.
	 */
	inSameWeekAs : function(date)
	{
		var clone = this.clone().setToNoon();
		clone = clone.add(Date.DAY, -1*clone.getDay());
		date.setToNoon();
		date = date.add(Date.DAY, -1*date.getDay());

		return clone.getTime() === date.getTime();
	},

	/**
	 * Checks if the given date is in the next week.
	 * @param {Date} date The date to compare
	 * @return {Boolean} true if the date is in the next week from this date, false otherwise.
	 */
	inNextWeek : function(date)
	{
		return this.add(Date.DAY, 7).inSameWeekAs(date);
	},

	/**
	 * Round the number of milliseconds/seconds/minutes or hours depending on the given value.
	 * Note that days, months and years cannot be rounded.
	 *
	 * Example of rounding:
	 *  9:12   round to 30	-> 9:00
	 *  9:17   round to 30	-> 9:30
	 *
	 * @param {String} field The field to round (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param {Number} roundTimeValue The number of minutes to round the time to.
	 * @return {Date} this date
	 */
	round : function(field, roundTimeValue)
	{
		// For each field we have a slightly different approach.
		// In all cases, if the field-value is already rounded,
		// then we don't need to do anything.
		// The calculation for the rounded value looks a bit weird, but
		// it is a bit more optimal then calling this.floor() or this.ceiling().
		// For seconds or higher units, we set all smaller units to 0,
		// to correctly round of the entire time.
		var value;
		switch (field) {
			case Date.MILLI:
				value = this.getMilliseconds();
				if (value % roundTimeValue > 0) {
					this.setMilliseconds(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				break;
			case Date.SECOND:
				value = this.getSeconds();
				if (value % roundTimeValue > 0) {
					this.setSeconds(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				this.setMilliseconds(0);
				break;
			case Date.MINUTE:
				value = this.getMinutes();
				if (value % roundTimeValue > 0) {
					this.setMinutes(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
			case Date.HOUR:
				value = this.getHours();
				if (value % roundTimeValue > 0) {
					this.setHours(value - (value % roundTimeValue) + (((value % roundTimeValue) >= (roundTimeValue / 2)) * roundTimeValue));
				}
				this.setMinutes(0);
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
		}

		return this;
	},

	/**
	 * Function to ceil timings according to the passed ceil milliseconds, seconds, minutes or hours.
	 * Note that days, months and years cannot be ceiled.
	 * @param {String} field The field to ceil (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param date ceilTimeValue date time which needs to be ceil (5/10/15/30/60 or so on)
	 * @return number Time number which is unixtimestamp of time.
	 *
	 * Example to understand what the code is actually suppose to do.
	 *	9:12	5min		ceil-9:15
	 *			10min		ceil-9.20
	 *			15min		ceil-9.15
	 *			30min		ceil-9.30
	 *			1hr/60min	ceil-10.00
	 *
	 */
	ceil : function(field, ceilTimeValue)
	{
		// For each field we have a slightly different approach.
		// In all cases, if the field-value is already rounded to the
		// given ceiling then we don't need to do anything.
		// For seconds or higher units, we set all smaller units to 0,
		// to correctly round of the entire time.
		var value;
		switch (field) {
			case Date.MILLI:
				value = this.getMilliseconds();
				if (value % ceilTimeValue > 0) {
					this.setMilliseconds(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				break;
			case Date.SECOND:
				value = this.getSeconds();
				if (value % ceilTimeValue > 0) {
					this.setSeconds(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				this.setMilliseconds(0);
				break;
			case Date.MINUTE:
				value = this.getMinutes();
				if (value % ceilTimeValue > 0) {
					this.setMinutes(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
			case Date.HOUR:
				value = this.getHours();
				if (value % ceilTimeValue > 0) {
					this.setHours(value - (value % ceilTimeValue) + ceilTimeValue);
				}
				this.setMinutes(0);
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
		}

		return this;
	},

	/**
	 * Function to floor timings according to the passed floor milliseconds, seconds, minutes or hours.
	 * Note that days, months and years cannot be floored.
	 * @param {String} field The field to floor (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param {Number} floorTimeValue date time which needs to be floor (5/10/15/30/60 or so on)
	 * @return {Date} This Date object
	 *
	 * Example to understand what the code is actually suppose to do.
	 *	9:12	5min		floor-9.10
	 *			10min		floor-9.10
	 *			15min		floor-9.00
	 *			30min		floor-9.00
	 *			1hr/60min	floor-9.00
	 *
	 */
	floor : function(field, floorTimeValue)
	{
		// For each field we have a slightly different approach.
		// In all cases, if the field-value is already rounded to the
		// given floor then we don't need to do anything.
		// For seconds or higher units, we set all smaller units to 0,
		// to correctly round of the entire time.
		var value;
		switch (field) {
			case Date.MILLI:
				value = this.getMilliseconds();
				if (value % floorTimeValue > 0) {
					this.setMilliseconds(value - (value % floorTimeValue));
				}
				break;
			case Date.SECOND:
				value = this.getSeconds();
				if (value % floorTimeValue > 0) {
					this.setSeconds(value - (value % floorTimeValue));
				}
				this.setMilliseconds(0);
				break;
			case Date.MINUTE:
				value = this.getMinutes();
				if (value % floorTimeValue > 0) {
					this.setMinutes(value - (value % floorTimeValue));
				}
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
			case Date.HOUR:
				value = this.getHours();
				if (value % floorTimeValue > 0) {
					this.setHours(value - (value % floorTimeValue));
				}
				this.setMinutes(0);
				this.setSeconds(0);
				this.setMilliseconds(0);
				break;
		}

		return this;
	},

	/**
	 * Get the week number of the month (1 to 5)
	*/
	getWeekOfMonth : function()
	{
		// get current week number in year
		var currentWeek = this.getWeekOfYear();

		// get month's first week number in year
		var monthStartDate = this.add(Date.DAY, -(this.getDate() - 1));
		var monthStartWeek = monthStartDate.getWeekOfYear();

		return currentWeek - monthStartWeek + 1;
	},

	/**
	 * Returns the date in a 'nicely' formatted string.
	 * @param {Boolean} includeTime The time will be added to the nicely
	 * formatted string when true, or omitted otherwise. Default is true.
	 *
	 * @return {String} The nicely formatted date string.
	 */
	getNiceFormat : function(includeTime)
	{
		includeTime = includeTime !== false;

		var d = this.clearTime(true);
		var now = new Date().clearTime();

		// Check for today. We'll only use time then.
		if ( d.getTime() === now.getTime() ){
			if ( includeTime ){
				return this.format(_('G:i'));
			} else {
				return _('Today');
			}
		}

		// Check for tomorrow.
		if ( d.add(Date.DAY, -1).getTime() === now.getTime() ){
			if ( includeTime ){
				return _('Tomorrow') + ' ' + this.format(_('G:i'));
			} else {
				return _('Tomorrow');
			}
		}

		// Check for future dates. We'll only show the date then.
		if ( d > now ){
			return this.format(_('d-m-Y'));
		}

		// Check for past week. We'll use day (name) + time then.
		if ( d.add(Date.DAY, 6) >= now ){
			if ( includeTime ){
				return this.format(_('D G:i'));
			} else {
				return this.format(_('D d-m'));
			}
		}

		// Check for two weeks ago. We'll use the day (name) + date (without year)
		if ( d.add(Date.DAY, 14) >= now ){
			return this.format(_('D d-m'));
		}

		// For anything older than two weeks ago, we'll show the date
		return this.format(_('d-m-Y'));
	}
});

Ext.apply(Date, {
	/**
	 * The number milliseconds per day
	 *
	 * @property
	 * @type Number
	 */
	dayInMillis : 24 * 60 * 60 * 1000,

	/**
	 * Calculate the DST difference between the 2 given dates.
	 * The first date serves as base, so when 'date' is not DST, but
	 * the second date is DST then a negative offset is returned. A
	 * positive value is returned when it is the other way around.
	 * When both dates have the same DST offset then this returns 0.
	 * @param {Date} date The base date from where the DST is calculated.
	 * @return {Number} milliseconds The DST difference in milliseconds
	 */
	getDSTDiff : function(a, b)
	{
		return (a.getTimezoneOffset() - b.getTimezoneOffset()) * 60 * 1000;
	},

	/**
	 * Calculates the difference between the 2 given dates.
	 * This applies the {@link #getDSTDiff} if needed to ensure that
	 * it always calculates the correct difference regardless of the DST changes
	 * which might have been made.
	 *
	 * In its absolute basic this function is equal to 'a.getTime() - b.getTime()'.
	 *
	 * @param {String} field The field which indicates the accuracy of the diff (e.g. {@link Date.MINUTE}, {@link Date.SECOND}, etc.)
	 * @param {Date} a The date object
	 * @param {Date} b The date object
	 * @return {Number} The difference between the 2 given dates
	 */
	diff : function(field, a, b)
	{
		var ta = a.getTime();
		var tb = b.getTime();
		var difference = ta-tb;

		switch (field) {
			case Date.DAY:
				// For calculating days we apply the same
				// inaccuracy as Date::add() we are not 100%
				// sure a day lasts 24 hour when DST is in play.
				difference -= Date.getDSTDiff(a, b);
				difference /= 24;
				/* falls through */
			case Date.HOUR:
				difference /= 60;
				/* falls through */
			case Date.MINUTE:
				difference /= 60;
				/* falls through */
			case Date.SECOND:
				difference /= 1000;
				/* falls through */
			case Date.MILLI:
				/* falls through */
			default:
				break;
		}

		return difference;
	},

	/**
	 * Function to getTimezone and all dst props
	 * This is a hard one. To create a recurring appointment, we need to save
	 * the start and end time of the appointment in local time. So if I'm in
	 * GMT+8, and I want the appointment at 9:00, I will simply save 9*60 = 540
	 * in the startDate. To make this usable for other users in other timezones,
	 * we have to tell the server in which timezone this is. The timezone is normally
	 * defined as a startdate and enddate for DST, the offset in minutes (so GMT+2 is 120)
	 * plus the extra DST offset when DST is in effect.
	 *
	 * We can't retrieve this directly from the browser, so we assume that the DST change
	 * will occure on a Sunday at 2:00 or 3:00 AM, and simply scan all the sundays in a
	 * year, looking for changes. We then have to guess which bit is DST and which is 'normal'
	 * by assuming that the DST offset will be less than the normal offset. From this we
	 * calculate the start and end dates of DST and the actuall offset in minutes.
	 *
	 * Unfortunately we can't detect the difference between 'the last week of october' and
	 * 'the fourth week of october'. This can cause subtle problems, so we assume 'last week'
	 * because this is most prevalent.
	 *
	 * Note that this doesn't work for many strange DST changes, see
	 * http://webexhibits.org/daylightsaving/g.html
	 * @static
	 */
	getTimezoneStruct : function()
	{
		var tzswitch = [],
			switchCount = 0,
			testDate = new Date(),
			tzStruct = {};

		// Clear the time
		testDate.setMonth(0);
		testDate.setDate(1);
		testDate.setMinutes(0);
		testDate.setSeconds(0);
		testDate.setMilliseconds(0);

		// Move to the next sunday
		testDate = testDate.getNextWeekDay(0);

		// Use 5:00 am because any change should have happened by then
		testDate.setHours(5);

		var lastoffset = testDate.getTimezoneOffset();

		for(var weekNr = 0; weekNr < 52; weekNr++) {
			if(testDate.getTimezoneOffset() != lastoffset) {
				// Found a switch
				tzswitch[switchCount] = {
					switchweek : testDate.getWeekOfMonth(),
					switchmonth : testDate.getMonth(),
					offset : testDate.getTimezoneOffset()
				};

				switchCount++;

				// We assume DST is only set or removed once per year
				if(switchCount == 2) {
					break;
				}

				lastoffset = testDate.getTimezoneOffset();
			}

			// advance one week
			testDate = testDate.add(Date.DAY, 7);
		}

		if(switchCount === 0) {
			// No DST in this timezone
			tzStruct = {
				timezone : testDate.getTimezoneOffset(),
				timezonedst : 0,
				dststartday : 0,
				dststartweek : 0,
				dststartmonth : 0,
				dststarthour : 0,
				dstendday : 0,
				dstendweek : 0,
				dstendmonth : 0,
				dstendhour : 0
			};

			return tzStruct;
		} else if(switchCount == 1) {
			// This should be impossible unless DST started somewhere in the year 2000
			// and ended more than a year later. This is an error.
			return tzStruct;
		} else if(switchCount == 2) {
			if(tzswitch[0].offset < tzswitch[1].offset) {
				// Northern hemisphere, eg DST is during Mar-Oct
				tzStruct = {
					timezone : tzswitch[1].offset,
					timezonedst : tzswitch[0].offset - tzswitch[1].offset,
					dststartday : 0, // assume sunday
					dststartweek : tzswitch[0].switchweek == 4 ? 5 : tzswitch[0].switchweek, // assume 'last' week if week = 4
					dststartmonth : tzswitch[0].switchmonth + 1, // javascript months are zero index based
					dststarthour : 2, // Start at 02:00 AM
					dstendday : 0,
					dstendweek : tzswitch[1].switchweek == 4 ? 5 : tzswitch[1].switchweek,
					dstendmonth : tzswitch[1].switchmonth + 1,
					dstendhour : 3
				};

				return tzStruct;

			} else {
				// Southern hemisphere, eg DST is during Oct-Mar
				tzStruct = {
					timezone : tzswitch[0].offset,
					timezonedst : tzswitch[1].offset - tzswitch[0].offset,
					dststartday : 0, // assume sunday
					dststartweek : tzswitch[1].switchweek == 4 ? 5 : tzswitch[1].switchweek, // assume 'last' week if week = 4
					dststartmonth : tzswitch[1].switchmonth + 1,
					dststarthour : 2, // Start at 02:00 AM
					dstendday : 0,
					dstendweek : tzswitch[0].switchweek == 4 ? 5 : tzswitch[0].switchweek,
					dstendmonth : tzswitch[0].switchmonth + 1,
					dstendhour : 3
				};

				return tzStruct;
			}
		} else {
			// Multi-DST timezone ? This is also an error.
			return tzStruct;
		}
	}
});