// date and time subroutines

// Constants:
var msecperday = 86400000;
var secperday = 86400;
var msecperhour = 3600000;
var secperhour = 3600;
var msecpermin = 60000;
var msecpersec = 1000;
var minperhour = 60;
var secpermin = 60;

// sum of days in 4-year span = 1461
// using 19700101000000 as time = 0
// previous leap is 19680229000000 = 365+366-31-28 = 672 days
// next leap is 19720229000000 = 365+365+31+28 = 789 days
// using 20000101000000 as time = 0
// previous leap is 19960229000000 = 365+365+365+366-31-28 = 1402 days
// next leap is 20000229000000 = 31+28 = 59 days
// assumes 32-bit Unix time convention...


function MyDate(datespec)
{ // assumes date is based on 19700101000000 as time = 0
  var p_date;
  var days, leaps, years, monthsize;

  var d = new Date();
  if (datespec==null)
    p_date = d.getTime();
  else
    p_date = Date.parse(datespec);
  p_date -= d.getTimezoneOffset()*msecpermin;

  this.seconds = Math.floor(p_date/msecpersec);
  this.hour = Math.floor((this.seconds%secperday)/secperhour);
  //this.min = Math.floor(((this.seconds%secperday)%secperhour)/secpermin);
  this.min = Math.floor((this.seconds%secperhour)/secpermin);
  //this.sec = ((this.seconds%secperday))%minperhour)%secpermin;
  this.sec = this.seconds%secpermin;

  days = Math.floor(this.seconds/secperday);
//alert(this.seconds+"\n"+this.seconds/86400+" vs "+days+"\n"+(this.seconds%86400)/3600+" vs "+this.hour+"\n"+((this.seconds)%3600)/60+" vs "+this.min+"\n"+(((this.seconds%86400)%3600)%60)+" vs "+this.sec);

  if (days*secperday>this.seconds)
    days--;
  this.dow = (4+days)%7;
  if (this.dow<0)
    this.dow += 7;
  if (this.seconds<0)
    leaps = Math.floor((days-789)/1461);
  else
    leaps = Math.floor((days+672)/1461);
  years = Math.floor((days-leaps)/365);
  if (years*365+leaps>days)
    years--;
  this.year = 1970+years;
  if (years<0)
    leaps = -((2-years)>>2);
  else
    leaps = ((years+1)>>2);
  this.doy = days-years*365-leaps;
  if ((this.year&0x3)==0 && ((this.year%100)!=0 || (this.year%400)==0))
    this.mm = 0xeefbb7;
  else
    this.mm = 0xeefbb3;
  this.dom = this.doy;
  this.month = 0;
  monthsize = 28+((this.mm>>(this.month<<1))&3);
  while (this.dom>=monthsize)
  {
    this.dom -= monthsize;
    this.month++;
    monthsize = 28+((this.mm>>(this.month<<1))&3);
  }
  this.month++;
  this.dom++;
} // MyDate constructor

function MyDate2String()
{
  var datestr = this.seconds+" seconds\n"+
this.year+"."+this.month+"."+this.dom+"."+this.hour+":"+this.min+":"+this.sec+
"\nday: "+this.dow+" doy: "+(this.doy+1)+"\n"+((this.mm==0xeefbb7)?"leap":"non-leap")+"\n";
  return datestr;
} // end MyDate2String
MyDate.prototype.toString = MyDate2String;

