
//  -*-mode: Java; coding: latin-1;-*-
// Time-stamp: "2005-05-22 23:20:21 ADT"

/*
  TimeDuration -- express time intervals in concise English.
    sburke@cpan.org
  Based on my Perl module Time::Duration.

  Usage:


   new TimeDuration(1234567).exact().ago()
    => "14 days, 6 hours, 56 minutes, and 7 seconds ago"

   new TimeDuration(1234567).ago()
    => "14 days and 7 hours ago"

   or you can feed it milliseconds:
    new TimeDuration().milliseconds(12345).ago()
       == the same as new TimeDuration(12).ago()

*/


function TimeDuration (seconds) {  this._exact = false; this.s = seconds;  }

(function (_) { // Class-block...

_.seconds      = function ( s) { this.s = s; return this; };
_.milliseconds = function (ms) { this.s = Math.round(ms/1000); return this; };

_.exact   =function () { this._exact = true;   return this; };
_.inexact =function () { this._exact = false;  return this; };

_.later   =function(p){return this._say(p,' earlier',' later','right then')};
_.earlier =function(p){return this._say(p,' later',' earlier','right then')};
_.ago     =function(p){return this._say(p,' from now',  ' ago','right now')};
_.fromNow =function(p){return this._say(p,' ago',  ' from now','right now')};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

_.duration = function (precision) {
  precision = Math.floor(precision || 0) || 2;  // precision (default: 2)
  if(!this.s) return '0 seconds';
  var odometer = this._make_odometer( Math.abs(this.s));
  if(!this._exact) odometer = this._approximate(precision, odometer);
  return this._render('', odometer);
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

_._say = function (precision, english_neg, english_pos, english_zero) {
  // Phrase an interval with the appropriate English wrapper
  precision = Math.floor(precision || 0) || 2;  // default to 2
  if(this.s == 0) return english_zero;
  var odometer = this._make_odometer( this.s );
  if(!this._exact) odometer = this._approximate(precision, odometer);
  var direction = (this.s <= -1) ? english_neg : english_pos;
  return this._render(direction, odometer);
};


/*
 *   The actual figuring is below here
 */


var _WheelStruct = function (u,c,l) { // Yup, an inner class!
  this.units = u; this.count = c; this.limit = l;
};

_._int = function (s) { // a function
  return(
    (isNaN(s) || !isFinite(s)) ? undefined
    : (s == 0) ? 0
    : (s >  0) ? Math.floor(s)
    : (s <  0) ? Math.ceil( s)
    :            undefined
    );
};


_._make_odometer = function () {
  // Breakdown of seconds into units, starting with the most significant

  //alert("separate on " + s + "!");

  var remainder = Math.abs( this.s ); 
  var x; // scratch
  var odometer = []; // retval
  
  // Years:
  x = this._int(remainder / (365 * 24 * 60 * 60));
  odometer.push( new _WheelStruct('year', x, 1000000000 ) );
   // that bignum is just a filler, never used
  remainder -= x * (365 * 24 * 60 * 60);
    
  // Days:
  x = this._int(remainder / (24 * 60 * 60));
  odometer.push( new _WheelStruct('day', x, 365 ));
  remainder -= x * (24 * 60 * 60);
    
  // Hours:
  x = this._int(remainder / (60 * 60));
  odometer.push( new _WheelStruct('hour', x, 24 ));
  remainder -= x * (60 * 60);
  
  // Minutes:
  x = this._int(remainder / 60);
  odometer.push( new _WheelStruct('minute', x, 60 ));
  remainder -= x * 60;
  
  odometer.push( new _WheelStruct('second', this._int(remainder), 600));

  return odometer;
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
_._approximate = function (precision, odo) {
  // Now nudge the wheels into an acceptably (im)precise configuration

  var nonzero_count, improperly_expressed;

 Fixing:
  while(1) {
    // Constraints for leaving this block:
    //  1) number of nonzero wheels must be <= precision
    //  2) no wheels can be improperly expressed (like having "60" for mins)
  
    nonzero_count = 0;
    improperly_expressed = null;

    //trace("Odo: ", odo.toSource(), "\n");

    for(var i = 0; i < odo.length; i++) {
      var thiswheel = odo[i];
      if(thiswheel.count == 0) continue;  // Zeros require no attention.
      ++nonzero_count;

      if(i==0) continue; // The years wheel is never improper or over any limit; skip
      
      if(nonzero_count > precision) { // This is one nonzero wheel too many!

        // If we're big enough, increment the previous wheel:
        if(thiswheel.count >= (thiswheel.limit / 2)) odo[i-1].count++;

        // Reset this and subsequent wheels to 0:
        for(var j = i; j < odo.length; j++) { odo[j].count = 0 }
        continue Fixing; // Start over.

      } else if( thiswheel.count >= thiswheel.limit) {
        // It's an improperly expressed wheel.  (Like "60" on the mins wheel)
        improperly_expressed = i;
      }
    }
    if(improperly_expressed != null) {
      // Only fix the least-significant improperly expressed wheel (at a time).
      odo[ improperly_expressed - 1 ].count++;
      odo[ improperly_expressed     ].count = 0;
      // We never have a "150" in the minutes slot -- if it's improper,
      //  it's only by having been rounded up to the limit.
      continue Fixing // start over;
    }
    
    // Otherwise there's not too many nonzero wheels, and there's no
    //  improperly expressed wheels, so fall thru...
    break Fixing;
  }

  return odo;
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
_._render = function (addendum,odo) {  // Render this odometer to English

  for(var i = 0; i < odo.length; i++) {
    var w = odo[i];

    if( w.count == 0 ) {
      odo.splice(i,1); // nix this element.
      i--;
      continue;
    }

    odo[i] = (w.count == 1)
      ? (w.count.toString() + " " + w.units      ) // singular
      : (w.count.toString() + " " + w.units + "s") // plural
    ;
  }

  if(!odo.length) return "just now";  // sanity
  odo[ odo.length - 1 ] += addendum;

  // Now return the readout with an appropriate conjunction
  if(odo.length == 1) return odo[0];
  if(odo.length == 2) return odo[0] + " and " + odo[1];

  odo[ odo.length - 1 ] = "and " + odo[ odo.length - 1 ];
  return odo.join( ", " );
};

//--------------------------------------------------------------------------

return; })(   TimeDuration.prototype   );  // End of class-block

// End of TimeDuration


//	#	#	#	#	#	#	#	#	#

document.write("<p class='up'><a href='./'>[Intro]</a><br><a " +
 " href='http://interglacial.com/rss/hoj.rss'>[RSS]</a></p>"
);

if( document.lastModified ) {
  var mtime =    new Date( Date.parse(document.lastModified) );

  var when;
  if(mtime) {
    when =  " about " + (
      new TimeDuration().milliseconds(
	new Date().getTime() - mtime.getTime() // age
    ).ago()) + " (at " + mtime.toLocaleString() + ")";
  } else {
    when = " at " +  document.lastModified;
  }

  document.write(
   "<p class='lastmod'>This document was last updated " +
   when +  ".</p>"
  );
}
