/*
 * Timeglider for Javascript / jQuery 
 * http://timeglider.com/jquery
 *
 * Copyright 2011, Mnemograph LLC
 * Licensed under Timeglider Dual License
 * http://timeglider.com/jquery/?p=license
 *
 */

// initial declaration of timeglider object for widget
// authoring app will declare a different object, so
// this will defer to window.timeglider
timeglider = window.timeglider || {mode:"publish", version:"0.1.0", ui:{touchtesting:false}};


/*
*  TG_Date
* 
*  dependencies: jQuery, Globalize
*
* You might be wondering why we're not extending JS Date().
* That might be a good idea some day. There are some
* major issues with Date(): the "year zero" (or millisecond)
* in JS and other date APIs is 1970, so timestamps are negative
* prior to that; JS's Date() can't handle years prior to
* -271820, so some extension needs to be created to deal with
* times (on the order of billions of years) existing before that.
*
* This TG_Date object also has functionality which  goes hand-in-hand
* with the date hashing system: each event on the timeline is hashed
* according to day, year, decade, century, millenia, etc
*
*/

/*

IMPORTED DATE STANDARD

http://www.w3.org/TR/NOTE-datetime
"a profile of" ISO 8601 date format

Complete date plus hours, minutes and seconds:
YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)

Acceptable:
YYYY
YYYY-MM
YYYY-MM-DD
YYYY-MM-DDT13
YYYY-MM-DD 08:15 (strlen 16)
YYYY-MM-DD 08:15:30 (strlen 19)
(above would assume either a timeline-level timezone, or UTC)

containing its own timezone, this would ignore timeline timezone
YYYY-MM-DD 08:15:30-07:00
   
*/

timeglider.TG_Date = {};