function MyDateDiff(p_mydate0, p_mydate1)
{
  // years as a floating-point value (not worth the effort)
  // years as years:months
  // years as years:days
  // months as a floating-point value
  // months as months:days
  // weeks as a floating-point value
  // weeks as weeks:days
  // days as a floating-point value
  // days as days:hours:minutes:seconds
  // seconds

  var left, Cmonthtime, Bmonthtime, month, monthsize, monthtime, totaltime;
  var D = new Array();

  // seconds
  this.seconds = p_mydate1.seconds-p_mydate0.seconds;
  if (this.seconds<0)
  {
    this.seconds = -this.seconds;
    D[0] = p_mydate1;
    D[1] = p_mydate0;
  }
  else
  {
    D[0] = p_mydate0;
    D[1] = p_mydate1;
  }

  // days as a floating-point value
  this.f_days = this.seconds/secperday;
  
  // days as days:hours:minutes:seconds
  this.days = Math.floor(this.f_days);
  left = this.seconds-this.days*secperday;
  this.hour = Math.floor(left/secperhour);
  this.min = Math.floor((left%secperhour)/secpermin);
  this.sec = left%secpermin;

  // months as a floating-point value
  this.years = D[1].year-D[0].year;
  this.months = D[1].month-D[0].month;
  if (this.months<0)
  {
    this.months += 12;
    this.years--;
  }
  Cmonthtime = ((D[1].dom*24+D[1].hour)*60+D[1].min)*60+D[1].sec;
  Bmonthtime = ((D[0].dom*24+D[0].hour)*60+D[0].min)*60+D[0].sec;
  if (Cmonthtime<Bmonthtime)
  {
    this.months--;
    if (this.months<0)
    {
      this.months += 12;
      this.years--;
    }
    month = D[1].month-2; // D[1].month in [1,12]
    if (month<0) month = 11;
    monthsize = 28+((D[1].mm>>(month<<1))&3);
    monthtime = monthsize*secperday;
    totaltime = monthtime-Bmonthtime+Cmonthtime;
  }
  else
  {
    month = D[1].month-1; // D[1].month in [1,12]
    monthsize = 28+((D[1].mm>>(month<<1))&3);
    monthtime = monthsize*secperday;
    totaltime = Cmonthtime-Bmonthtime;
  }
  this.totalmonths = this.months+this.years*12; // total age in months
  this.dom = Math.floor(totaltime/secperday);

// floating-point months: seconds per month: depends on the month (28,29,30,31)
// seconds into this month/seconds in this month
this.f_month = totaltime/monthtime;
// floating-point years: seconds per year: depends on the year (365 vs 366)
// seconds into this year/seconds in this year

  this.f_weeks = this.f_days/7;
  this.weeks = Math.floor(this.days/7);
  this.dow = this.days%7;

//  {
//    // next leap after start date
//    if (D[0].mm==0xeefbb7)
//      diy = 366;
//    else
//      diy = 365;
//    leapyear = Math.floor((D[0].year+3)/4)*4;
//    if (D[0].year==leapyear)
//    {
//      if (D[0].month>2) // what about month==2 && dom==29
//        leapyear+=4;
//    }
//    if (leapyear-D[0].year>0)
//      daystoleap = diy-D[0].doy+59+365*(leapyear-D[0].year-1);
//    else
//      daystoleap = 59-D[0].doy;
//
//    daysfromleap = 1461-daystoleap;
//    this.leaps = Math.floor((this.days+daysfromleap)/1461);
//    years = Math.floor((this.days-this.leaps)/365);
//    this.newdoy = this.days-years*365-this.leaps;
//// if last leap falls within last year of difference, newdoy is off by 1
//    if (D[1].mm==0xeefbb7)
//      diy = 366;
//    else
//      diy = 365;
//    leapyear = Math.floor((D[1].year+3)/4)*4;
//    if (D[1].year==leapyear)
//    {
//      if (D[1].month>2) // what about month==2 && dom==29
//        leapyear+=4;
//    }
//    if (leapyear-D[1].year>0)
//      daystoleap = diy-D[1].doy+59+365*(leapyear-D[1].year-1);
//    else
//      daystoleap = 59-D[1].doy;
//daysfromleap = 1461-daystoleap;
//if (daystoleap==0 || daysfromleap<this.days)
//{ // not quite...
//  this.newdoy++;
//}
//  }
// calculate floating-point year from month fraction instead:
// fraction of year = month fraction/12
// an approximation without having to recalculate and account for leap days

} // end MyDateDiff constructor

