/*-------------------------------------------------------------------------
*
* Copyright (c) 2003-2011, PostgreSQL Global Development Group
*
*
*-------------------------------------------------------------------------
*/
package org.teiid.transport.pg;

import java.sql.Date;
import java.util.TimeZone;

/**
 * Slimmed down from org.postgresql.jdbc2.TimestampUtils
 */
public class TimestampUtils {

    public static final long DATE_POSITIVE_INFINITY = 9223372036825200000l;
    public static final long DATE_NEGATIVE_INFINITY = -9223372036832400000l;
    public static final long DATE_POSITIVE_SMALLER_INFINITY = 185543533774800000l;
    public static final long DATE_NEGATIVE_SMALLER_INFINITY = -185543533774800000l;

    /**
     * Returns the SQL Date object matching the given bytes with
     * {@link Oid#DATE}.
     * 
     * @param tz The timezone used.
     * @param bytes The binary encoded date value.
     * @return The parsed date object.
     */
    public static Date toDate(TimeZone tz, int days) {
        long secs = toJavaSecs(days * 86400L);
        long millis = secs * 1000L;
        int offset = tz.getOffset(millis);
        if (millis <= DATE_NEGATIVE_SMALLER_INFINITY) {
            millis = DATE_NEGATIVE_INFINITY;
            offset = 0;
        } else if (millis >= DATE_POSITIVE_SMALLER_INFINITY) {
            millis = DATE_POSITIVE_INFINITY;
            offset = 0;
        } 
        return new Date(millis - offset);
    }

    /**
     * Converts the given postgresql seconds to java seconds.
     * Reverse engineered by inserting varying dates to postgresql
     * and tuning the formula until the java dates matched.
     * See {@link #toPgSecs} for the reverse operation.
     * 
     * @param secs Postgresql seconds.
     * @return Java seconds.
     */
    private static long toJavaSecs(long secs) {
        // postgres epoc to java epoc
        secs += 946684800L;
    
        // Julian/Gregorian calendar cutoff point
        if (secs < -12219292800L) { // October 4, 1582 -> October 15, 1582
            secs += 86400 * 10;
            if (secs < -14825808000L) { // 1500-02-28 -> 1500-03-01
                int extraLeaps = (int) ((secs + 14825808000L) / 3155760000L);
                extraLeaps--;
                extraLeaps -= extraLeaps / 4;
                secs += extraLeaps * 86400L;
            }
        }
        return secs;
    }

    /**
     * Converts the given java seconds to postgresql seconds.
     * See {@link #toJavaSecs} for the reverse operation.
     * The conversion is valid for any year 100 BC onwards.
     * 
     * @param secs Postgresql seconds.
     * @return Java seconds.
     */
     public static long toPgSecs(long secs) {
        // java epoc to postgres epoc
        secs -= 946684800L;

        // Julian/Greagorian calendar cutoff point
        if (secs < -13165977600L) { // October 15, 1582 -> October 4, 1582
            secs -= 86400 * 10;
            if (secs < -15773356800L) { // 1500-03-01 -> 1500-02-28
                int years = (int) ((secs + 15773356800L) / -3155823050L);
                years++;
                years -= years/4;
                secs += years * 86400;
            }
        }
        
        return secs;
    }

}