(function(tg){
  
  	
	var tg = timeglider, $ = jQuery;
  
	// caches speed up costly calculations
	var getRataDieCache = {},
		getDaysInYearSpanCache = {},
		getBCRataDieCache = {},
		getDateFromRDCache = {},
		getDateFromSecCache = {};
		
	var VALID_DATE_PATTERN = /^(\-?\d+)?(\-\d{1,2})?(\-\d{1,2})?(?:T| )?(\d{1,2})?(?::)?(\d{1,2})?(?::)?(\d{1,2})?(\+|\-)?(\d{1,2})?(?::)?(\d{1,2})?/;
  
  
   // MAIN CONSTRUCTOR
        
	tg.TG_Date = function (strOrNum, date_display, offSec) {

		var dateStr, isoStr, gotSec,
    		offsetSeconds = offSec || 0;

		if (typeof(strOrNum) == "number") {
      	  // SERIAL SECONDS	
			dateStr = isoStr = TG_Date.getDateFromSec(strOrNum);
			gotSec = (strOrNum + offsetSeconds);
		
		} else if (typeof(strOrNum) === "object") {
			// TODO: JS Date object?
			// dateStr = strOrNum.ye + "-" + strOrNum.mo + "-" + strOrNum.da 
		
			
		} else {
		// STRING
			if (strOrNum == "today") {
				strOrNum = TG_Date.getToday();
			}
			
			dateStr = isoStr = strOrNum;
		}
  
  
  		if (VALID_DATE_PATTERN.test(dateStr)) {

			// !TODO: translate strings like "today" and "now"
			// "next week", "a week from thursday", "christmas"
	       		
      		var parsed =  TG_Date.parse8601(dateStr);
      		
      		
      		if (parsed.tz_ho) {
      			// this is working ------ timezones in the string translate correctly
      			// OK: transforms date properly to UTC since it should have been there
      			parsed = TG_Date.toFromUTC(parsed, {hours:parsed.tz_ho, minutes:parsed.tz_mi}, "to");
      		}
      		
      		
      		// ye, mo, da, ho, mi, se arrive in parsed (with tz_)
      					
			$.extend(this,parsed);

      		// SERIAL day from year zero
      		this.rd  = TG_Date.getRataDie(this);
    
      		// SERIAL month from year 0
      		this.mo_num = getMoNum(this);
      		
      		// SERIAL second from year 0
      		this.sec = gotSec || getSec(this);
      		
      		this.date_display = (date_display) ? (date_display.toLowerCase()).substr(0,2) : "ho";
			
			// TODO: get good str from parse8601  
      		this.dateStr = isoStr;
  		
  		} else {
  			return {error:"invalid date"};
  		}
		        
        return this;

  } // end TG_Date Function



  var TG_Date = tg.TG_Date;

  /*
  *  getTimeUnitSerial
  *  gets the serial number of specified time unit, using a ye-mo-da date object
  *  used in addToTicksArray() in Mediator
  *
  *  @param fd {object} i.e. the focus date: {ye:1968, mo:8, da:20}
  *  @param unit {string} scale-unit (da, mo, ye, etc)
  *
  *  @return {number} a non-zero serial for the specified time unit
  */
  TG_Date.getTimeUnitSerial = function (fd, unit) {
      var ret = 0;
      var floorCeil;
      
      if (fd.ye < 0) {
      	floorCeil = Math.ceil;
      } else {
      	floorCeil = Math.floor;
      }
      
  		switch (unit) {
  			case "da": ret =  fd.rd; break;
  			// set up mo_num inside TG_Date constructor
  			case "mo": ret =  fd.mo_num; break;
  			case "ye": ret = fd.ye; break;
  			case "de": ret =  floorCeil(fd.ye / 10); break;
  			case "ce": ret =  floorCeil(fd.ye / 100); break;
  			case "thou": ret =  floorCeil(fd.ye / 1000); break;
  			case "tenthou": ret =  floorCeil(fd.ye / 10000); break;
  			case "hundredthou": ret =  floorCeil(fd.ye / 100000); break;
  			case "mill": ret =  floorCeil(fd.ye / 1000000); break;
  			case "tenmill": ret =  floorCeil(fd.ye / 10000000); break;
  			case "hundredmill": ret =  floorCeil(fd.ye / 100000000); break;
  			case "bill": ret =  floorCeil(fd.ye / 1000000000); break;
  		}
  		return ret;
  };



	TG_Date.getMonthDays = function(mo,ye) {
  		if ((TG_Date.isLeapYear(ye) == true) && (mo==2)) {
  			return 29;
  		} else  {
  			return TG_Date.monthsDayNums[mo];
		}
	};


	TG_Date.twentyFourToTwelve = function (e) {
	
		var dob = {};
		dob.ye = e.ye;
		dob.mo = e.mo || 1;
		dob.da = e.da || 1;
		dob.ho = e.ho || 0;
		dob.mi = e.mi || 0;
		dob.ampm = "am";
	
		
		if (e.ho >= 12) {
			dob.ampm = "pm";
			if (e.ho > 12) {
				dob.ho = e.ho - 12;
			} else {
				dob.ho = 12;
			}
		} else if (e.ho == 0) {
			dob.ho = 12;
			dob.ampm = "am";
		} else {
			dob.ho = e.ho;
		}
		
		
		if (dob.mi < 9) {
			dob.mi = "0" + dob.mi;
		} 
	
		return dob;
	};
	
	
	/*
	* RELATES TO TICK WIDTH: SPECIFIC TO TIMELINE VIEW
	*/
	TG_Date.getMonthAdj = function (serial, tw) {
		var d = TG_Date.getDateFromMonthNum(serial);
		var w;
		switch (d.mo) {
		
			// 31 days
			case 1: case 3: case 5: case 7: case 8: case 10: case 12:
				var w = Math.floor(tw + ((tw/28) * 3));
				return {"width":w, "days":31};
			break;
	
			// Blasted February!
			case 2:
			if (TG_Date.isLeapYear(d.ye) == true) {
				w = Math.floor(tw + (tw/28));
				return {"width":w, "days":29};
			} else {
				return {"width":tw, "days":28};
			}
			break;
		
			default: 
			// 30 days
			w = Math.floor(tw + ((tw/28) * 2));
			return {"width":w, "days":30};
		}
	
	
	};


	/*
	* getDateFromMonthNum
	* Gets a month (1-12) and year from a serial month number
	* @param mn {number} serial month number
	* @return {object} ye, mo (numbers)
	*/
	TG_Date.getDateFromMonthNum = function(mn) {
	
		var rem = 0;
		var ye, mo;
	
		if (mn > 0) {
			rem = mn % 12;
	
			if (rem == 0) { rem = 12 };
	
			mo = rem;
			ye = Math.ceil(mn / 12);
	
		} else {
			// BCE!
			rem = Math.abs(mn) % 12;
			mo = (12 - rem) + 1;
			if (mo == 13) mo = 1;
			// NOYEARZERO problem: here we would subtract
			// a year from the results to eliminate the year 0
			ye =  -1 * Math.ceil(Math.abs(mn) / 12); // -1
	
			}
		
		return {ye:ye, mo:mo};
	};



	/*
	* getMonthWidth
	* Starting with a base-width for a 28-day month, calculate
	* the width for any month with the possibility that it might
	* be a leap-year February.
	*
	* @param mo {number} month i.e. 1 = January, 12 = December
	* @param ye {number} year
	*
	* RELATES TO TICK WIDTH: SPECIFIC TO TIMELINE VIEW
	*/
	TG_Date.getMonthWidth = function(mo,ye,tickWidth) {
	
		var dayWidth = tickWidth / 28;
		var ad;
		var nd = 28;
	
		switch (mo) {
			case 1: case 3: case 5: case 7: case 8: case 10: case 12: ad = 3; break;
			case 4: case 6: case 9: case 11: ad = 2; break;
			// leap year
			case 2: if (TG_Date.isLeapYear(ye) == true) { ad = 1; } else { ad=0; }; break;
		
		}
	
		var width = Math.floor(tickWidth + (dayWidth * ad));
		var days = nd + ad;
	
		return {width:width, numDays:days};
	};




	TG_Date.getToday = function () {
		var d = new Date(); 
		return d.getUTCFullYear() + "-" + (d.getUTCMonth() + 1) + "-" + d.getUTCDate() + " " + d.getUTCHours() + ":" + d.getUTCMinutes() + ":" + d.getUTCSeconds();
	}


  /*
   * Helps calculate the position of a modulo remainder in getRataDie()
   */
  	TG_Date.getMonthFromRemDays = function (dnum, yr) {

	  	var tack = 0;
	  	var rem = 0;
	  	var m = 0;
	
	  	if (TG_Date.isLeapYear(yr)){ tack = 1; } else { tack=0; }
		
	  	if (dnum <= 31) { m = 1; rem = dnum; }
	  	else if ((dnum >31) && (dnum <= 59 + tack)) { m = 2; rem = dnum - (31 + tack); }
	  	else if ((dnum > 59 + tack) && (dnum <= 90 + tack)) { m = 3; rem = dnum - (59 + tack); }
	  	else if ((dnum > 90 + tack) && (dnum <= 120 + tack)) { m = 4; rem = dnum - (90 + tack); }
	  	else if ((dnum > 120 + tack) && (dnum <= 151 + tack)) { m = 5; rem = dnum - (120 + tack); }
	  	else if ((dnum > 151 + tack) && (dnum <= 181 + tack)) { m = 6; rem = dnum - (151 + tack); }
	  	else if ((dnum > 181 + tack) && (dnum <= 212 + tack)) { m = 7; rem = dnum - (181 + tack); }
	  	else if ((dnum > 212 + tack) && (dnum <= 243 + tack)) { m = 8; rem = dnum - (212 + tack); }
	  	else if ((dnum > 243 + tack) && (dnum <= 273 + tack)) { m = 9; rem = dnum - (243 + tack); }
	  	else if ((dnum > 273 + tack) && (dnum <= 304 + tack)) { m = 10; rem = dnum - (273 + tack); }
	  	else if ((dnum > 304 + tack) && (dnum <= 334 + tack)) { m = 11; rem = dnum - (304 + tack); }
	  	else { m = 12; rem = dnum - (334 + tack);  }
	
	  	return {mo:m, da:rem};

  	};





  /*
   GET YYYY.MM.DD FROM (serial) rata die 
  @param snum is the rata die or day serial number
  */
  TG_Date.getDateFromRD = function (snum) {
    
    if (getDateFromRDCache[snum]) {
      return getDateFromRDCache[snum]
    }
    // in case it arrives as an RD-decimal
    var snumAb = Math.floor(snum);

    var bigP = 146097; // constant days in big cal cycle
    var chunk1 = Math.floor(snumAb / bigP);
    var chunk1days = chunk1 * bigP;
    var chunk1yrs = Math.floor(snumAb / bigP) * 400;
    var chunk2days = snumAb - chunk1days;
    var dechunker = chunk2days; 
    var ct = 1;

    var ia = chunk1yrs + 1;
    var iz = ia + 400;

    for (var i = ia; i <= iz; i++) {
    	if (dechunker > 365) {
    		dechunker -= 365;
    		if (TG_Date.isLeapYear(i)) { dechunker -= 1; }
    		ct++;
    	}  else { i = iz; }
    }

  	var yt = chunk1yrs + ct;
	
  	if (dechunker == 0) dechunker = 1;
  	var inf = TG_Date.getMonthFromRemDays(dechunker,yt);
  	// in case...
  	var miLong = (snum - snumAb) * 1440;
  	var mi = Math.floor(miLong % 60);
  	var ho = Math.floor(miLong / 60);
	
  	if ((TG_Date.isLeapYear(yt)) && (inf['mo'] == 2)) {
  		inf['da'] += 1;
  	}

  	var ret = yt + "-" + inf['mo'] + "-" + inf['da'] + " " + ho + ":" + mi + ":00";
	  getDateFromRDCache[snum] = ret;
	  
	  return ret;
	
  }, // end getDateFromRD


  TG_Date.getDateFromSec = function (sec) {
  	// FIRST GET Rata die
  	if (getDateFromSecCache[sec]) {
  	  return getDateFromSecCache[sec]
	  }
	  
	  // the sec/86400 represents a "rd-decimal form"
	  // that will allow extraction of hour, minute, second
  	var ret = TG_Date.getDateFromRD(sec / 86400);
  	
  	getDateFromSecCache[sec] = ret;
  	
  	return ret;
  };


  TG_Date.isLeapYear =  function(y) {
    if (y % 400 == 0) {
      return true;
    } else if (y % 100  == 0){
      return false;
    } else if (y % 4 == 0) {
      return true;
    } else {
      return false;
    }
  };


  /*
  * getRataDie
  * Core "normalizing" function for dates, the serial number day for
  * any date, starting with year 1 (well, zero...), wraps a getBCRataDie()
  * for getting negative year serial days
  *
  * @param dat {object} date object with {ye, mo, da}
  * @return {number} the serial day
  */
  TG_Date.getRataDie = function (dat) {
	  
  	var ye = dat.ye;
  	var mo = dat.mo;
  	var da = dat.da;
  	var ret = 0;
  	
  	if (getRataDieCache[ye + "-" + mo + "-" + da]) {
  	  return getRataDieCache[ye + "-" + mo + "-" + da];
	  }

  if (ye >= 0) { 
  	// THERE IS NO YEAR ZERO!!!
  	if (ye == 0) ye = 1;

  	var fat =  (Math.floor(ye / 400)) * 146097,
  	    remStart = (ye - (ye % 400)),
  	    moreDays = parseInt(getDaysInYearSpan(remStart, ye)),
  	    daysSoFar = parseInt(getDaysSoFar(mo,ye));
	    
  	ret = (fat + moreDays + daysSoFar + da) - 366;
	
  } else if (ye < 0) {
    
  	ret = TG_Date.getBCRataDie({ye:ye, mo:mo, da:da});
  } 

  getRataDieCache[ye + "-" + mo + "-" + da] = ret;
  
  return ret;

  ////// internal RataDie functions
      	/*
      	*  getDaysInYearSpan
      	*  helps calculate chunks of whole years
      	
      	*  @param a {number} initial year in span
      	*  @param z {number} last year in span
      	* 
      	*  @return {number} days in span of arg. years
      	*/
        function getDaysInYearSpan (a, z) {
  
          if (getDaysInYearSpanCache[a + "-" + z]) {
            return getDaysInYearSpanCache[a + "-" + z];
          }
        	var t = 0;

        	for (var i = a; i < z; i++){
        		if (TG_Date.isLeapYear(i)) { t += 366; } else { t += 365; }
        	}
      	
          getDaysInYearSpanCache[a + "-" + z] = t;
        
        	return t;

        };


        function getDaysSoFar (mo,ye) {
        	
        	var d;
	
        	switch (mo) {
        		case 1: d=0;   break; // 31
        		case 2: d=31;  break; // 29
        		case 3: d=59;  break; // 31
        		case 4: d=90;  break; // 30
        		case 5: d=120; break; // 31
        		case 6: d=151; break; // 30
        		case 7: d=181; break; // 31
        		case 8: d=212; break; // 31
        		case 9: d=243; break; // 30
        		case 10: d=273;break; // 31
        		case 11: d=304;break; // 30
        		case 12: d=334;break; // 31
        	}
	
        	if (mo > 2) {
        	   if (TG_Date.isLeapYear(ye)) { d += 1; }
        	}

        	return d;
        };


  };

	TG_Date.monthNamesLet = ["","J","F","M","A","M","J","J","A","S","O","N","D"];

    TG_Date.monthsDayNums = [0,31,28,31,30,31,30,31,31,30,31,30,31,29];
  
    // NON-CULTURE
    TG_Date.units = ["da", "mo", "ye", "de", "ce", "thou", "tenthou", "hundredthou", "mill", "tenmill", "hundredmill", "bill"];
    
    
  /*
  Counts serial days starting with -1 in year -1. Visualize a number counting 
  from "right to left" on top of the other calendrical pieces chunking away
  from "left to right".  But since there's no origin farther back before 0
  we have no choice. 

  @param dat  object with .ye, .mo, .da
  */
  TG_Date.getBCRataDie = function (dat) {

  	var ye = dat.ye,
  	    mo = dat.mo,
  	    da = dat.da;
  	
  	if (getBCRataDieCache[ye + "-" + mo + "-" + da]) {
    	  return getBCRataDieCache[ye + "-" + mo + "-" + da];
  	}

  	if (mo == 0) mo = 1;
  	if (da == 0) da = 1;

  	var absYe = Math.abs(ye);
  	var chunks = [0,335,306,275,245,214,184,153,122,92,61,31,0];
  	var mdays = TG_Date.monthsDayNums[mo];
  	var rawYeDays = (absYe - 1) * 366;
  	var rawMoDays = chunks[mo];
  	var rawDaDays = (mdays - da) + 1;
  	var ret = -1 * (rawYeDays + rawMoDays + rawDaDays);
  	
  	getBCRataDieCache[ye + "-" + mo + "-" + da] = ret;
  	return ret;
  };



  TG_Date.setCulture = function(culture_str) {
    
    var cult = tg.culture = Globalize.culture(culture_str || "default");
     
  	// ["","January", "February", "March", etc];
    TG_Date.monthNames = $.merge([""], cult.calendar.months.names);
    
    // ["","Jan", "Feb", "Mar", etc];
    TG_Date.monthNamesAbbr = $.merge([""], cult.calendar.months.namesAbbr);

    // ["Sunday", "Monday", "Tuesday", etc];
    TG_Date.dayNames = cult.calendar.days.names;
  
    // ["Sun", "Mon", "Tue", etc];
    TG_Date.dayNamesAbbr = cult.calendar.days.namesAbbr;
  
    TG_Date.dayNamesShort = cult.calendar.days.namesShort;
  
    TG_Date.patterns = cult.calendar.patterns;
    
  };



  /*
  *  INSTANCE METHODS  
  *
  */
  TG_Date.prototype = {
      
		format : function (sig, useLimit, tz_off) {
		
			var offset = tz_off || {"hours":0, "minutes":0};
			
			var jsDate, ddlim = "da", fromUTC; // jsDate

			var tgFormatDate = function(fromUTC, display) {
				
				var tgd = timeglider.TG_Date,
					f = "", d = fromUTC, ampm, hopack = {},
					disp = display || "da",
					
					bceYe = function(ye) {
					 
						var  y = parseInt(ye,10);
					
						if (y < 0) {
							return Math.abs(y) + " bce";
						} else {
							return y;
						}
					};
		
				switch (disp) {
				 	
				 	case "no":  return ""; break;
					case "ye": f = bceYe(d.ye); break;
					case "mo": f = tgd.monthNamesAbbr[d.mo] + " " + bceYe(d.ye); break;
					case "da": f =bceYe(d.ye) + "-"+ tgd.monthNamesAbbr[d.mo] + "-" + d.da ; break;
					case "ho": 
						ampm  = "AM";
						hoPack = tgd.twentyFourToTwelve(d);
						var hour=hoPack.ho;
						if(hoPack.ampm=="pm")
							hour=hour+12;
						f =d.ye+"-"+d.mo+"-"+d.da+ " " +hour+":"+hoPack.mi;
						
						//f = tgd.monthNamesAbbr[d.mo] 
							//+ " " + d.da + ", " 
							//+ bceYe(d.ye) + " " 
							//+ hoPack.ho + ":" + hoPack.mi + " " + hoPack.ampm; 
						break;
				} // end switch
				
				return f;
		
			};
	
			if (useLimit == true) {
			  // reduce to 2 chars for consistency
			  ddlim = this.date_display.substr(0,2);
			  	  
			  switch (ddlim) {
			    case "no": return ""; break;
			    case "ye": sig = "yyyy"; break;
			    case "mo": sig = "MMM yyyy"; break;
			    case "da": sig = "MMM d, yyyy"; break;
			    case "ho": sig = "MMM d, yyyy h:mm tt"; break;
			    
			    default: sig = "f";
			  }
			}
			
			
			var cloner = _.clone(this),
			
				fromUTC = TG_Date.toFromUTC(cloner, offset, "from");  
          	
          		          		
      		if (timeglider.i18n) {
      			// make use of possible other culture via i18n
      			return timeglider.i18n.formatDate(fromUTC, ddlim);
      			
      		} else {
      			// dates before roughly this time do not work in JS
      			if (fromUTC.ye < -270000){
      				return this.ye;
      			} else {
      				
      				// jsDate = new Date(fromUTC.ye, (fromUTC.mo-1), fromUTC.da, fromUTC.ho, fromUTC.mi, fromUTC.se, 0);
      				
      				// return Globalize.format(jsDate, sig);
      				return tgFormatDate(fromUTC, ddlim);
      				
      			}
      		}

		}
		
  	} // end .prototype
  	
  	
  
	TG_Date.getTimeOffset = function(offsetString) {
		
		// remove all but numbers, minus, colon
		var oss = offsetString.replace(/[^-\d:]/gi, ""),
			osA = oss.split(":"),
			ho = parseInt(osA[0], 10),
			mi = parseInt(osA[1], 10),
		
			// minutes negative if hours are 
			sw = (ho < 0) ? -1 : 1,
		
			miDec = sw * ( mi / 60 ),
			dec = (ho + miDec),
			se = dec * 3600;
			
			var ob = {"decimal":dec, "hours":ho, "minutes":mi, "seconds":se, "string":oss};
	
			return ob; 
		
	};	
	
	
	TG_Date.tzOffsetStr = function (datestr, offsetStr) {
		if (datestr) {
		if (datestr.length == 19) {
			datestr += offsetStr;
		} else if (datestr.length == 16) {
			datestr += ":00" + offsetStr;
		}
		return datestr;
		}
	};
	
		
	/*
	* TG_parse8601
	* transforms string into TG Date object
	*/
	TG_Date.parse8601 = function(str){
		
		/*
		len   str
    	4     YyYyYyY
		7     YyYyYyY-MM
		10    YyYyYyY-MM-DD
		13    YyYyYyY-MM-DDTHH (T is optional between day and hour)
		16    YyYyYyY-MM-DD HH:MM
		19    YyYyYyY-MM-DDTHH:MM:SS
		25    YyYyYyY-MM-DD HH:MM:SS-ZH:ZM
		*/
		
		var ye, mo, da, ho, mi, se, bce, bce_ye, tz_pm, tz_ho, tz_mi,
			mo_default = 1,
			da_default = 1,
			ho_default = 0,
			mi_default = 0,
			se_default = 0,
			
			dedash = function (n){
				if (n) {
			 		return parseInt(n.replace("-", ""), 10);
			 	} else {
			 		return 0;
			 	}
			},
			//       YyYyYyY    MM          DD
			reg = VALID_DATE_PATTERN;
			var rx = str.match(reg);

    	// picks up positive OR negative (bce)	
		ye = parseInt(rx[1]);
		
		if (!ye) return {"error":"invalid date; no year provided"};

		mo = dedash(rx[2]) || mo_default;
		da = dedash(rx[3]) || da_default;
		// rx[4] is the "T" or " "
		ho = dedash(rx[4]) || ho_default;
		// rx[6] is ":"
		mi = dedash(rx[5]) || mi_default;
		// rx[8] is ":"
		se = dedash(rx[6]) || se_default;
				
		// if year is < 1 or > 9999, override
		// tz offset, set it to 0/UTC no matter what
		
		// If the offset is negative, we want to make
		// sure that minutes are considered negative along
		// with the hours"-07:00" > {tz_ho:-7; tz_mi:-30}
		tz_pm = rx[7] || "+";
   		tz_ho = parseInt(rx[8], 10) || 0;
		if (tz_pm == "-") {tz_ho = tz_ho * -1;}
		tz_mi = parseInt(rx[9], 10) || 0;
		if (tz_pm == "-") {tz_mi = tz_mi * -1;}
		
	
		return {"ye":ye, "mo":mo, "da":da, "ho":ho, "mi":mi, "se":se, "tz_ho":tz_ho, "tz_mi":tz_mi};
		

	}; // parse8601
	
	
	TG_Date.getLastDayOfMonth = function(ye, mo) {
		var lastDays = [0,31,28,31,30,31,30,31,31,30,31,30,31],
			da = 0;
		if (mo == 2 && TG_Date.isLeapYear(ye) == true) {
			da = 29;
		} else {
			da = lastDays[mo];
		}
		return da;
		
	}; 
	
	/* 
	* getDateTimeStrings
	*
	* @param str {String} ISO8601 date string
	* @return {Object} date, time as strings with am or pm
	*/
	TG_Date.getDateTimeStrings = function (str) {
		
		var obj = TG_Date.parse8601(str);
	
		if (str == "today" || str == "now") {
			return {"date": str, "time":""}
		} else {
			var date_val = obj.ye + "-" + unboil(obj.mo) + "-" + unboil(obj.da);
		}
				
		var ampm = "pm";
		
		if (obj.ho >= 12) {
			if (obj.ho > 12) obj.ho -= 12;
			ampm = "pm";
		} else {
			if (obj.ho == 0) { obj.ho = "12"; }
			ampm = "am";
		}
	
		var time_val = boil(obj.ho) + ":" + unboil(obj.mi) + " " + ampm;
		
		return {"date": date_val, "time":time_val}
	};
	
	
	// This is for a separate date input field --- YYYY-MM-DD (DATE ONLY)
	// field needs to be restricted by the $.alphanumeric plugin
	TG_Date.transValidateDateString = function (date_str) {
		
		if (date_str == "today" || date_str == "now"){
			return date_str;
		}
		
		if (!date_str) return false; // date needs some value
		
		var reg = /^(\-?\d+|today|now) ?(bce?)?-?(\d{1,2})?-?(\d{1,2})?/,
			valid = "",
			match = date_str.match(reg),
			zb = TG_Date.zeroButt;
			
		if (match) {
			// now: 9999-09-09
			// today: get today
			
			// translate
			var ye = match[1],
				bc = match[2] || "",
				mo = match[3] || "07",
				da = match[4] || "1";
			
			if (parseInt(ye, 10) < 0 || bc.substr(0,1) == "b") {
				ye = -1 * (Math.abs(ye));
			}
			
			if (TG_Date.validateDate(ye, mo, da)) {
				return ye + "-" + zb(mo) + "-" + zb(da);
			} else {
				return false;
			}
		
			
		} else {
			return false;
		}
	};
	
	// This is for a separate TIME input field: 12:30 pm
	// field needs to be restricted by the $.alphanumeric plugin
	TG_Date.transValidateTimeString = function (time_str) {
		
		if (!time_str) return "12:00:00";
		
		var reg = /^(\d{1,2}|noon):?(\d{1,2})?:?(\d{1,2})? ?(am|pm)?/i,
			match = time_str.toLowerCase().match(reg),
			valid = "",
			zb = TG_Date.zeroButt;
		
		if (match[1]) {

			// translate
			if (match[0] == "noon") {
				valid = "12:00:00"
			} else {
				// HH MM
				var ho = parseInt(match[1], 10) || 12;
				var mi = parseInt(match[2], 10) || 0;
				var se = parseInt(match[3], 10) || 0;
				var ampm = match[4] || "am";
				
				if (TG_Date.validateTime(ho, mi, se) == false) return false;
				
				if (ampm == "pm" && ho < 12) {
					ho += 12;
				} else if (ampm == "am" && ho ==12){
					ho = 0;
				} 
				
				valid = zb(ho) + ":" + zb(mi) + ":" + zb(se);
			}
		} else {
			valid = false;
		}
		
		return valid;
	};
	
	
	// make sure hours and minutes are valid numbers
	TG_Date.validateTime = function (ho, mi, se) {
		if ((ho < 0 || ho > 23) || (mi < 0 || mi > 59) || (se < 0 || se > 59)) { return false; }
		return true;
	};
	
	
  	/*
  	* validateDate
  	* Rejects dates like "2001-13-32" and such
  	*
  	*/
  	TG_Date.validateDate = function (ye, mo, da) {
  		
  		// this takes care of leap year
  		var ld = TG_Date.getMonthDays(mo, ye);

  		if ((da > ld) || (da <= 0)) { return false; } 
  		// invalid month numbers
  		if ((mo > 12) || (mo < 0)) { return false; }
  		// there's no year "0"
  		if (ye == 0) { return false; }
  		
  		return true;
  	};
      	
      	
	// make sure hours and minutes are valid numbers
	TG_Date.zeroButt = function (n) {
		
		var num = parseInt(n, 10);
		if (num > 9) {
			return String(num);
		} else {
			return "0" + num;
		}
	}
		

	/*
	* toFromUTC
	* transforms TG_Date object to be either in UTC (GMT!) or in non-UTC
	*
	* @param ob: {Object} date object including ye, mo, da, etc
	* @param offset: {Object} eg: hours, minutes {Number} x 2
	* @param toFrom: either "to" UTC or "from"
	*
	* with offsets made clear. Used for formatting dates at all times
	* since all event dates are stored in UTC
	*
	* @ return {Object} returns SIMPLE DATE OBJECT: not a full TG_Date instance
	*                   since we don't want the overhead of calculating .rd etc.
	*/		
	TG_Date.toFromUTC = function (ob, offset, toFrom) {
				
		var nh_dec = 0,
			lastDays = [0,31,28,31,30,31,30,31,31,30,31,30,31,29],
			
			deltaFloatToHM = function (flt){
				var fl = Math.abs(flt),
					h = Math.floor(fl),
					dec = fl - h,
					m = Math.round(dec * 60);
				
				return {"ho":h, "mi":m, "se":0};
			},
			delta = {};
						
		// Offset is the "timezone setting" on the timeline,
		// or the timezone to which to translate from UTC
		if (toFrom == "from") {
			delta.ho = -1 * offset.hours;
			delta.mi = -1 * offset.minutes;
		} else if (toFrom == "to"){
			delta.ho = offset.hours;
			delta.mi = offset.minutes;
		} else {
			delta.ho = -1 * ob.tz_ho;
			delta.mi = -1 * ob.tz_mi;
		}
	
		
		// no change, man!
		if (delta.ho == 0 && delta.mi ==0) {
			return ob; 
		}	
		
		// decimal overage or underage after adding offset
		var ho_delta = (ob.ho + (ob.mi / 60)) + ((-1 * delta.ho) + ((delta.mi * -1) / 60));
				
		// FWD OR BACK ?
		if (ho_delta < 0) {
			// go back a day
			nh_dec = 24 + ho_delta;
		
			if (ob.da > 1) {
				ob.da = ob.da - 1;
			} else { 
				// day is 1....
				if (ob.mo == 1) {
					// & month is JAN, go back to DEC
					ob.ye = ob.ye - 1; ob.mo = 12; ob.da = 31;
				} else { 
					ob.mo = ob.mo-1;
					// now that we know month, what is the last day number?
					ob.da = TG_Date.getLastDayOfMonth(ob.ye, ob.mo)
				}
			}
			
		} else if (ho_delta >= 24) {
			// going fwd a day
			nh_dec = ho_delta - 24;			

			if (TG_Date.isLeapYear(ob.ye) && ob.mo == 2 && ob.da==28){
				ob.da = 29;
			} else if (ob.da == lastDays[ob.mo]) {
				if (ob.mo == 12) {
					ob.ye = ob.ye + 1;
					ob.mo = 1;
				} else {
					ob.mo = ob.mo + 1;
				}
				ob.da = 1;
			} else {
				ob.da = ob.da + 1;
			}

		} else {
			nh_dec = ho_delta;
		}
		// delta did not take us from one day to another
		// only adjust the hour and minute
		var hm = deltaFloatToHM(nh_dec);
			ob.ho = hm.ho;
			ob.mi = hm.mi; 
			
		if (!offset) {
			ob.tz_ho = 0;
			ob.tz_mi = 0;
		} else {
			ob.tz_ho = offset.tz_ho;
			ob.tz_mi = offset.tz_mi;
		}
		
				
		////// 
		// return ob;
		var retob = {ye:ob.ye, mo:ob.mo, da:ob.da, ho:ob.ho, mi:ob.mi, se:ob.se};
		
		return retob;
		
		
	}; // toFromUTC
	
	
	/*
	 * TGSecToUnixSec
	 * translates Timeglider seconds to unix-usable
	 * SECONDS. Multiply by 1000 to get unix milliseconds
	 * for JS dates, etc.
	 *
	 * @return {Number} SECONDS (not milliseconds)
	 *
	 */
	TG_Date.TGSecToUnixSec = function(tg_sec) {
		// 62135686740
		return tg_sec - (62135686740 - 24867);
	};
	
	
	TG_Date.JSDateToISODateString = function (d){  
  		var pad = function(n){return n<10 ? '0'+n : n}  
  		return d.getUTCFullYear()+'-'  
	      + pad(d.getUTCMonth()+1)+'-'  
	      + pad(d.getUTCDate())+' '  
	      + pad(d.getUTCHours())+':'  
	      + pad(d.getUTCMinutes())+':'  
	      + pad(d.getUTCSeconds());  
	};
	
	
	
	TG_Date.timezones = [
	    {"offset": "-12:00", "name": "Int'l Date Line West"},
	    {"offset": "-11:00", "name": "Bering & Nome"},
	    {"offset": "-10:00", "name": "Alaska-Hawaii Standard Time"},
	    {"offset": "-10:00", "name": "U.S. Hawaiian Standard Time"},
	    {"offset": "-10:00", "name": "U.S. Central Alaska Time"},
	    {"offset": "-09:00", "name": "U.S. Yukon Standard Time"},
	    {"offset": "-08:00", "name": "U.S. Pacific Standard Time"},
	    {"offset": "-07:00", "name": "U.S. Mountain Standard Time"},
	    {"offset": "-07:00", "name": "U.S. Pacific Daylight Time"},
	    {"offset": "-06:00", "name": "U.S. Central Standard Time"},
	    {"offset": "-06:00", "name": "U.S. Mountain Daylight Time"},
	    {"offset": "-05:00", "name": "U.S. Eastern Standard Time"},
	    {"offset": "-05:00", "name": "U.S. Central Daylight Time"},
	    {"offset": "-04:00", "name": "U.S. Atlantic Standard Time"},
	    {"offset": "-04:00", "name": "U.S. Eastern Daylight Time"},
	    {"offset": "-03:30", "name": "Newfoundland Standard Time"},
	    {"offset": "-03:00", "name": "Brazil Standard Time"},
	    {"offset": "-03:00", "name": "Atlantic Daylight Time"},
	    {"offset": "-03:00", "name": "Greenland Standard Time"},
	    {"offset": "-02:00", "name": "Azores Time"},
	    {"offset": "-01:00", "name": "West Africa Time"},
	    {"offset": "00:00", "name": "Greenwich Mean Time/UTC"},
	    {"offset": "00:00", "name": "Western European Time"},
	    {"offset": "01:00", "name": "Central European Time"},
	    {"offset": "01:00", "name": "Middle European Time"},
	    {"offset": "01:00", "name": "British Summer Time"},
	    {"offset": "01:00", "name": "Middle European Winter Time"},
	    {"offset": "01:00", "name": "Swedish Winter Time"},
	    {"offset": "01:00", "name": "French Winter Time"},
	    {"offset": "02:00", "name": "Eastean EU"},
	    {"offset": "02:00", "name": "USSR-zone1"},
	    {"offset": "02:00", "name": "Middle European Summer Time"},
	    {"offset": "02:00", "name": "French Summer Time"},
	    {"offset": "03:00", "name": "Baghdad Time"},
	    {"offset": "03:00", "name": "USSR-zone2"},
	    {"offset": "03:30", "name": "Iran"},
	    {"offset": "04:00", "name": "USSR-zone3"},
	    {"offset": "05:00", "name": "USSR-zone4"},
	    {"offset": "05:30", "name": "Indian Standard Time"},
	    {"offset": "06:00", "name": "USSR-zone5"},
	    {"offset": "06:30", "name": "North Sumatra Time"},
	    {"offset": "07:00", "name": "USSR-zone6"},
	    {"offset": "07:00", "name": "West Australian Standard Time"},
	    {"offset": "07:30", "name": "Java"},
	    {"offset": "08:00", "name": "China & Hong Kong"},
	    {"offset": "08:00", "name": "USSR-zone7"},
	    {"offset": "08:00", "name": "West Australian Daylight Time"},
	    {"offset": "09:00", "name": "Japan"},
	    {"offset": "09:00", "name": "Korea"},
	    {"offset": "09:00", "name": "USSR-zone8"},
	    {"offset": "09:30", "name": "South Australian Standard Time"},
	    {"offset": "09:30", "name": "Central Australian Standard Time"},
	    {"offset": "10:00", "name": "Guam Standard Time"},
	    {"offset": "10:00", "name": "USSR-zone9"},
	    {"offset": "10:00", "name": "East Australian Standard Time"},
	    {"offset": "10:30", "name": "Central Australian Daylight Time"},
	    {"offset": "10:30", "name": "South Australian Daylight Time"},
	    {"offset": "11:00", "name": "USSR-zone10"},
	    {"offset": "11:00", "name": "East Australian Daylight Time"},
	    {"offset": "12:00", "name": "New Zealand Standard Time"},
	    {"offset": "12:00", "name": "Int'l Date Line East"},
	    {"offset": "13:00", "name": "New Zealand Daylight Time"}
	];



        /*
        * boil
        * basic wrapper for parseInt to clean leading zeros,
        * as in dates
        */
      	function boil (n) {
      		return parseInt(n, 10);
      	}; TG_Date.boil = boil;
      	
      	function unboil (n) {
      		var no = parseInt(n, 10);
      		if (no > 9 || no < 0) {
      			return String(n);
      		} else {
      			return "0" + no;
      		}
      	}; TG_Date.unboil = unboil;


      	function getSec (fd) {
      		      		
      		var daSec = Math.abs(fd.rd) * 86400;
      		var hoSec = (fd.ho) * 3600;
      		var miSec = (fd.mi - 1) * 60;
      		var bc = (fd.rd > 0) ? 1 : -1;
      		var ret = bc * (daSec + hoSec + miSec);
      		
      		return ret;
      	};


  
        /* getMoNum
        *
        * @param mo {Number} month from 1 to 12
        * @param ye {Number} straight year
        *
        */ 
        function getMoNum (ob) {
        	    if (ob.ye > 0) {
        			return  ((ob.ye -1) * 12) + ob.mo;
        		} else {
        			return getMoNumBC(ob.mo, ob.ye);
        		}
        };




        /*
        * getMoNumBC
        * In BC time, serial numbers for months are going backward
        * starting with December of 1 bce. So, a month that is actually
        * "month 12 of year -1" is actually just -1, and November of 
        * year 1 bce is -2. Capiche!?
        *
        * @param {object} ob ---> .ye (year)  .mo (month)
        * @return {number} serial month number (negative in this case)
        */
        function getMoNumBC (mo, ye) {
        	var absYe = Math.abs(ye);
        	var n = ((absYe - 1) * 12) + (12-(mo -1));
        	return -1 * n;
        };
        


		function show(ob){
			return ob.ye + "-" + ob.mo + "-" + ob.da + " " + ob.ho + ":" + ob.mi;
		}
		

  
})(timeglider);