//*********************************************************************************************************
//                               Class AnalogClock
//*********************************************************************************************************
/*
 * Parameters:
 *   divId         die Id eines div Elements, in das die Uhr eingefuegt werden soll (* muss exisitieren! *)
 *   numstyle      1 | i | II | .    (* ein String! Bestimmt die Zahlen auf dem Ziffernblatt *)
 *   diameter      der Durchmesser der Uhr in Pixeln (* Uhr ist immer quadratisch *)
 *   hoursColor    Farbe des Stundenzeigers
 *   minutesColor  Farbe des Minutenzeigers
 *   secondsColor  Farbe des Sekundenzeigers
 *   clockZone     anzuzeigende zeitzone: "" heisst lokale Zeit, sonst relativ zu Greenwitch Meantime (also +1 fuer
 *                                        Mitteleuropa, -1 für 1 Stunde westlich von Greenwich...). Muss zwischen +12 und -12 liegen
 *   tick          true or false. Falls true springt der Minutenzeiger nach Ablauf einer Minute, falls false bewegt er sich
 *                                kontinuierlich.
 *   city          Name der Stadt, deren Zeit angezeigt wird (* leerer String "" falls keine Anzeige gewuenscht *)
 *   country       Name des Landes, in dem der Ort liegt, dessen Zeit angezeigt wird (* leerer String "" falls keine Anzeige gewuenscht *)
*/
function AnalogClock( divId, numstyle, diameter, hoursColor, minutesColor, secondsColor,
		      clockZone, tick, city, country ) {
  var container = document.getElementById( divId );
  if ( container == null ) {
    throw new Error( "Invalid id '" + divId + "'" );
  }
  if ( clockZone < -12 || clockZone > 12 ) {
    throw new Error( "Invalid clockZone '" + clockZone + "'" );
  }
  // only needed for testing and debugging purposes
  this.lastDstFlag = -1;
  this.debugMode = false;

  this.mainElem = container;

        var numStyles = new Array();
        numStyles["1"] = new Array("1","2","3","4","5","6","7","8","9","10","11","12");
        numStyles["i"] = new Array("i","ii","iii","iv","v","vi","vii","viii","ix","x","xi","xii");
        numStyles["I"] = new Array("I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII");
        numStyles["."] = new Array("·","·","?","·","·","|","·","·","?","·","·","||");
       
        if ("1I.".indexOf(numstyle.toUpperCase())==-1) numstyle="1";
        var numstyle=numStyles[numstyle];
       
  this.radius = Math.floor(diameter/2);
  if (isNaN(this.radius)) {
    alert( "Given diameter is not numerical" );
    this.radius=50;
  }

  this.mainElem.style.width=this.radius*2;
  this.mainElem.style.height=this.radius*2;
  this.mainElem.style.position='relative';
  this.mainElem.style.overflow='hidden';
  this.mainElem.style.margin='0px';
  this.mainElem.style.padding='0px';
  //Create Hour labels
  this.hourLabels = new Array();
  for (var i=0; i<12; i++) {
    this.hourLabels[i]=document.createElement("div");
    this.hourLabels[i].appendChild(document.createTextNode(numstyle[i]));
    this.hourLabels[i].style.position='absolute';
    this.mainElem.appendChild(this.hourLabels[i]);   
  }
  var numhours = Math.floor(this.radius*0.5);
  this.hourHand = new Array();
  for (i=0; i<numhours; i++) {
    this.hourHand[i]=document.createElement("div");
    this.hourHand[i].style.backgroundColor=hoursColor;
    this.hourHand[i].style.width=Math.ceil(this.radius*3/75)+1;
    this.hourHand[i].style.height=Math.ceil(this.radius*3/75)+1;
    this.hourHand[i].style.position='absolute';
    this.hourHand[i].style.overflow='hidden';
    this.mainElem.appendChild(this.hourHand[i]);
  }
  var numminutes = Math.floor(this.radius*0.75);
  this.minuteHand = new Array();
  for (i=0; i<numminutes; i++) {
    this.minuteHand[i]=document.createElement("div");
    this.minuteHand[i].style.backgroundColor=minutesColor;
    this.minuteHand[i].style.width=Math.round(this.radius*2/75)+1;
    this.minuteHand[i].style.height=Math.round(this.radius*2/75)+1;
    this.minuteHand[i].style.position='absolute';
    this.minuteHand[i].style.overflow='hidden';
    this.mainElem.appendChild(this.minuteHand[i]);
  }
  var numseconds = Math.floor(this.radius*0.90);
  this.secondHand = new Array();
  for (i=0; i<numseconds; i++) {
    this.secondHand[i]=document.createElement("div");
    this.secondHand[i].style.backgroundColor=secondsColor;
    this.secondHand[i].style.width=Math.floor(this.radius*1/75)+1;
    this.secondHand[i].style.height=Math.floor(this.radius*1/75)+1;
    this.secondHand[i].style.position='absolute';
    this.secondHand[i].style.overflow='hidden';
    this.mainElem.appendChild(this.secondHand[i]);
  }
  this.AmPm=document.createElement("div");
  this.AmPm.style.position="absolute";
  this.mainElem.appendChild(this.AmPm);
       
  this.DoW=document.createElement("div");
  this.DoW.style.position="absolute";
  this.mainElem.appendChild(this.DoW);
  
  this.City=document.createElement("div");
  this.City.style.position="absolute";
  this.City.font="8pt Times";
  this.mainElem.appendChild(this.City);
  
  this.Country=document.createElement("div");
  this.Country.style.position="absolute";
  this.Country.font="8pt Times";
  this.mainElem.appendChild(this.Country);

  this.clockZone = clockZone;
  this.tick = tick;
  this.cityArgument = city;
  this.countryArgument = country;

  this.daylightSavingTimeLowerBound = new Array();
  this.daylightSavingTimeUpperBound = new Array();
  this.fillDstArrays();

}

