// vim: set autoindent shiftwidth=4 tabstop=8:
// Time zone and date related functions
// Author: Yuliyan Lishev
// $Id: timezone.js,v 1.1.2.3 2008/04/17 15:25:13 jlishev Exp $

var TIMEZONE_JS = 1;                // Timezone library is included.
var TIMESTAMP_TO_MINUTE = 60000;
var DST_CACHE;

BEGIN();

// Find DST boundaries
function get_DST_boundaries(year) {
    var queue = new Array();
    
    var for_date, to_date;
    
    var done        = false;    // Cycle unless  
    var cycles      = 0;        // Current cycle/iteration
    var max_cycles  = 500;      // Max cycles allowed to run
    var boundaries  = [false, 0, false, 0];
    
    DST_CACHE       = new Array();
    
    var timezone_offsets = get_DST_min_max(year);
    
    if (timezone_offsets[0] == timezone_offsets[1]) {
        // Current timezone does not support DST.
        boundaries = [ 0, timezone_offsets[0] * -1,
                       0, timezone_offsets[1] * -1 ];
        return boundaries;
    }
    
    if (year) for_date      = year + "/1/1";                // 1st Jan
    var tmp_date            = new Date(for_date);
    tmp_date.setMonth(0, 1);                                // 1th Jan
    var offset_from_date    = tmp_date.getTime() / TIMESTAMP_TO_MINUTE;
    tmp_date.setMonth(11, 31);                              // 31th Dec
    var offset_to_date      = tmp_date.getTime() / TIMESTAMP_TO_MINUTE;
    
    // Initialize queue with time interval (from 1th Jan to 31th Dec)
    queue.push(offset_from_date, offset_to_date);

    while (!done && cycles <= max_cycles) {
        // Fetch next time interval from the queue.
        var start   = queue.shift();
        var end     = queue.shift();
        
        if (typeof(start) == "undefined") {
            // Queue is empty - stop searching.
            break;
        }
        
        cycles++;

        var diff    = end - start;
        
        if (diff <= 0) {
            // Give up.
            // Difference is too small for further split up of current interval.
            continue;
        }
        
        // Split up current interval to two equal parts.
        var middle      = parseInt(start + (diff / 2));
        var dst_middle  = get_DST_state(middle, timezone_offsets[0], tmp_date);
        
        // We are at place where we can check the boundaries.
        if (diff == 1) {
            // Difference is precisely one minute.
            var dst_right = get_DST_state(middle+1,
                timezone_offsets[0], tmp_date);
            
            if (dst_middle != dst_right) {
                // Both boundaries (from the left and from the right) are
                // different, i.e. we are on the DST boundary.
                if (dst_middle) {
                    // Found boundary where DST ends.
                    boundaries[2] = middle;
                    boundaries[3] = timezone_offsets[1] * -1;
                } else {
                    // Found boundary where DST starts.
                    boundaries[0] = middle;
                    boundaries[1] = timezone_offsets[0] * -1;
                }
                if (boundaries[0] > 0 && boundaries[2] > 0) {
                    // Both boundaries are found - quit the search.
                    done = true;
                    break;
                }
            }
            // Can not found anything, so proceed with next interval.
            continue;
        }
        
        DST_CACHE[middle] = dst_middle;
        
        queue.push( start, middle, middle, end );
        
        if (dst_middle) {
            // DST in the middle of the interval is turned on, so clean up
            // fulureless intervals from the queue.
            queue = clean_up_queue(queue, dst_middle,
                timezone_offsets[0], tmp_date);
        }
    }
    return boundaries;
}

// Cleans up queue from usless time intervals.
function clean_up_queue(queue, dst, tz_offset, tmp_date) {
    var new_queue = new Array();
    var i, start, end, len = queue.length;
    for (i=0; i<len; i+=2) {
        start   = queue[i];
        end     = queue[i+1];
        var dst_left    = get_DST_state(start, tz_offset, tmp_date);
        var dst_right   = get_DST_state(end, tz_offset, tmp_date);
        if (dst_left != dst_right) {
            new_queue.push( start, end);
        }
    }
    return new_queue;
}

// Returns both possible timezone offsets depnding ot DST(on or off) state.
function get_DST_min_max(year) {
    var for_date;
    if (year) for_date = year + "/1/1";
    var d = new Date(for_date);
    d.setMonth(0);
    var min = d.getTimezoneOffset();
    d.setMonth(6);
    var max = d.getTimezoneOffset();
    return [min, max];
}

