/** dh_calendar.js
 *
 * Javascript/CSS Calendar
 *
 * visit:
 * http://www.datahouse.ch
 *
 *
 * usage:
 * include css and this script like:
 * <link href="/PATH/dh_calendar.css" media="screen" rel="stylesheet" type="text/css" />
 * <script charset="utf-8" src="/PATH/dh_calendar.js" type="text/javascript" ></script>
 *
 * put following code at your calendar-link
 * onclick="display_dh_calendar(document.getElementById('YOUR_INPUT_ELEMENT'),'dd.mm.yyyy',this,'en')"
 *
 * and take a look at the setting_* variables
 *
 * supported languages:
 * - 'en' => English
 * - 'de' => German
 * - 'fr' => French
 * - 'it' => Italian
 *
 * tested formats:
 * - 'dd.mm.yyyy'
 * - 'yyyy-mm-dd'
 * - 'mm,dd,yy' // problems with yy and the year change in input field
 *
 * TODO:
 * - week starts on sunday support
 * - img support
 * - hours and min support
 *
 * @author:  philipp.schoenenberg@datahouse.ch
 * @date:    2009-01-10
 * @package: DH_IT_CAL
 * @version: 0.1a
 */

/**
 * setting to disable the even_handler
 * if you encounter problems with it
 * @var boolean (true/false)
 */
var setting_enable_event_handler = true;

/**
 * setting to show week numbers
 * @var boolean (true/false)
 */
var setting_show_week_number = true;

/**
 * setting to set the left arrow in navigation
 * @var string
 */
var setting_left_arrow = '&lsaquo;';

/**
 * setting to set the right arrow in navigation
 * @var boolean
 */
var setting_right_arrow = '&rsaquo;';

/**
 * setting for the up arrow in time
 * @var string
 */
var setting_arrow_up = '&uarr;';

/**
 * setting for the down arrow in time
 * @var string
 */
var setting_arrow_down = '&darr;';

var setting_ok_string = 'OK';

/**
 * setting to completely disable time
 * if given format has time an this setting
 * is set to false it ignores the time
 * @var boolean
 */
var setting_show_time = true;

/**
 * setting to set the minute offset.
 * any given minute will be round to
 * a value / setting_minute_offset
 * @var int
 */
var setting_minute_offset = 15;

/**
 * holds the object
 * @var Dh_language object
 */
var global_language = null;

/**
 * holds the object
 * @var Dh_calendar object
 */
var global_calendar = null;

/**
 * init our calendar
 * @param object inputfield
 * @param string date format
 * @param object element to open the calendar
 * @param string language
 */
function display_dh_calendar(input, format, pos, lang, left, top) {
	if (global_language == null) global_language = new Dh_language();
	if (global_calendar == null) global_calendar = new Dh_calendar();
    set_events();
    global_language.set_language(lang);
    global_calendar.set(input,format,pos,left,top);
}

/**
 * let the calendar check for new value, and reprint it
 */
function check_input() {
    global_calendar.set_input_reload();
}

/**
 * trys to set the new year
 */
function select_year(element) {
    var date = global_calendar.date_;
    if (
        date.check_year(element.value) &&
        date.set_all_date_values(
          element.value,
          date.get_month(),
          date.get_date()
        )
    ) {
        global_calendar.reload();
    }
}

/**
 * closes the calendar
 */
function close_calendar() {
    global_calendar.set_field(global_calendar.date_.toString());
    global_calendar.hide();
}

/**
 * select todays date
 * @param object selected element
 */
function select_today(element) {
    global_calendar.set_field(global_calendar.date_.actualToString());
    global_calendar.hide();
}

/**
 * navigate the months
 * @param object navigation element
 */
function navigate(element) {
    if (element.id == 'dh_cal_nav_prev_month') {
        global_calendar.date_.add_month(-1);
    } else if (element.id == 'dh_cal_nav_next_month') {
        global_calendar.date_.add_month(1);
    } else {
        // unknown element...
        return false;
    }
    global_calendar.reload();
}

/**
 * select specific day
 * @param object element that holds a day
 */
function select_day(element) {
    var value = element.innerHTML;
    var date  = global_calendar.date_;

    if (element.className == 'prev_month') {
        date.add_month(-1);
    } else if (element.className == 'next_month') {
        date.add_month(1);
    }
    date.set_all_date_values(date.get_year(),date.get_month(),value);
    global_calendar.set_field(date.toString());
}