//*****************************************************************
//    class variable timeOffsetServerToLocal (initialized in constructor)
// Will contain an offset (in milliseconds) to be added to any local
// time values to be in time sync with the server
AnalogClock.prototype.timeOffsetServerToLocal = 0;

//*****************************************************************
AnalogClock.prototype.fillDstArrays = function () {
  this.daylightSavingTimeLowerBound['Paris'] = new Limit( 3, getSunday( 3, "last" ) );
  this.daylightSavingTimeUpperBound['Paris'] = new Limit( 10, getSunday( 10, "last" ) );

  this.daylightSavingTimeLowerBound['New York'] = new Limit( 3, getSunday( 3, 2 ) );
  this.daylightSavingTimeUpperBound['New York'] = new Limit( 11, getSunday( 11, 1 ) );
}

//*****************************************************************
// serverSeconds are seconds since 1.1.1970 GMT (value given by server via php)
AnalogClock.computeTimeOffset = function( serverSeconds ) {
  var localDate = new Date();
  var localMilliseconds = localDate.getTime();
  var localSeconds = Math.floor( localMilliseconds / 1000 );

  AnalogClock.prototype.timeOffsetServerToLocal = ( serverSeconds - localSeconds ) * 1000; // in milliseconds
  return( AnalogClock.prototype.timeOffsetServerToLocal );
}

//*****************************************************************

AnalogClock.prototype.update = function () {

  var localDate = new Date();
  var localMilliseconds = localDate.getTime();
  localDate.setTime( localMilliseconds );
  
  var currentOffset = localDate.getTimezoneOffset();

  // check, if offset between server and local has to be changed due to the fact,
  // that local time has switched to DST or back to standard time
  if ( AnalogClock.prototype.currentOffset == null ) {
    AnalogClock.prototype.currentOffset = currentOffset;
  }

  if ( AnalogClock.prototype.currentOffset != currentOffset ) {
    // switch took place _now_. Correct timeOffsetServerToLocal
    var correction = ( AnalogClock.prototype.currentOffset - currentOffset ) * 60 * 1000;
    alert( "Computed correction factor " + (correction/60/1000) );
    AnalogClock.prototype.timeOffsetServerToLocal += correction;
    alert( "AnalogClock.prototype.update(): Updated offset server to local to " + AnalogClock.prototype.timeOffsetServerToLocal );
    AnalogClock.prototype.currentOffset = currentOffset;
  }
  // synchronize with server time
  localDate.setTime( localMilliseconds + AnalogClock.prototype.timeOffsetServerToLocal );
  var utcDate = new Date();
  // synchronize with server time and normalize to GMT
  utcDate.setTime( localMilliseconds + AnalogClock.prototype.timeOffsetServerToLocal + currentOffset * 60 * 1000 );
  
  this.updateDisplayedClock( localDate, utcDate );
}

