
    // $Id: countdowntimer.module,v 1.8.2.22.2.4.2.17 2008/05/21 17:04:35 jvandervort Exp $
    if (Drupal.jsEnabled) {
      $(document).ready(countdown_auto_attach);
    }

    var formats = ['<em>(%dow% %moy%%day%)</em><br/>%days% days + %hours%:%mins%:%secs%','Only %days% days, %hours% hours, %mins% minutes and %secs% seconds left', '%days% shopping days left', '<em>(%dow% %moy%%day%)</em><br/>%days% days + %hours%:%mins%:%secs%'];

    function jstimer(timer_element) {
      /* defaults */
      var d = {dir:"down",format_num:0, format_txt:"", timer_complete:new String('<em>Timer Completed</em>'), highlight:new String('style="color:red"').split(/=/), threshold:new Number('5')};
      /* jstimer.properties */
      this.element = timer_element;
      this.d = d;
      /* jstimer.methods */
      this.parse_microformat = parse_microformat;
      this.update = update_timer;

      /* bootstrap, parse microformat, load object */
      try {
        this.parse_microformat();
      }
      catch(e) {
        alert(e.message);
        alert($(timer_element).html());
        this.parse_microformat_success = 0;
        return;
      }

      if ( d.format_txt != "" ) {
          this.outformat = d.format_txt;
      } else {
        this.outformat = formats[d.format_num];
      }

      // replace the static stuff in the format string
      // this only needs to be done once, so here is a good spot.
      this.outformat = this.outformat.replace(/%day%/,   this.to_date.getDate());
      this.outformat = this.outformat.replace(/%month%/, this.to_date.getMonth() + 1);
      this.outformat = this.outformat.replace(/%year%/,  this.to_date.getFullYear());
      this.outformat = this.outformat.replace(/%moy%/,   this.to_date.get_moy());
      this.outformat = this.outformat.replace(/%dow%/,   this.to_date.get_dow());
    }

    function parse_microformat() {
      var strdate = $(this.element).children("span[@class=datetime]").html();
      var str_current_server_time = $(this.element).children("span[@class=current_server_time]").html();

      if ( String(strdate) == 'null' ) {
        this.parse_microformat_success = 0;
        throw new Object({name:"NoDate",message:"CountdownTimer: Span with class=datetime not found within the timer span."});
      }

      var date = new Date();
      try {
        date.set_iso8601_date(strdate);
      }
      catch(e) {
        throw(e);
      }
      this.to_date = date;

      if ( String(str_current_server_time) != 'null' ) {
        // this is a feedback time from the server to correct for small server-client time differences.
        // not used for normal block and node timers.
        var date_server = new Date();
        date_server.set_iso8601_date(str_current_server_time);
        var date_client = new Date();
        var adj = date_client.getTime() - date_server.getTime();
        // adjust target date to clients domain
        this.to_date.setTime(this.to_date.getTime() + adj); 
      }

      this.d.dir = $(this.element).children("span[@class=dir]").html() || this.d.dir;
      this.d.format_num = $(this.element).children("span[@class=format_num]").html() || this.d.format_num;
      this.d.format_txt = $(this.element).children("span[@class=format_txt]").html() || "";
      if ( String(this.d.format_txt).match("'") ) {
        this.d.format_txt = "<span style=\"color:red;\">Format may not contain single quotes(').</span>";
      }
      this.d.threshold = $(this.element).children("span[@class=threshold]").html() || this.d.threshold;
      this.d.timer_complete = $(this.element).children("span[@class=complete]").html() || this.d.timer_complete;
      this.d.tc_redir = $(this.element).children("span[@class=tc_redir]").html() || '';
      this.d.tc_msg = $(this.element).children("span[@class=tc_msg]").html() || '';

      this.parse_microformat_success = 1;
    }

    // update_timer: returns false if the timer is done.
    function update_timer() {
      var now_date = new Date();
      var diff_secs;
      if ( this.d.dir == "down" ) {
        diff_secs = Math.floor((this.to_date.getTime() - now_date.getTime()) / 1000);
      } else {
        diff_secs = Math.floor((now_date.getTime() - this.to_date.getTime()) / 1000);
      }

      if ( this.d.dir == "down" && diff_secs < 0 ) {
        /* timer complete */
        $(this.element).html(this.d.timer_complete.valueOf());
        
        if ( this.d.tc_msg != '' && diff_secs > -3 ) {
          alert(this.d.tc_msg);
          if ( this.d.tc_redir != '' ) {
            window.location = this.d.tc_redir;
          }
        } else if ( this.d.tc_redir != '' && diff_secs > -3) {
          window.location = this.d.tc_redir;
        }
        
        return false;
      } else {
        /* timer still counting */
        var years = Math.floor(diff_secs / 60 / 60 / 24 / 365.25);
        var days = Math.floor(diff_secs / 60 / 60 / 24);
        var ydays = Math.ceil(days - (years * 365.25));
        var hours = Math.floor(diff_secs / 60 / 60) - (days * 24);
        var minutes = Math.floor(diff_secs / 60) - (hours * 60) - (days * 24 * 60);
        var seconds = diff_secs - (minutes * 60) - (hours * 60 * 60) - (days * 24 * 60 * 60);

        var outhtml = new String(this.outformat);
        outhtml = outhtml.replace(/%years%/,years);
        outhtml = outhtml.replace(/%ydays%/,ydays);
        outhtml = outhtml.replace(/%days%/,days);
        outhtml = outhtml.replace(/%hours%/,LZ(hours));
        outhtml = outhtml.replace(/%mins%/,LZ(minutes));
        outhtml = outhtml.replace(/%secs%/,LZ(seconds));

        if ( days == 1 ) {
          outhtml = outhtml.replace(/days/,'day'); //kludge, find a better way
        }
        if ( years == 1 ) {
          outhtml = outhtml.replace(/years/,'year'); //kludge, find a better way
        }

        if ( this.d.dir == "down" && (diff_secs <= (this.d.threshold * 60)) ) {
          $(this.element).html('<span ' + this.d.highlight[0] + '=' + this.d.highlight[1] + '>' +  outhtml + '</span>');
        } else {
          $(this.element).html(outhtml);
        }

        return true;
      }
    }

    // clock functions
    function js_clock(_element) {
      this.element = _element;
      this.update = update_clock;
    }
    function update_clock() {
      var timenow = new Date();
      var h = timenow.getHours();
      var m = timenow.getMinutes();
      var s = timenow.getSeconds();
      if ( '0' == '0' ) {
        var am_pm = ""
        if ( h < 12 ) {
          am_pm = "am";
        } else {
          am_pm = "pm";
          h = h - 12;
        }
        $(this.element).html(h + ":" + LZ(m) + ":" + LZ(s) + am_pm);
      } else if ( '0' == '1' ) {
        $(this.element).html(h + ":" + LZ(m) + ":" + LZ(s));
      }
      return true;
    }


    // bootstrap and timing functions
    var running = 0;
    var timer_stack = new Array();
    function countdown_auto_attach() {
      $(".countdowntimer").each(
        function(i) {  // i is the position in the each fieldset
          var t = new jstimer(this,1);
          if ( t.parse_microformat_success == 1 ) {
            timer_stack[timer_stack.length] = t;
          }
          if ( running == 0 ) {
            running = 1;
            timer_loop();
          }
        }
      );
      $(".js-clock").each(
        function(i) {
          var t = new js_clock(this,1);
          timer_stack[timer_stack.length] = t;
          if ( running == 0 ) {
            running = 1;
            timer_loop();
          }
        }
      );
    }
    function timer_loop() {
      for (var i = timer_stack.length - 1; i >= 0; i--) {
        if ( timer_stack[i].update() == false ) {
          timer_stack.splice(i, 1);
        }
      }
      setTimeout('timer_loop()',999);
    }
    function LZ(x) {
      return (x >= 10 || x < 0 ? "" : "0") + x;
    }

    Date.prototype.set_iso8601_date = function (string) {
      var iso8601_re = /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/;
      var date_bits = iso8601_re.exec(string);
      var date_obj = null;
      if ( date_bits ) {
        date_bits.shift();
        date_bits[1] && date_bits[1]--; // normalize month
        date_bits[6] && (date_bits[6] *= 1000); // convert mils
        date_obj = new Date(date_bits[0]||1970, date_bits[1]||0, date_bits[2]||0, date_bits[3]||0, date_bits[4]||0, date_bits[5]||0, date_bits[6]||0);
        
        //timezone handling
        var zone_offset = 0;  // in minutes
        var zone_plus_minus = date_bits[7] && date_bits[7].charAt(0);
        // get offset from isostring time to Z time
        if ( zone_plus_minus != 'Z' ) {
          zone_offset = ((date_bits[8] || 0) * 60) + (Number(date_bits[9]) || 0);
          if ( zone_plus_minus != '-' ) {
            zone_offset *= -1;
          }
        }
        // convert offset to localtime offset, will include daylight savings
        if ( zone_plus_minus ) {
          zone_offset -= date_obj.getTimezoneOffset();
        }
        if ( zone_offset ) {
          date_obj.setTime(date_obj.getTime() + zone_offset * 60000);
        }
      }
      
      // set this object to current localtime representation
      try {
        this.setTime(date_obj.getTime());
      }
      catch(e) {
        throw new Object({name:"DatePatternFail",message:"CountdownTimer: Date does not have proper format (ISO8601, see readme.txt)."});
      }
    }
    Date.prototype.get_moy = function () {
      var myMonths=new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
      return myMonths[this.getMonth()];
    }
    Date.prototype.get_dow = function () {
      var myDays=["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
      return myDays[this.getDay()];
    }