/**
 * function to add or subtract a time
 * @param int hour
 * @param int min
 */
function add_time(hour, min) {
    if (min != false) {
        global_calendar.date_.time.add_min(min * setting_minute_offset);
    }
    if (hour != false) {
        global_calendar.date_.time.add_hour(hour);
    }
    global_calendar.reload();
}

/**
 * function to set the minutes
 * @param object input element
 */
function select_minute(element) {
    global_calendar.date_.time.set_min(element.value);
    global_calendar.reload();
}

/**
 * function to set the hour
 * @param object input element
 */
function select_hour(element) {
    global_calendar.date_.time.set_hour(element.value);
    global_calendar.reload();
}

/**
 * function to set an event_handler for specific Events
 */
function set_events() {
	if (setting_enable_event_handler) {
		var event_handler     = detect_event;
		document['onkeydown'] = detect_event;
	}
}

/**
 * function to unset the event_handler
 */
function unset_events() {
	if (setting_enable_event_handler) {
		var event_handler     = null;
		document['onkeydown'] = null;
	}
}

/**
 * function for key Events
 *
 * @param evt event object..
 * @return keyCode or false
 */
function detect_event(evt) {
    var e = (evt?evt:window.event);

    switch (e.keyCode) {
    case 9: // tab
        break;
    case 13: // enter
        select_year(document.getElementById('dh_cal_year_input'));
        return 9;
        break;
    default:
        break;
    }
    return e.keyCode;
}

/**
 * class that holds the language and translations
 */