//*****************************************************************

// 'now' is representing the time local time, utcNow in GMT (== UTC)
AnalogClock.prototype.updateDisplayedClock = function ( now, utcNow ) {
  var arithmeticLocalDate = ArithmeticDate.getInstanceFromGivenDate( now );
  var arithmeticUtcDate = ArithmeticDate.getInstanceFromGivenDate( utcNow );
  var dateToUse = arithmeticUtcDate;

  if ( this.clockZone == "" ) {  // empty means: local time
    dateToUse = arithmeticLocalDate;
  }

  if (this.tick) {
    //Hour=Math.floor(Hour);
    dateToUse.minute=Math.floor( dateToUse.minute );
    dateToUse.second=Math.floor( dateToUse.second );
  }

  if ( this.clockZone != "" ) {  // not empty means: not local time. Compute correct time from utc time
    dateToUse.addHours( this.clockZone );
    if ( dateToUse.isBetween( this.daylightSavingTimeLowerBound[this.cityArgument],
			      this.daylightSavingTimeUpperBound[this.cityArgument] ) ) {
      if ( this.debugMode && this.lastDstFlag == 0 ) {
	alert( "DST has changed for city " + this.cityArgument );
      }
      this.lastDstFlag = 1;
      dateToUse.addHours( 1 );
    }
    else {
      if ( this.debugMode && this.lastDstFlag == 1 ) {
	alert( "DST has changed for city " + this.cityArgument );
      }
      this.lastDstFlag = 0;
    }
  }

  for (var i=0; i<this.hourLabels.length; i++) {
    radpos = (i+1)*2*Math.PI/12-Math.PI/2;
    this.hourLabels[i].style.top=this.radius+Math.sin(radpos)*this.radius*0.90-parseInt(this.hourLabels[i].offsetHeight)/2;
    this.hourLabels[i].style.left=this.radius+Math.cos(radpos)*this.radius*0.90-parseInt(this.hourLabels[i].offsetWidth)/2;
  }
  radpos=(dateToUse.hour%12)*Math.PI*2/12-Math.PI/2;
  for (i=0; i<this.hourHand.length; i++) {
    this.hourHand[i].style.top=this.radius+Math.sin(radpos)*i;
    this.hourHand[i].style.left=this.radius+Math.cos(radpos)*i;
  }
  radpos=dateToUse.minute*Math.PI*2/60-Math.PI/2;
  for (i=0; i<this.minuteHand.length; i++) {
    this.minuteHand[i].style.top=this.radius+Math.sin(radpos)*i;
    this.minuteHand[i].style.left=this.radius+Math.cos(radpos)*i;
  }
  radpos=dateToUse.second*Math.PI*2/60-Math.PI/2;
  for (i=0; i<this.secondHand.length; i++) {
    this.secondHand[i].style.top=this.radius+Math.sin(radpos)*i;
    this.secondHand[i].style.left=this.radius+Math.cos(radpos)*i;
  }
  this.AmPm.innerHTML=dateToUse.amOrPm;
  this.AmPm.style.bottom=1.333*this.radius;
  this.AmPm.style.left=this.radius-parseInt(this.AmPm.offsetWidth)/2;
  this.AmPm.style.zIndex=10;
       
  this.DoW.innerHTML=ArithmeticDate.WEEK_DAYS[dateToUse.dayOfWeek];
  this.DoW.style.top=0.666*this.radius;
  this.DoW.style.left=this.radius-parseInt(this.DoW.offsetWidth)/2;
  this.DoW.style.zIndex=10;
       
       
  this.City.innerHTML=this.cityArgument;
  this.City.style.bottom=0.666*this.radius;
  this.City.style.left=this.radius-parseInt(this.City.offsetWidth)/2;
  this.City.style.zIndex=10;
       
  this.Country.innerHTML=this.countryArgument;
  this.Country.style.top=1.333*this.radius;
  this.Country.style.left=this.radius-parseInt(this.Country.offsetWidth)/2;
  this.Country.style.zIndex=10;
}

