function city( name, tzOffset, DSTOffset, DSTStartMonth, DSTStartDay,
	DSTStartDOW, DSTStartHour, DSTStartMin, DSTEndMonth, DSTEndDay,
	DSTEndDOW, DSTEndHour, DSTEndMin )
{
	this.city = name;
	this.tzOffset = tzOffset;
	this.DSTOffset = DSTOffset;
	this.DSTStartMonth = DSTStartMonth;
	this.DSTStartDay = DSTStartDay;
	this.DSTStartDOW = DSTStartDOW;
	this.DSTStartHour = DSTStartHour;
	this.DSTStartMin = DSTStartMin;
	this.DSTEndMonth = DSTEndMonth;
	this.DSTEndDay = DSTEndDay;
	this.DSTEndDOW = DSTEndDOW;
	this.DSTEndHour = DSTEndHour;
	this.DSTEndMin = DSTEndMin;
	this.getTimezoneOffset = calculateTZOffsetWithDST;
}

function cityWithoutDST( name, tzOffset )
{
	this.city = name;
	this.tzOffset = tzOffset;
	this.getTimezoneOffset = calculateTZOffset;
}

var TZ = new Array(
	new city( "Adelaide", -570, -630, 9, 0, 1, 2, 0, 2, 0, -1, 2, 0 ),
	new city( "Anchorage", 540,  480, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new city( "Athens", -120, -180, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new cityWithoutDST( "Beijing", -480 ),
	new city( "Berlin", -60, -120, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new cityWithoutDST( "Bogota", 300 ),
	new cityWithoutDST( "Buenos Aires", 180 ),
	new city( "Cairo", -120, -180, 4, 5, 1, 2, 0, 8, 3, -1, 2, 0 ),
	new cityWithoutDST( "Caracas", 240 ),
	new city( "Chicago", 360, 300, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new city( "Denver", 420, 360, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new city( "Helsinki", -120, -180, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new cityWithoutDST( "Hong Kong", -480 ),
	new cityWithoutDST( "Honolulu", 600 ),
	new cityWithoutDST( "Jakarta", -420 ),
	new city( "Jerusalem", -120, -180, 3, 5, 1, 2, 0, 8, 5, 1, 2, 0 ),
	new cityWithoutDST( "Johannesburg", -120 ),
	new cityWithoutDST( "Karachi", -300 ),
	new city( "Kiev", -120, -180, 2, 0, -1, 0, 0, 9, 0, -1, 1, 0 ),
	new cityWithoutDST( "Lagos", -60 ),
	new cityWithoutDST( "Lima", 300 ),
	new city( "London", 0, -60, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new city( "Los Angeles", 480, 420, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new cityWithoutDST( "Luanda", -60 ),
	new city( "Madrid", -60, -120, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new cityWithoutDST( "Manila", -480 ),
	new cityWithoutDST( "Mecca", -180 ),
	new city( "Mexico", 360, 300, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new city( "Moscow", -180, -240, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new cityWithoutDST( "Mumbai", -330 ),
	new cityWithoutDST( "Nairobi", -180 ),
	new cityWithoutDST( "New Delhi", -330 ),
	new city( "New York", 300, 240, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new city( "Paris", -60, -120, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new cityWithoutDST( "Perth", -480 ),
	new cityWithoutDST( "Reykjavik", 0 ),
	new city( "Rio De Janeiro", 180, 120, 9, 0, 1, 1, 0, 1, 0, -1, 0, 0 ),
	new city( "Rome", -60, -120, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new city( "Santiago", 240, 180, 9, 0, 2, 0, 0, 2, 0, 2, 0, 0 ),
	new city( "Seattle", 480, 420, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new cityWithoutDST( "Seoul", -540 ),
	new cityWithoutDST( "Shanghai", -480 ),
	new cityWithoutDST( "Singapore", -480 ),
	new city( "Stockholm", -60, -120, 2, 0, -1, 2, 0, 9, 0, -1, 3, 0 ),
	new city( "Sydney", -600, -660, 9, 0, -1, 3, 0, 2, 0, -1, 2, 0 ),
	new city( "Tehran", -210, -270, 2, 0, 1, 2, 0, 8, 2, 4, 2, 0 ),
	new cityWithoutDST( "Tokyo", -540 ),
	new city( "Toronto", 300, 240, 3, 0, 1, 2, 0, 9, 0, -1, 2, 0 ),
	new city( "Wellington", -720, -780, 9, 0, 1, 2, 0, 2, 0, 3, 2, 0 )
	 );

function calculateTZOffset( currDate )
{
	return this.tzOffset;
}

function getDSTBasedDate( DSTYear, DSTMonth, DSTStartDay, DSTStartDOW, DSTStartHour, DSTStartMin )
{
	var year = DSTYear, month = DSTMonth, day, prevday, skipped;
	var checkingDate = new Date();

	checkingDate.setFullYear( year );
	checkingDate.setMonth( month );

	if( DSTStartDOW < 0 )
		// Day of the week before or after day of the month
		// abs(DSTStartDOW) specifies required day
		// startDay - specifies date before
		{
		if( DSTStartDay < 0 )
			{
			if( DSTStartDay == 0 )
				day = daysInMonth[ month ];
			else
                        	day = Math.abs( DSTStartDay );
			for( ; day>0; day-- )
				{
				checkingDate.setDate( day );
				if( checkingDate.getDay()+1 == Math.abs( DSTStartDOW ) )
					break;
				}
			}
		else
			{
			for( day=1; day<=DSTStartDay; day++ )
				{
				checkingDate.setDate( day );
				if( checkingDate.getDay()-1 == Math.abs( DSTStartDOW ) )
					break;
				}
			}
		}
	else
		{
		if( DSTStartDOW == 0 )
			// Day of the month; startDay - specifies required day
			{
			day = DSTStartDay;
			}
		else
			// DSTStartDOW > 0; 1 - Sunday, 2 - Monday, ...
			// Specified day of specified week of the month
			// startDay contain week number;
			{
			if( DSTStartDay >= 0 )
				// specified week is week from begining of the month
				{
				for( day=1; day<=DSTStartDay*7; day++ );
				for( ; day<=(DSTStartDay+1)*7; day++ )
					{
					checkingDate.setDate( day );
					if( checkingDate.getDay()+1 == DSTStartDOW )
						break;
					}
				}
			else
				// specified week is week from end of the month
				{
				day = daysInMonth[ month ];
				if( ((year % 4) != 0) && ((year % 100) != 0 ) && (month == 1) )
					day--;
				checkingDate.setDate( day );
				prevday = checkingDate.getDay();
				skipped = 0;
				while( skipped<Math.abs( DSTStartDay ) )
					{
					day--;
					checkingDate.setDate( day );
					if( checkingDate.getDay() > prevday )
						skipped++;
					prevday = checkingDate.getDay();
					}
				for( ; day>0; day-- )
					{
					checkingDate.setDate( day );
					if( checkingDate.getDay()+1 == DSTStartDOW )
						break; 
					}
				}
			}
		}

	return new Date( year, month, day, DSTStartHour, DSTStartMin );
}

function checkDates( date1, date2 )
{
	if( date1.getFullYear() != date2.getFullYear() )
		return date1.getFullYear() < date2.getFullYear() ? 1: -1;
	if( date1.getMonth() != date2.getMonth() )
		return date1.getMonth() < date2.getMonth() ? 1 : -1;
	if( date1.getDate() != date2.getDate() )
		return date1.getDate() < date2.getDate() ? 1 : -1;
	if( date1.getHours() != date2.getHours() )
		return date1.getHours() < date2.getHours() ? 1 : -1;
	if( date1.getMinutes() != date2.getMinutes() )
		return date1.getMinutes() < date2.getMinutes() ? 1 : -1;
	return 0;
}

function calculateTZOffsetWithDST( currDate )
{
	var isInDaylightSaving = false;
	var beginingDate = getDSTBasedDate( currDate.getFullYear(),
		this.DSTStartMonth, this.DSTStartDay, this.DSTStartDOW,
		this.DSTStartHour, this.DSTStartMin );
	var endDate = getDSTBasedDate( currDate.getFullYear(),
		this.DSTEndMonth, this.DSTEndDay, this.DSTEndDOW,
		this.DSTEndHour, this.DSTEndMin );

	if( beginingDate.getMonth() > endDate.getMonth() )
		endDate.setFullYear( beginingDate.getFullYear()+1 );

	isInDaylightSaving = ( checkDates( beginingDate, currDate ) >= 0 ) &&
		 	     ( checkDates( currDate, endDate ) >= 0 );

	if( isInDaylightSaving )
		return this.DSTOffset;
	else
		return this.tzOffset;
}