function Dh_language() {
    this.code_          = null;
    this.week_          = null;
    this.today_         = null;
    this.a_day_         = null;
    this.a_month_       = null;
    this.a_month_short_ = null;
    this.time_          = null;
    /**
     * sets the correct language
     * @param string language code
     */
    this.init = function(code) {
        // if the right code already set skip
        if (this.code_ == code) return;
        // else set the language arrays
        switch (code) {
        case 'en': /* English */
            this.a_month_ = ['January','February','March','April','May','June','July','August','September','October','November','December'];
            this.a_month_short_ = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
            this.a_day_ = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
            this.week_  = 'Week';
            this.today_ = 'Today';
            this.time_  = 'Time';
            this.hour_  = 'h';
            this.min_   = 'min';
            break;
        case 'de': /* German */
        default:
            this.a_month_ = ['Januar','Februar','M&auml;rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
            this.a_month_short_ = ['Jan','Feb','Mar','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'];
            this.a_day_ = ['Mo','Di','Mi','Do','Fr','Sa','So'];
            this.week_  = 'Woche';
            this.today_ = 'Heute';
            this.time_  = 'Zeit';
            this.hour_  = 'Std.';
            this.min_   = 'Min.';
            break;
        case 'fr': /* French */
            this.a_month_ = ['Janvier','F&eacute;vrier','Mars','Avril','Mai','Juin','Juillet','Ao&ucirc;t','Septembre','Octobre','Novembre','D&eacute;cembre'];
            this.a_month_short_ = ['Jan','Fev','Mar','Avr','Mai','Jun','Jul','Aou','Sep','Oct','Nov','Dec'];
            this.a_day_ = ['Lun','Mar','Mer','Jeu','Ven','Sam','Dim'];
            this.week_  = 'Sem';
            this.today_ = "Aujourd'hui";
            this.time_  = 'Heure';
            this.hour_  = 'H';
            this.min_   = 'Min';
            break;
        case 'it': /* Italian */
            this.a_month_ = ['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno','Luglio','Agosto','Settembre','Ottobre','Novembre','Dicembre'];
            this.a_month_short_ = ['Gen','Feb','Mar','Apr','Mag','Giu','Lug','Ago','Set','Ott','Nov','Dic'];
            this.a_day_ = ['Lun','Mar','Mer','Gio','Ven','Sab','Dom'];
            this.week_  = 'Settimana';
            this.today_ = "Oggi";
            this.time_  = 'Tempo';
            this.hour_  = 'Ora';
            this.min_   = 'Min';
            break;
        }
        this.code_ = code;
    };
    /**
     * get the translation for a month 1 = jan... 12 = dec
     * @param int month
     * @return string month
     */
    this.get_month_translation = function(i) {
        return this.a_month_[--i];
    };
    /**
     * get the short translation for a month 1 = jan... 12 = dec
     * @param int month
     * @return string month
     */
    this.get_month_short_translation = function(i) {
        return this.a_month_short_[--i];
    };
    /**
     * get the day in week translation 1 = Mon... 6 = Sun
     * @param int day
     * @return string weekday
     */
    this.get_day_translation = function(i) {
        return this.a_day_[--i];
    };
    /**
     * get today translation
     * @return string today
     */
    this.get_today = function() {
        return this.today_;
    };
    /**
     * get time translation
     * @return string time
     */
    this.get_time = function() {
        return this.time_;
    };
    /**
     * function to get hour translation
     * @return string hour
     */
    this.get_hour = function() {
        return this.hour_;
    };
    /**
     * function to get minute translation
     * @return string minute
     */
    this.get_minute = function() {
        return this.min_;
    };
    /**
     * sets the language
     */
    this.set_language = function(code) {
        this.init(code);
    };
}

/**
 * class for time handling
 */
function Dh_time() {
    this.hour_ = 0;
    this.min_  = 0;

    /**
     * set the hour
     * @param int hour (0-23)
     * @return boolean if is set
     */
    this.set_hour = function(val) {
        val = parseInt(val);
        if (val >= 0 && val < 24) {
            this.hour_ = val;
            this.correct_time();
            return true;
        }
        return false;
    };
    /**
     * set the minutes
     * @param int minutes (0-59)
     * @return boolean if is set
     */
    this.set_min = function(val) {
        val = parseInt(val);
        if (val >= 0 && val < 60) {
            this.min_ = val;
            this.correct_time();
            return true;
        }
        return false;
    };
    /**
     * function to get the hour
     * @return int hour
     */
    this.get_hour = function() {
        return this.hour_;
    };
    /**
     * function to get the minutes
     * @return int minutes
     */
    this.get_min = function() {
        return this.min_;
    };
    /**
     * function to add or subtract minutes
     * @param int minutes
     */
    this.add_min = function(val) {
        this.min_ += val;
        this.correct_time();
    };
    /**
     * function to add or subtract hour
     * @param
     */
    this.add_hour = function(val) {
        this.hour_ += val;
        this.correct_time();
    };
    /**
     * helper function witch corrects the time that it fits the range..
     * commented out the wrap days around, cause its strange in usage..
     */
    this.correct_time = function() {
        this.min_ = this.round(this.min_);

        // to high minutes
        for (; this.min_ >= 60; this.min_ -= 60) ++this.hour_;
        // to low minutes
        for (; this.min_ < 0; this.min_ += 60) --this.hour_;
        // to high hour
        for (; this.hour_ >= 24; this.hour_ -= 24) {
            // global_calendar.date_.add_date_day(+1);
        }
        // to low hour
        for (; this.hour_ < 0; this.hour_ += 24) {
            // global_calendar.date_.add_date_day(-1);
        }
    };
    /**
     * helper function to round given value to setting_minute_offset
     * @param int value to round
     * @return int rounded value
     */
    this.round = function(value) {
        var t = value / setting_minute_offset;
        var d = Math.floor(t);
        if (t != d) {
            if (t - d > 0.5) {
                // round up
                return Math.ceil(t) * setting_minute_offset;
            } else {
                // round down
                return Math.floor(t) * setting_minute_offset;
            }
        } else {
            return value;
        }
    };
}

/**
 * class for Date handling
 */
function Dh_date() {
    this.time       = null;
    this.date_      = new Date();
    this.user_date_ = new Date();

    this.format_     = null;
    this.stored_day_ = this.date_.getDate();

    this.days_in_month_      = [31,28,31,30,31,30,31,31,30,31,30,31];
    this.days_in_month_leap_ = [31,29,31,30,31,30,31,31,30,31,30,31];

    /**
     * find out if year is a leap year
     * @param int year
     * @return boolean
     */
    this.is_leap_year = function(year) {
        if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) {
            return true;
        }
        return false;
    };
    /**
     * check if time is needed
     * @return boolean
     */
    this.time_isset = function() {
        if (this.time != null) return true;
        return false;
    }
    /**
     * how much days has month xy
     * @param int year
     * @param int month
     * @return int days in this month
     */
    this.get_days_in_month = function(year, month) {
        if (this.is_leap_year(year)) return this.days_in_month_leap_[month];
        return this.days_in_month_[month];
    };
    /**
     * sets a date format
     * @param string format
     */
    this.set_format = function(format) {
        var min  = format.search(/ii/);
        var hour = format.search(/hh/);
        if (setting_show_time && (min != -1 || hour != -1)) {
            this.time = new Dh_time();
        }
        this.format_ = format.toLowerCase();
    };
    /**
     * format a date corresponding format_
     * @param int year
     * @param int month
     * @param int day
     * @return string formated date
     */
    this.do_format = function(year, month, day) {
        var s    = this.format_;
        var min  = 0;
        var hour = 0;
        if (this.time != null) {
            min = this.time.get_min();
            hour = this.time.get_hour();
        }
        // remove leading 0
        day   /= 1;
        month /= 1;
        min   /= 1;
        hour  /= 1;
        // cast year to string
        year = String(year);

        // insert values formated..
        s = s.replace('dd',(day < 10?0 + day.toString():day));
        s = s.replace('d',day);
        s = s.replace('mm',(month < 10?0 + month.toString():month));
        s = s.replace('m',month);
        s = s.replace('yyyy',year);
        s = s.replace('yy',year.substr(2,2));
        s = s.replace('hh',(hour < 10? 0 + hour.toString():hour));
        s = s.replace('ii',(min < 10? 0 + min.toString():min));
        return s;
    };
    /**
     * helper to check if value could be a year
     * @param string year
     * @return boolean
     */
    this.check_year = function(value) {
        var year = parseInt(value);
        if (isNaN(year)) return false;

        return true;
    };
    /**
     * parses a date corresponding to format_ and sets it
     * @param string value
     * @return boolean ok or not..
     */
    this.parse_input = function(value) {
        var format = this.format_;
        var min    = null;
        var hour   = null;
        var day    = null;
        var month  = null;
        var year   = null;
        var t      = null;

        // given value needs at least 6 chars
        if (value.length < 6) return false;
        // and it needs to fit our regex
        if (value.search(/^[0-9]*?$/gi) != -1) return false;

        /* parse input values */
        if (format.search('d') != -1) {
            t   = format.indexOf('d');
            day = parseInt(value.substr(t,2),10);
        }
        if (format.search('m') != -1) {
            t     = format.indexOf('m');
            month = parseInt(value.substr(t,2),10);
        }
        if (format.search('yyyy') != -1) {
            t    = format.indexOf('yyyy');
            year = parseInt(value.substr(t,4),10);
        } else if (format.search('yy') != -1) {
            t    = format.indexOf('yy');
            year = parseInt(value.substr(t,2),10);
        }
        // values arent a number so skip parsing and dont set them
        if (isNaN(day) || isNaN(month) || isNaN(year)) {
            return false;
        }
        // We need to see if there are hours or minutes..
        if (this.time_isset()) {
            if (format.search('i') != -1) {
                t   = format.indexOf('i');
                min = parseInt(value.substr(t,2),10);
            }
            if (format.search('h') != -1) {
                t    = format.indexOf('h');
                hour = parseInt(value.substr(t,2),10);
            }
            if (!isNaN(min)) {
                this.time.set_min(min);
            }
            if (!isNaN(hour)) {
                this.time.set_hour(hour);
            }
        }
        this.stored_day_ = day;
        this.set_all_date_values(year,month-1,day);
        return true;
    };
    /**
     * actual day in week
     * 0 = monday... 6 = sunday
     * @return int day in week
     */
    this.get_actual_day = function() {
        var day = this.date_.getDay();

        // our week starts on monday
        if (day == 0) day = 6;
        else --day;

        return day;
    };
    /**
     * actual day in month
     * @return int day
     */
    this.get_actual_date = function() {
        return this.date_.getDate();
    };
    /**
     * actual month
     * 0 = jan... 12 = dec
     * @return int month
     */
    this.get_actual_month = function() {
        return this.date_.getMonth();
    };
    /**
     * actual year with 4 digits
     * @return int year
     */
    this.get_actual_year = function() {
        return this.date_.getFullYear();
    };
    /**
     * user selected day in month
     * @return int day
     */
    this.get_date = function() {
        return this.user_date_.getDate();
    };
    /**
     * user selected month
     * @return int month
     */
    this.get_month = function() {
        return this.user_date_.getMonth();
    };
    /**
     * user selected year 4 digits
     * @return int year
     */
    this.get_year = function() {
        return this.user_date_.getFullYear();
    };
    /**
     * user stored day in month
     * @return int day
     */
    this.get_stored_date = function() {
        return this.stored_day_;
    };
    /**
     * check if user year/month == actual year/month
     * @return boolean
     */
    this.is_this_month = function() {
        if (
            this.get_year() == this.get_actual_year() &&
            this.get_month() == this.get_actual_month()
        ) {
            return true;
        }
        return false;
    };
    /**
     * day of week
     * 0 = monday... 6 = sunday
     * @return int day
     */
    this.get_day = function() {
        var day = this.user_date_.getDay();

        // our week starts on monday
        if (day == 0) day = 6;
        else --day;

        return day;
    };
    /**
     * date object with first day in month
     * @return date
     */
    this.get_first_day_date = function() {
        var d = new Date(this.get_year(),this.get_month(),this.get_date());
        d.setDate(1);
        return d;
    };
    /**
     * sets the day in month
     * @param int day
     */
    this.set_date = function(day) {
        this.user_date_.setDate(day);
    };
    /**
     * sets the user selected date
     * @param date object
     */
    this.set_date_obj = function(date) {
        this.user_date_ = date;
    };
    /**
     * helper to add or subtract a month to user selected date
     * @param int how many months
     */
    this.add_month = function(amount) {
        var year  = this.get_year();
        var month = this.get_month();
        var day   = this.stored_day_;

        month += amount;

        if (month < 0) {
            month = 11;
            --year;
        } else if (month > 11) {
            month = 0;
            ++year;
        }
        var max_days = this.get_days_in_month(year,month);
        if (day > max_days) day = this.get_days_in_month(year,month);

        var t = new Date(year,month,day);
        this.set_date_obj(t);
    };
    /**
     * sets user selected date
     * @param int year
     * @param int month
     * @param int day
     * @return boolean on success true
     */
    this.set_all_date_values = function(year, month, day) {
        d = new Date(year,month,day);
        /* check if its a valid date.. */
        if (d == null) return false;
        if (
            year != d.getFullYear() ||
            month != d.getMonth() ||
            day != d.getDate()
        ) {
            return false;
        }
        this.user_date_ = d;
        return true;
    };
    /**
     * generates user friendly today string
     * @return string today
     */
    this.get_today = function() {
        var s = '';

        s += global_language.get_day_translation(
                                                 this.get_actual_day()+1
                                                 );
        s += ". ";
        s += this.get_actual_date() + ". ";
        s += global_language.get_month_short_translation(
                                                         this.get_actual_month()+1
                                                         );
        s += " " + this.get_actual_year();
        return s;
        // today as formated date but looks ugly
        // return this.actualToString();
    };
    /**
     * adds or subract some month days
     * @param int days
     */
    this.add_date_day = function(val) {
        this.set_date(this.get_date() + val);
    };
    /**
     * formats user selected date according to format_
     * @return string formated date
     */
    this.toString = function() {
        return this.do_format(
           this.get_year(),
           this.get_month()+1,
           this.get_date()
        );
    };
    /**
     * formats actual date according to format_
     * @return string formated date
     */
    this.actualToString = function() {
        return this.do_format(
          this.get_actual_year(),
          this.get_actual_month()+1,
          this.get_actual_date()
        );
    };
}