//*****************************************************************
/*
The variable date1 is initially set to the 1st of January (month 0 in JavaScript) this year. The variable date2 is initially set to the 1st of July (month 6 in JavaScript) this year. Then date1 is converted to a GMT string. This format will be something like Fri, 25 Apr 2003 00:00:00 UTC. In fact, your computer's clock as of when this page was loaded gives a GMT string of Fri, 19 Jan 2007 21:33:37 GMT. The variable date3 pulls out everything except the time zone from that string. (The time zone says "UTC", and we want this date to be in the local time zone). So that GMT time (in the local time zone) minus the actual time (in the local time zone) is the number of hours away from GMT we are at this point. By your computer information, this difference is 1.

All JavaScript dates are in milliseconds, so 1000 milliseconds times 60 seconds times 60 minutes is the number of milliseconds in one hour. This is the methodology used to compare the hours. The check of time in January sets the benchmark for winter.

The summer time check serves the same purpose, establishing the benchmark for summer comparison. If the calculated number of hours difference changes from January to July, then daylight saving time is observed in this time zone. (However, keep in mind that not all locations within a specific time zone honor DST.) Based on the information provided by your computer and login, daylight saving time IS observed in this time zone.

This code is valid in time zones that are partial (not full hour differences) also, such as a quarter hour or a half hour difference from GMT, in some parts of the world. The hoursDiffStdTime variable value will obviously not be a whole number; the fractional part will be .25, .5, or .75 depending on the time zone value. Pay particular attention in your coding NOT to make that variable an integer. The button below will test the actual Javascript code within this page. It is identical to what is shown above.
*/
function checkTimeZone() {
   var rightNow = new Date();
   var date1 = new Date(rightNow.getFullYear(), 0, 1, 0, 0, 0, 0);
   var date2 = new Date(rightNow.getFullYear(), 6, 1, 0, 0, 0, 0);
   var temp = date1.toGMTString();
   var date3 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1));
   var temp = date2.toGMTString();
   var date4 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1));
   var hoursDiffStdTime = (date1 - date3) / (1000 * 60 * 60);
   var hoursDiffDaylightTime = (date2 - date4) / (1000 * 60 * 60);
   if (hoursDiffDaylightTime == hoursDiffStdTime) {
      alert("Time zone is GMT " + hoursDiffStdTime + ".\nDaylight Saving Time is NOT observed here.");
   } else {
      alert("Time zone is GMT " + hoursDiffStdTime + ".\nDaylight Saving Time is observed here.");
   }
}

//*****************************************************************
/*
IN GENERAL, in the US, in those places that observe DST
  DST begins at 2am on the first Sunday in April  
  DST ends at 2am on the last Sunday in October
From 2007 on DST in New York
  starts second Sunday in March
  ends first Sunday in November
Paris DST
  starts last Sunday in March
  ends last Sunday in October
*/

/* get the day of the week of an arbitrary given date */
function getDayOfTheWeek( day, month, year ) {
  dayNames = new Array('So','Mo','Di','Mi','Do','Fr','Sa');
  
  secret = month < 3 ? 1 : 0;
  if ( secret == 1 ) year--;
  month += -2 + ( secret * 12 );
  
  indexToUse = (day+Math.floor((31*month)/12)+year+Math.floor(year/4)-Math.floor(year/100)+Math.floor(year/400))%7;
  return dayNames[indexToUse];
}

//*********************************************************************************************************
//                          Utility Class Limit
//*********************************************************************************************************
function Limit( month, day ) {
  this.month = month - 1; // months have to be stored in values from 0 to 11
  this.day = day;
}
//*********************************************************************************************************
//                               Class ArithmeticDate
//*********************************************************************************************************
function ArithmeticDate( year, month, day, hour, minute, second, millisecond, dayOfWeek ) {
  this.millisecond = millisecond;
  this.second = second+this.millisecond/1000;
  this.minute = minute+this.second/60;
  this.hour = hour+this.minute/60;
  this.year = year;
  this.month = month;
  this.day =  day; // 1..31
  this.amOrPm = this.hour<12 ? "AM" : "PM";
  this.dayOfWeek = dayOfWeek; // 0 (Sunday) .. 6 (Saturday)

}