function get_DST_state(timestamp, tz_offset, tmp_date) {
    var cache = DST_CACHE[timestamp];
    if (typeof(cache) != "undefined") return cache;
    
    tmp_date.setTime(timestamp * TIMESTAMP_TO_MINUTE);
    
    return tmp_date.getTimezoneOffset() == tz_offset
        ? 0
        : 1;
}

function as_date (timestamp) {
    var d = new Date();
    d.setTime(timestamp * TIMESTAMP_TO_MINUTE);

    return d.toString();
}

// Get daylight saving duration (in hours)
function get_DST_duration() {
    var date  = new Date();
    var year  = date.getFullYear();
    
    var DST   = 2 * date.getTimezoneOffset()
              - new Date(year, 0).getTimezoneOffset() // Jan
              - new Date(year, 6).getTimezoneOffset() // Jul

    if ( isNaN(DST) ) return 0;
    return DST;
}

// Get current daylight saving duration state (0 or 1)
function is_DST() {
    var DST = get_DST_duration();
    if ( DST != 0 ) {
        var date    = new Date();
        var year    = date.getFullYear();
        if ( date.getTimezoneOffset() ==
            new Date(year, 6).getTimezoneOffset() ) {
            return 1;
        }
    }
    return 0;
}

// Get timezone offset (in hours)
function get_client_timezone_offset() {
    return parseInt( new Date().getTimezoneOffset() ) * -1;
}

// Write cookie: {name: cookie_name, value: cookie_value, expires: 0_or_time_in_min}
function write_cookie(param) {
    var name, value, expires;
    
    if (typeof(param) == "object") {
        name    = param.name;
        value   = param.value;
        expires = param.expires;
        if ( typeof(expires) == "undefined" || !parseInt(expires) ) {
            expires = 0;
        } else {
            expires = parseInt(expires) * 60 * 1000;  // to miliseconds
        }
    } else {
        return null;
    }
    
    var date = new Date( Date.parse( new Date().toGMTString() ) + expires );
    
    var cookie  = escape(name) + "=" + escape(value) + ";"
                + (
                    expires != 0
                            ?   "expires=" + date.toGMTString() + ";"
                            :   ""
                  )
                + "path=/;";
    document.cookie = cookie;
    return 1;
}

function delete_cookie(name) {
    var date = new Date();

    date.setDate(date.getDate() - 1);
    var cookie  = escape(name) + "=;" + "expires=" + date.toGMTString() + ";"
                + "path=/;";
    document.cookie = cookie;
    return 1;
}

function read_cookie(name) {
    var stop, index;

    index = document.cookie.indexOf(escape(name) + "=");
    if (index == -1) return null;
    index   = document.cookie.indexOf("=", index) + 1;
    stop    = document.cookie.indexOf(";", index);
    if (stop == -1) stop = document.cookie.length;
    return unescape( document.cookie.substring(index, stop) );
}

function BEGIN() {
    // TZ and DST boundaries are marked as currently saved in user's record.
    var tz_timestamp = read_cookie('tz_info_saved');
    
    var utc_date = Date.parse( new Date().toGMTString() ) / 1000;    // in sec
    
    // Every 24h TZ info is outdated.
    if (tz_timestamp != null && tz_timestamp != "") {
        if (utc_date - parseInt(tz_timestamp) <= 24*60*60) return false;
    }

    var tz_offset   = get_client_timezone_offset() * 60;             // in sec
    var dst         = is_DST();
    var dst_zones   = "";

    // Compute DST boundaries only when user logs in.
    var i, year = new Date().getYear();
    if (year < 1900) {
        year += 1900;
    }

    for (i=0; i<10; i++) {
        var z = get_DST_boundaries(year + i);
        
        if (typeof(z) == "object" && z[0] !== false) {
        
        var year_starts = Date.parse((year+i) + "/1/1 00:00:00")      / 1000;
        var year_ends   = Date.parse((year+i) + "/12/31 23:59:59")    / 1000;
        
            dst_zones   += "dst_on="    + (z[3] * 60) + ","
                        +  "dst_off="   + (z[1] * 60) + ","
                        +  "zone="
                        +  year_starts  + "."
                        +  (z[0] * 60)  + "."
                        +  (z[2] * 60)  + "."
                        +  year_ends
                        +  ";";
        }
    }
    
    if (dst_zones != "") {
        write_cookie( { name:       'dst_zones',
                        value:      dst_zones,
                        expires:    0
        } );
    }
    write_cookie( { name:       'tz_info',
        value:      'offset:'   + tz_offset + ';'
                  + 'dst:'      + dst       + ';'
                  + 'utc_date:' + utc_date,
        expires:    0
    } );
}