/**
 * class for the calendar
 */
function Dh_calendar(left,top) {
    this.date_        = new Dh_date();
    this.div_         = null;
    this.pos_         = null;
    this.day_table_   = null;
    this.input_       = null;
    this.left_offset_ = 0;
    this.top_offset_  = 0;

    /**
     * init out calendar (create div element etc.)
     */
    this.init = function() {
        this.div_ = document.getElementById('dh_calendar');
        if (this.div_ != null) this.div_.parentNode.removeChild(this.div_);

        this.div_           = document.createElement('DIV');
        this.div_.id        = 'dh_calendar';
        this.div_.className = 'hidden';

        document.body.appendChild(this.div_);
    };
    /**
     * is our div hidden?
     * @return boolean
     */
    this.is_hidden = function() {
        if (this.div_.className == 'hidden') return true;
        return false;
    };
    /**
     * helper to find the position of an element
     * @param object element
     * @return array 0 => offset left, 1 => offset top
     */
    this.find_pos = function(ele) {
        var offset_trail = ele;
        var offset_left  = 0;
        var offset_top   = 0;

        if (ele == null) return null;

        while (offset_trail) {
            offset_left += offset_trail.offsetLeft;
            offset_top  += offset_trail.offsetTop;
            offset_trail = offset_trail.offsetParent;
        }
        if (
            navigator.userAgent.indexOf('Mac') != -1 &&
            typeof document.body.leftMargin != 'undefined'
        ) {
            offset_left += document.body.leftMargin;
            offset_top  += document.body.topMargin;
        }
        return [offset_left,offset_top];
    };
    /**
     * sets position of the calendar div according to given element
     */
    this.set_pos = function() {
        var t = this.find_pos(this.pos_);
        t[1] += this.pos_.height;

        if (this.left_offset_) {
            t[0] += this.left_offset_;
        }
        if (this.top_offset_) {
            t[1] += this.top_offset_;
        }

        this.div_.style.left   = t[0] + 'px';
        this.div_.style.top    = t[1] + 'px';
    };
    /**
     * helper to set our div visible and sets the position
     */
    this.display = function() {
        this.div_.className = 'visible';
        this.set_pos();
    };
    /**
     * hides our div and unsets onkeyup event on input element
     */
    this.hide = function() {
        //this.input_.onkeyup = '';
        this.input_.readOnly = false;
        this.div_.className  = 'hidden';
		unset_events();
    };
    /**
     * reprint our div
     */
    this.reload = function() {
        this.create();
        this.display();
    };
    /**
     * set all propertys for our calendar
     * @param object input element
     * @param string format
     * @param object element containing link to the calendar
     * @return boolean true on success
     */
    this.set = function(input,format,pos,left,top) {
        if (!left) {
            left = 0;
        }
        if (!top) {
            top = 0;
        }
        this.left_offset_ = left;
        this.top_offset_ = top;

        // toggle hidden
        if (!this.is_hidden()) {
            this.hide();
            if (this.input_ == input) return false;
        }
        if (this.input_ != input) this.date_ = new Dh_date();
        this.pos_ = pos;
        this.date_.set_format(format);
        this.input_ = input;
        //this.input_.onkeyup = check_input;
        this.input_.readOnly = true;
        this.set_input_reload();
        return true;
    };
    /**
     * parses the input element and reprint the calendar
     */
    this.set_input_reload = function() {
        this.date_.parse_input(this.input_.value);
        this.reload();
    };
    /**
     * sets the value of our input element and hides the calendar
     * @param string value
     */
    this.set_field = function(value) {
        this.input_.value = value;
        if (!this.date_.time_isset()) {
            this.hide();
        } else {
            this.reload();
        }
    };
    /**
     * create new calendar
     */
    this.create = function() {
        this.init();
        this.day_table_ = new Dh_day_table(
          this.div_,
          this.date_
        );
    };
    this.init();
}