//*****************************************************************
ArithmeticDate.prototype.getHourMax12 = function() {
  if ( this.hour > 12 ) {
    return( this.hour - 12 );
  }
  else {
    return( this.hour );
  }
}
//*****************************************************************
ArithmeticDate.getInstanceFromGivenDate = function( givenDate ) {
  return( new ArithmeticDate( givenDate.getFullYear(), givenDate.getMonth(), givenDate.getDate(), givenDate.getHours(),
			      givenDate.getMinutes(), givenDate.getSeconds(), givenDate.getMilliseconds(), givenDate.getDay() ) );
}
//*****************************************************************
// To add was checked above to be in range -12..12
ArithmeticDate.prototype.isBetween = function( lowerLimit, upperLimit ) {
  if ( lowerLimit != null && upperLimit != null ) {
    if ( ( this.month > lowerLimit.month ) ||
	 ( this.month == lowerLimit.month && this.day > lowerLimit.day ) ||
	 ( this.month == lowerLimit.month && this.day == lowerLimit.day && this.hour >= 2 ) ) {

      if ( ( this.month < upperLimit.month ) ||
	   ( this.month == upperLimit.month && this.day < upperLimit.day ) ||
	   ( this.month == upperLimit.month && this.day == upperLimit.day && this.hour < 2 ) ) {
	return( true );
      }
    }
  }
  return( false );
}
//*****************************************************************
// To add was checked above to be in range -12..12
ArithmeticDate.prototype.addHours = function( toAdd ) {
  var fullHours = Math.floor( toAdd );
  var minutesToAdd = (toAdd - fullHours) * 60; // yes: there are timezones measured not in full hours!!
  if ( minutesToAdd != 0 ) {
    this.minutes += minutesToAdd;
    if ( this.minutes >= 60 ) {
      this.hour += 1;
      this.minutes -= 60;
    }
    if ( this.minutes < 0 ) {
      this.hour -= 1;
      this.minutes += 60;
    }
  }
  this.hour += fullHours;
  this.amOrPm = this.hour<12 ? "AM" : "PM";

  if ( this.hour<0 ) {
    this.hour += 12;
    this.amOrPm="PM";     // switched to latenight
      
    if ( this.dayOfWeek == 0 ) {
      this.dayOfWeek = 6;
    }
    else {
      this.dayOfWeek--;
    }

    if ( this.day == 1 ) {
      if ( this.month == 0 ) {
	this.month = 12;
	this.year--;
      }
      else {
	this.month--;
      }
      this.day=ArithmeticDate.MONTH_LENGTHS[this.month];
    }
    else {
      this.day--;
    }

    
  }
  if (this.hour>24) {
    //this.hour -= 12;
    this.hour = this.hour%24;
    this.amOrPm="AM";     // switched to early morning
    if ( this.dayOfWeek == 6 ) {
      this.dayOfWeek = 0;
    }
    else {
      this.dayOfWeek++;
    }

    if ( this.day == ArithmeticDate.MONTH_LENGTHS[this.month] ) {
      if ( this.month == 11 ) {
	this.month = 1;
	this.year++;
      }
      else {
	this.month++;
      }
      this.day = 1;
    }
    else {
      this.day++;
    }
  }
}
//*****************************************************************
ArithmeticDate.prototype.toString = function() {
  var month = this.month + 1;
  return( this.day + "." + month + "." + this.year + " " + this.hour + ":" + this.minute + ":" + this.second + " " +
	  this.amOrPm + " " + ArithmeticDate.WEEK_DAYS[this.dayOfWeek] );
}
//*****************************************************************

// class variables
ArithmeticDate.MONTH_LENGTHS = new Array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
ArithmeticDate.WEEK_DAYS = new Array("Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag");

//*********************************************************************************************************
/* get a certain Sunday of given month (1..12):
     1 gets first sunday
     2 gets second Sunday ...
   If a number >= last Sunday or no number at all (e.g. the string 'last') is specified,
   the last Sunday of the given month is returned
*/
function getSunday( month, whichOne ) {
  var year = new Date().getFullYear();
  var dayOfInterest = -1;
  var numberOfSundays = 0;
  var monthLength = ArithmeticDate.MONTH_LENGTHS;
  for( i=1; i <= monthLength[month-1]; i++ ) {
    if ( getDayOfTheWeek( i, month, year ) == "So" ) {
      dayOfInterest = i;
      numberOfSundays++;
      if ( numberOfSundays == whichOne ) {
	break;
      }
    }
  }
  return( dayOfInterest );
}