function MyDateDiff2String(index, header, footer)
{
var str;
var hms = ("00"+this.hour).slice(-2)+":"+("00"+this.min).slice(-2)+":"+("00"+this.sec).slice(-2);
var ystr = ("0000"+this.years).slice(-4);
var moystr = ("00"+this.months).slice(-2);
var domstr = ("00"+this.dom).slice(-2);
var fdstr = ((this.f_days-this.days)+"0").slice(1,8);
var fmstr = (this.f_month+"0").slice(1,8);
var tmstr = ("0000"+this.totalmonths).slice(-4);
var wstr = ("0000"+this.weeks).slice(-4);
var dowstr = ("00"+this.dow).slice(-2);
var fwstr = ((this.f_weeks-this.weeks)+"0").slice(1,8);
var dstr = ("00000"+this.days).slice(-5);
switch (index)
{
  case 99:
    header = footer = false;
    str = this.sec+"\xa0second"+(this.sec!=1?"s":"");
    if (this.min>0)
      str = this.min+"\xa0minute"+(this.min!=1?"s ":" ")+str;
    if (this.hour>0)
      str = this.hour+"\xa0hour"+(this.hour!=1?"s ":" ")+str;
    if (this.dom>0)
      str = this.dom+"\xa0day"+(this.dom!=1?"s ":" ")+str;
    if (this.months>0)
      str = this.months+"\xa0month"+(this.months!=1?"s ":" ")+str;
    if (this.years>0)
      str = this.years+"\xa0year"+(this.years!=1?"s ":" ")+str;
    break;
  default:
  case 0:
// options:
// years:months:days:hours:minutes:seconds
// -> this.years this.moy this.dom this.hour this.min this.sec
    hdr = "yyyy:mm:dd:hh:mm:ss";
    str = ystr+":"+moystr+":"+domstr+":"+hms;
    break;
  case 1:
// years:months:floating-point days
// -> this.years this.months this.dom+this.f_days-this.days
    hdr = "yyyy:mm:dd.dddddd";
    str = ystr+":"+moystr+":"+domstr+fdstr;
    break;
  case 2:
// years:floating-point months
// -> this.years this.months+this.f_month
    hdr = "yyyy:mm.mmmmmm";
    str = ystr+":"+moystr+fmstr;
    break;
  case 3:
// months:days:hours:minutes:seconds
// -> this.totalmonths this.dom this.hour this.min this.sec
    hdr = "mmmm:dd:hh:mm:ss";
    str = tmstr+":"+domstr+":"+hms;
    break;
  case 4:
// months:floating-point days
// -> this.totalmonths this.dom+this.f_days-this.days
    hdr = "mmmm:dd.dddddd";
    str = tmstr+":"+domstr+fdstr;
    break;
  case 5:
// floating-point months
// -> this.totalmonths+this.f_month
    hdr = "mmmm.mmmmmm";
    str = tmstr+fmstr;
    break;
  case 6:
// weeks:days:hours:minutes:seconds
// -> this.weeks this.dow this.hour this.min this.sec
    hdr = "wwww:dd:hh:mm:ss";
    str = wstr+":"+dowstr+":"+hms;
    break;
  case 7:
// weeks:floating-point days
// -> this.weeks this.dow+this.f_days-this.days
    hdr = "wwww:dd.dddddd";
    str = wstr+":"+dowstr+fdstr;
    break;
  case 8:
// floating-point weeks
// -> this.f_weeks
    hdr = "wwww.wwwwww";
    str = wstr+fwstr;
    break;
  case 9:
// days:hours:minutes:seconds
// -> this.days this.hour this.min this.sec
    hdr = "ddddd:hh:mm:ss";
    str = dstr+":"+hms;
    break;
  case 10:
// floating-point days
// -> this.f_days
    hdr = "ddddd.dddddd";
    str = dstr+fdstr;
    break;
  case 11:
// seconds
// -> this.seconds
    hdr = "seconds";
    str = this.seconds;
    break;
}
//var str = "seconds: "+this.seconds+"\n"+
//"years:months:days "+this.years+":"+this.months+":"+this.dom+"\n"+
//"months: "+this.totalmonths+"\n"+
//"days:hours:min:sec: "+this.days+":"+hms+"\n"+
//"f days: "+this.f_days +"\n"+
//"f weeks: "+this.f_weeks+" weeks:days: "+this.weeks+":"+this.dow+"\n"+
//"f month: "+this.f_month+"\n";
//"newdoy: "+this.newdoy+" leaps: "+this.leaps+"\n";
if (header)
  str = hdr+"\n"+str;
if (footer)
  str = str+"\n"+hdr;
return str;
} // end MyDateDiff2String
MyDateDiff.prototype.toString = MyDateDiff2String;

// Usage:
// now = new MyDate(); uses the current date and time
// that = new MyDate(<date specification>);
// diff = new MyDateDiff(now, that);