/**
 * class to print the day table
 * @param object element to print the stuff
 * @param object instance of our date class
 */
function Dh_day_table(ele, date) {
    this.ele_   = ele;
    this.table_ = null;
    this.date_  = date;
    this.week_  = setting_show_week_number;

    /**
     * creates the title bar incl. arrows and inputfield
     */
    this.create_title_bar = function() {
        var disp_month = global_language.get_month_translation(
          this.date_.get_month()+1
        );
        var thead = document.createElement('THEAD');
        this.table_.appendChild(thead);

        var row   = thead.insertRow(-1);
        var cell  = null;
        var input = null
        row.id    = 'dh_cal_day_title_bar';

        /* create left arrow */
        cell           = document.createElement('TH');
        cell.id        = 'dh_cal_nav_prev_month';
        cell.onclick   = new Function("navigate(this)");
        cell.colSpan   = 2;
        cell.innerHTML = setting_left_arrow;
        row.appendChild(cell);

        /* create middle */
        cell = document.createElement('TH');
        cell.colSpan   = 3;
        if (this.week_) ++cell.colSpan;

        cell.innerHTML = disp_month;

        // create inputfield
        input          = document.createElement('INPUT');
        input.id       = 'dh_cal_year_input';
        input.onchange = new Function("select_year(this)");
        input.value    = this.date_.get_year();
        cell.appendChild(input);
        row.appendChild(cell);

        /* create right arrow */
        cell           = document.createElement('TH');
        cell.id        = 'dh_cal_nav_next_month';
        cell.onclick   = new Function("navigate(this)");
        cell.colSpan   = 2;
        cell.innerHTML = setting_right_arrow;
        row.appendChild(cell);
    };
    /**
     * creates the week dat title (Mon... Sun)
     */
    this.create_day_title = function() {
        var tbody = document.createElement('TBODY');
        this.table_.appendChild(tbody);

        var row  = tbody.insertRow(-1);
        var cell = null
        row.id   = 'dh_cal_day_title';
        if (this.week_) {
            cell           = row.insertCell(-1);
            cell.className = 'week';
        }
        for (var i = 1; i <= 7; ++i) {
            cell           = row.insertCell(-1);
            cell.innerHTML = global_language.get_day_translation(i);
        }
    };
    /**
     * creates the days in our calendar
     */
    this.create_days = function() {
        var tbody      = document.createElement('TBODY');
        this.table_.appendChild(tbody);

        var counter    = 1;
        var row        = tbody.insertRow(-1);
        var cell       = null;
        var is_now     = this.date_.is_this_month();
        var t          = this.date_.get_first_day_date();
        var day        = t.getDay();
        var active_day = this.date_.get_date();
        var len        = this.date_.get_days_in_month(
          this.date_.get_year(),
          this.date_.get_month()
        );
        row.className = 'dh_cal_days';

        this.create_week(row,1);

        if (day == 0) day = 6;
        else --day;
        if (day != 0) {
            /* now we need to calculate the days
               before this month and display them */
            t.setDate(0);
            var last_day      = t.getDate();
            var last_week_day = t.getDay();
            var i             = last_day;

            while (--last_week_day > 0) --i;
            for ( ; i <= last_day; ++i, ++counter) {
                if (counter > 1 && (counter-1) % 7 == 0) {
                    row    = this.table_.insertRow(-1);
                    row.id = 'dh_cal_days';
                    this.create_week(row,i,'prev');
                }
                cell           = row.insertCell(-1);
                cell.className = 'prev_month';
                cell.onclick   = new Function("select_day(this)");
                cell.innerHTML = i;
            }
        }
        /* our days in calendar */
        for (var i = 1; i <= len; ++i, ++counter) {
            if (counter > 1 && (counter-1) % 7 == 0) {
                row           = this.table_.insertRow(-1);
                row.className = 'dh_cal_days';
                this.create_week(row,i,false);
            }
            cell         = row.insertCell(-1);
            cell.onclick = new Function("select_day(this)");
            if (active_day == i) {
                cell.className = 'active_day';
            }
            if (is_now && this.date_.get_actual_date() == i) {
                cell.className = 'today';
            }
            cell.innerHTML = i;
        }
        /* now display the days in the next month */
        for (var i = 1 ; counter <= 6*7; ++i, ++counter) {
            if (counter >1 && (counter-1) % 7 == 0) {
                row           = this.table_.insertRow(-1);
                row.className = 'dh_cal_days';
                this.create_week(row,i,'next');
            }
            cell           = row.insertCell(-1);
            cell.className = 'next_month';
            cell.onclick   = new Function("select_day(this)");
            cell.innerHTML = i;
        }
    };
    /**
     * helper to create a cell containig the week
     * @param object row
     * @param int day in month
     * @param string flag if its the previous or next month
     */
    this.create_week = function(row, day, flag) {
        if (this.week_) {
            var cell  = row.insertCell(-1);
            var month = this.date_.get_month();
            if (flag == 'prev') --month;
            else if (flag == 'next') ++month;

            cell.className = 'week';
            cell.innerHTML = getWeek(
              this.date_.get_year(),
              month,
              day
            );
        }
    };
    /**
     * creates the today row
     */
    this.create_today = function() {
        var tbody = document.createElement('TBODY');
        this.table_.appendChild(tbody);

        var row  = tbody.insertRow(-1);
        var cell = row.insertCell(-1);
        row.id   = 'dh_cal_day_today';

        if (this.week_) {
            cell.className = "week";
            cell = row.insertCell(-1);
        }
        cell.colSpan   = 7;

        cell.onclick   = new Function("select_today(this)");
        cell.innerHTML = global_language.get_today();
        cell.innerHTML += " " + this.date_.get_today();
    };
    /**
     * check if time row is needed and create them
     */
    this.create_time = function() {
        if (this.date_.time_isset() == false) return false;
        var tbody = document.createElement('TBODY');
        this.table_.appendChild(tbody);

        var row  = tbody.insertRow(-1);
        row.id   = 'dh_cal_day_time';

        var cell = row.insertCell(-1);
        var span = null;

        if (this.week_) {
            cell.className = "week";
            cell = row.insertCell(-1);
        }

        // hour title
        cell.innerHTML = global_language.get_hour();
        cell = row.insertCell(-1);

        // create inputfield
        input          = document.createElement('INPUT');
        input.id       = 'dh_cal_hour_input';
        input.onchange = new Function("select_hour(this)");
        input.value    = this.date_.time.get_hour();
        cell.className = "td_input";
        cell.appendChild(input);
        row.appendChild(cell);

        // create upper arrow for hours
        cell           = row.insertCell(-1);
        span           = document.createElement('SPAN');
        span.innerHTML = setting_arrow_up;
        span.className = "left";
        span.onclick   = new Function("add_time(1,false)");
        cell.appendChild(span);
        // create down arrow for hours
        span           = document.createElement('SPAN');
        span.innerHTML = setting_arrow_down;
        span.className = "right";
        span.onclick   = new Function("add_time(-1,false)");
        cell.appendChild(span);

        // Ok cell
        cell = row.insertCell(-1);
        // some browsers have problems with empty cells..
        cell.innerHTML = setting_ok_string;
        cell.className = "ok_string";
        cell.onclick   = new Function("close_calendar()");

        // min title
        cell = row.insertCell(-1);
        cell.innerHTML = global_language.get_minute();
        cell = row.insertCell(-1);

        // create inputfield
        input          = document.createElement('INPUT');
        input.id       = 'dh_cal_hour_input';
        input.onchange = new Function("select_minute(this)");
        input.value    = this.date_.time.get_min();
        cell.className = "td_input";
        cell.appendChild(input);
        row.appendChild(cell);

        // create upper arrow
        cell = row.insertCell(-1);
        span           = document.createElement('SPAN');
        span.innerHTML = setting_arrow_up;
        span.className = "left";
        span.onclick   = new Function("add_time(false,1)");
        cell.appendChild(span);
        // create down arrow for minutes
        span           = document.createElement('SPAN');
        span.innerHTML = setting_arrow_down;
        span.className = "right";
        span.onclick   = new Function("add_time(false,-1)");
        cell.appendChild(span);
    };
    /**
     * checks if time is needed and displays
     * an creates an seperator row incl. time title
     */
    this.create_time_title = function() {
        if (this.date_.time_isset() == false) return false;
        var tbody = document.createElement('TBODY');
        this.table_.appendChild(tbody);
        var row  = tbody.insertRow(-1);
        row.id   = 'dh_cal_time_title';

        var cell = row.insertCell(-1);

        if (this.week_) {
            cell.className = "week";
            cell = row.insertCell(-1);
        }

        // hour title
        cell.innerHTML = global_language.get_time();
        cell.colSpan   = 7;
    };
    /* create table */
    this.table_    = document.createElement('TABLE');
    this.table_.id = 'dh_cal_day_table';
    this.ele_.appendChild(this.table_);

    /* create table content */
    this.create_title_bar();
    this.create_day_title();
    this.create_days();
    this.create_today();
    this.create_time_title();
    this.create_time();
}

/**
 * From http://www.codeproject.com/csharp/gregorianwknum.asp
 * @param int year
 * @param int month
 * @param int day
 * @return correct week
 */
function getWeek(year,month,day) {
    //lets calc weeknumber the cruel and hard way :D

    //Find JulianDay

    month += 1; //use 1-12

    var a = Math.floor((14-(month))/12);
    var y = year+4800-a;
    var m = (month)+(12*a)-3;
    var jd = day + Math.floor(((153*m)+2)/5) +
        (365*y) + Math.floor(y/4) - Math.floor(y/100) +
        Math.floor(y/400) - 32045;      // (gregorian calendar)

    //var jd = (day+1)+Math.Round(((153*m)+2)/5)+(365+y) +

    //                 Math.round(y/4)-32083;    // (julian calendar)
    //now calc weeknumber according to JD

    var d4 = (jd+31741-(jd%7))%146097%36524%1461;
    var L = Math.floor(d4/1460);
    var d1 = ((d4-L)%365)+L;
    return Math.floor(d1/7) + 1;
}
