<?php
class CI_Date {
    public $sec_in_min, $min_in_hour, $hr_in_day, $day_in_wk, $sec_in_wk, $sec_in_day, $min_in_wk, $min_in_day, $hr_in_wk;
    public function __construct() {
        $this->sec_in_min   = 60;
        $this->min_in_hr    = 60;
        $this->hr_in_day    = 24;
        $this->day_in_wk    = 7;
        $this->day_in_yr    = 365;
        $this->sec_in_hr    = $this->sec_in_min * $this->min_in_hr;
        $this->sec_in_wk    = $this->sec_in_min * $this->min_in_hr * $this->hr_in_day * $this->day_in_wk;
        $this->sec_in_day   = $this->sec_in_min * $this->min_in_hr * $this->hr_in_day;
        $this->sec_in_yr    = $this->sec_in_day * $this->day_in_yr;
        $this->sec_in_mo    = $this->sec_in_yr / 12;
        $this->min_in_wk    = $this->min_in_hr  * $this->hr_in_day * $this->day_in_wk;
        $this->min_in_day   = $this->min_in_hr  * $this->hr_in_day;
        $this->hr_in_wk     = $this->hr_in_day  * $this->day_in_wk;
    }

    // *****************************************************************************
    // convert_datetime_to_epoch( $datetime )
    //  -- takes datetime and converts it to seconds since epoch
    // *****************************************************************************
    public function convert_datetime_to_epoch( $datetime = '1971-01-01 12:00:00' ) {
        list( $date, $time ) = explode( ' ', $datetime );
        list( $year, $month, $day) = explode( '-', $date );
        list( $hour, $min, $sec) = explode( ':', $time );
        return mktime( $hour, $min, $sec, $month, $day, $year );
    }
    
    // *****************************************************************************
    // Takes time in seconds since epoch and converts it to a datetime (Y-m-d h:i:s)
    // If not time is given, the current time is used.
    // -----
    // @param   Integer $epoch  The number of seconds since Jan 1 1971 @ 00:00:00.
    // @return  String  Datetime formatted from epoch time.
    // *****************************************************************************
    public function convert_epoch_to_datetime($epoch=false) {
        if(!$epoch) { $epoch = time(); }
        return date('Y-m-d h:i:s',$epoch);
    }
    
    // *****************************************************************************
    // Takes one of those wierd time values from AD/LDAP found in pwdLastSet and
    // stuff like that and converts it to a unix epoch timestamp.
    // -----
    // @param   Integer $ldap_date  Crazy time like this: 129095083680625000
    // @return  Integer             Unix seconds since epoch.
    // *****************************************************************************
    public function convert_ldap_to_epoch($ldap_date=false) {
        if(empty($ldap_date)) return 0;
        $years_from_1601_to_1970 = 1970 - 1601;
        $days_from_1601_to_1970 = $years_from_1601_to_1970 * 365;
        $days_from_1601_to_1970 += ($years_from_1601_to_1970 / 4); // leap years
        $days_from_1601_to_1970 -= 3; // non-leap centuries (1700,1800,1900).  2000 is a leap century
        $seconds_from_1601_to_1970 = $days_from_1601_to_1970 * 24 * 60 * 60;
        $total_seconds_since_1601 = ($ldap_date / 10000000);
        $total_seconds_since_1970 = $total_seconds_since_1601 - $seconds_from_1601_to_1970;
        
        return $total_seconds_since_1970;
    }
    
    // *****************************************************************************
    // Converts a given date/time string and converts it to the ISO 8601 datetime
    // standard. The format is as such: 2004-02-12T15:19:21+00:00.
    // If `$datetime` is empty, the current time is used.
    // -----
    // @param   String  $datetime   The date/time in some 'valid' format to convert.
    // @return  String  ISO 8601 date/time string.
    // *****************************************************************************
    public function convert_to_iso_8601($datetime) {
        if(empty($datetime)) 
            $datetime = time();
        else
            $datetime = strtotime($datetime);
        if($this->is_valid_date($datetime))
            return date('c',$datetime);
        return date('c',0);
    }

    // *****************************************************************************
    // convert_google_to_epoch( $google_time )
    //  -- Converts google date/time format (2009-04-28T10:00:00.000-04:00) to unix time
    // *****************************************************************************
    public function convert_google_to_epoch($google_time='1971-01-01T12:00:00.000-05:00') {
        if(strpos($google_time,'T') === false) {
            // Assume only date is given (yyyy-mm-dd)
            list( $year, $month, $day) = explode( '-', $google_time );
            return mktime( 0,0,0, $month, $day, $year );
        }
        list($date,$time) = explode('T',$google_time);
        list($time,$timezone) = explode('.',$time);
        list( $year, $month, $day) = explode( '-', $date );
        list( $hour, $min, $sec) = explode( ':', $time );
        return mktime( $hour, $min, $sec, $month, $day, $year );
    }

    // *****************************************************************************
    // convert_to_time_ago( $datefrom, $dateto )
    //  -- Tells how long ago a time (in seconds since epoch) occured.
    //      http://www.sajithmr.com/php-time-ago-calculation/
    // *****************************************************************************
    public function convert_to_time_ago( $datefrom, $dateto=-1 ) {
        return $this->convert_to_easy_time( $datefrom, $dateto );
    }
    public function convert_to_easy_time( $datefrom, $dateto=-1,$easy_measure='ago' ) {
        if( $datefrom<=0 ) return "before the 70's... wow...!";
        if( $dateto==-1 ) $dateto = time();
        $difference = $dateto - $datefrom;
        // If difference is less than 60 seconds, seconds is a good interval of choice
        if($difference < 60) {
            $interval = "s";
        }
        // If difference is between 60 seconds and 60 minutes, minutes is a good interval
        elseif($difference >= 60 && $difference<60*60) {
            $interval = "n";
        }
        // If difference is between 1 hour and 24 hours hours is a good interval
        elseif($difference >= 60*60 && $difference<60*60*24) {
            $interval = "h";
        }
        // If difference is between 1 day and 7 days days is a good interval
        elseif($difference >= 60*60*24 && $difference<60*60*24*7) {
            $interval = "d";
        }
        // If difference is between 1 week and 30 days weeks is a good interval
        elseif($difference >= 60*60*24*7 && $difference < 60*60*24*30) {
            $interval = "ww";
        }
        // If difference is between 30 days and 365 days months is a good interval, again, the same thing
        // applies, if the 29th February happens to exist between your 2 dates, the function will return
        // the 'incorrect' value for a day
        elseif($difference >= 60*60*24*30 && $difference < 60*60*24*365) {
            $interval = "m";
        }
        // If difference is greater than or equal to 365days, return year. This will be incorrect if
        // for example, you call the function on the 28th April 2008 passing in 29th April 2007. It will return
        // 1 year ago when in actual fact (yawn!) not quite a year has gone by
        elseif($difference >= 60*60*24*365) {
            $interval = "y";
        }
        // Based on the interval, determine the number of units between the two dates
        // From this point on, you would be hard pushed telling the difference between
        // this function and DateDiff. If the $datediff returned is 1, be sure to return the singular
        // of the unit, e.g. 'day' rather 'days'
        switch($interval) {
            case "m":
                $months_difference = floor($difference / 60 / 60 / 24 /
                29);
                while (mktime(date("H", $datefrom), date("i", $datefrom),
                    date("s", $datefrom), date("n", $datefrom)+($months_difference),
                    date("j", $dateto), date("Y", $datefrom)) < $dateto) {
                        $months_difference++;
                }
                $datediff = $months_difference;
                // We need this in here because it is possible
                // to have an 'm' interval and a months
                // difference of 12 because we are using 29 days
                // in a month
                if($datediff==12) {
                    $datediff--;
                }
                $res = ($datediff==1) ? "$datediff month $easy_measure" : "$datediff months $easy_measure";
                break;
            case "y":
                $datediff = floor($difference / 60 / 60 / 24 / 365);
                $res = ($datediff==1) ? "$datediff year $easy_measure" : "$datediff years $easy_measure";
                break;
            case "d":
                $datediff = floor($difference / 60 / 60 / 24);
                $res = ($datediff==1) ? "$datediff day $easy_measure" : "$datediff days $easy_measure";
                break;
            case "ww":
                $datediff = floor($difference / 60 / 60 / 24 / 7);
                $res = ($datediff==1) ? "$datediff week $easy_measure" : "$datediff weeks $easy_measure";
                break;
            case "h":
                $datediff = floor($difference / 60 / 60);
                $res = ($datediff==1) ? "$datediff hour $easy_measure" : "$datediff hours $easy_measure";
                break;
            case "n":
                $datediff = floor($difference / 60);
                $res = ($datediff==1) ? "$datediff minute $easy_measure" : "$datediff minutes $easy_measure";
                break;
            case "s":
                $datediff = $difference;
                $res = ($datediff==1) ? "$datediff second $easy_measure" : "$datediff seconds $easy_measure";
                break;
        }
        return $res;
    }

    // *****************************************************************************
    // Deducts the given number of seconds from a given datetime and returns the modified
    // MySQL datetime string.
    // -----
    // @param String $datetime - the valid MySQL datetime to be modified
    // @param Integer $seconds - the number of seconds to be deducted from datetime 
    // @hint - use this format for seconds: 2*60*60 (2 hrs * 60 mins * 60 secs), for instance.
    // @Return String - the new datetime with deducted seconds
    // *****************************************************************************
    public function deduct_time_from_datetime($datetime,$seconds) {
        if(is_valid_datetime($datetime)) {
            $time   = (int)$this->convert_datetime_to_epoch($datetime)-(int)$seconds;
            $time   = $this->convert_epoch_to_datetime($time);
            return $time;
        }
        return false;
    }
    
    // ***************************************************************************************
    // Establishes a date range from the most recent previous Sunday to the next-most Saturday.
    // -----
    // @return	Array	- an array containin the start date and end date described above.
    // ***************************************************************************************
    public function get_current_week_range($current_date=false) {
        if($current_date === false) { $current_date = time(); }
        // figure out date range info
        $one_day = 24*60*60;
        $today   = getdate($current_date);
        // Use 1 am so DST spring forward doesn't mess up our date calculation below
        $ts      = mktime(1, 0, 0, $today['mon'], $today['mday'], $today['year']);

        // adjust the timestamp to Sunday
        if($today['wday'] != 0)  {
            $ts -= $today['wday'] * $one_day;
        }
        $start_ts   = $ts; // keep track of the starting timestamp
        $start_date = date("Y-m-d", $ts);
        $end_date   = date("Y-m-d", ( $ts + (6 * $one_day)) );
        $date_range = array('start_date'=>$start_date,'end_date'=>$end_date);
        return $date_range;
    }

    // *****************************************************************************
    // Formats a timestamp (seconds since epoch) to its wordy cousin:
    // "Monday, the 27th of October 2008"
    // -----
    // @param   Unix Timestamp  $when   Either 'now' or a valid unix timestamp.
    // *****************************************************************************
    public function get_formatted_date( $when = 'now' ) {
        if( $when == 'now' ) {
            return date( 'l, \t\h\e jS \of F Y' );
        }
        return date( 'l, \t\h\e jS \of F Y', $when );
    }

    // *****************************************************************************
    // Takes two times (24-hour range, formatted HH:MM) and returns an array
    // of half-hour increments within the two given times
    // -----
    // @param   String  $start_time The time to tart giving 30 minute increments
    // @param   String  $end_time   The time to stop creating 30 minute increments
    // *****************************************************************************
    public function get_half_hour_time_range( $start_time='8:00', $end_time='18:00' ) {
        list( $sh,$sm ) = explode( ':', $start_time );
        list( $eh,$em ) = explode( ':', $end_time );
        $timeRangeArray = array();
        //return ;
        while((int)$sh.$sm < (int)$eh.$em) {
            if($sm == "00") {
                $sm = "30";
            } else {
                $sm = "00";
                $sh = (int)($sh+1);
            }
            $fullTime = date("h:ia",mktime((int)$sh,(int)$sm,0));
            array_push($timeRangeArray,$fullTime);
            if($sh < 10) {
                $sh = "0"+$sh;
            }
        }
        return $timeRangeArray;
    }
    
    // *****************************************************************************
    // Returns the number of work days within the range of a provided start and end
    // date. If no date is provided for either or both dates, defaults will be:
    // -----
    // @default Date    $date1  today -1 week
    // @default Date    $date2  today
    // -----
    // @param   Date    $date1  - should be the start of the date range
    // @param   Date    $date2  - should be the end of the date range.
    // @return array
    // *****************************************************************************
    public function get_num_work_days($date1='',$date2='') {
        if(empty($date1)) {
            $date1 = strtotime(date('Y-m-d', strtotime('-1 week')) . " 00:00:00");
        } else {
            $date1 = strtotime($date1 . " 00:00:00");
        }
        if(empty($date2)) {
            $date2 = strtotime(date('Y-m-d', time()) . " 23:59:59");
        } else {
            $date2 = strtotime($date2 . " 23:59:59");
        }
        
        if($date1 >= $date2) { $end_date = $date1; $start_date = $date2; }
        if($date2 > $date1) { $end_date = $date2; $start_date = $date1; }
        
        //echo "Start Date: {$start_date} (".date('Y-m-d H:i:s',$start_date).") | End Date: {$end_date} (".date('Y-m-d H:i:s',$end_date).")<br />";
        
        $num_days = abs(($end_date - $start_date)) / $this->sec_in_day;
        $num_weekdays = 0;
        for($i=0;$i<=$num_days;$i++) {
            $the_date = date('N',($start_date + ($this->sec_in_day * $i)));
            if($the_date >= 1 && $the_date <= 5)
                $num_weekdays++;
        }
        
        return $num_weekdays;
    }
    
    // ***************************************************************************************
    // Returns the number of seconds in between two dates. Dates provided could be in any
    // parsable date format. There is no inherent order in which the params need to be provided.
    // -----
    // @param   String  $t1     One of the two times in the span
    // @param   String  $t2     One of the two times in the span
    // @return  Integer         The number of seconds between the two times given.
    // ***************************************************************************************
    public function get_time_between($t1=0,$t2=0) {
        $t1 = (preg_match('/^\d{6,}$/',$t1) ? $t1 : strtotime($t1));
        $t2 = (preg_match('/^\d{6,}$/',$t2) ? $t2 : strtotime($t2));
        if($t1 == 0 || $t2 == 0) {
            $t1 = $this->nice_date($t1,'U');
            $t2 = $this->nice_date($t2,'U');
            //error_log("T1: $t1, T2: $t2");
            if(empty($t2) || empty($t1) || $t2 == 'Unknown' || $t1 == 'Unknown')
                return 'Unknown';
        }
        if($t2 >= $t1)
            return ($t2 - $t1);
        return ($t1 - $t2);
    }

    // *****************************************************************************
    // Returns in a human-readable format, the amount of time left between the given 
    // time ($time) and now (time()). If time has already passed, let the user know.
    // -----
    // @param Integer $time - the timestamp that we will compare to now.
    // @Return array
    // *****************************************************************************
    public function get_time_left($time,$day=true,$hr=true,$min=true,$sec=true,$week=false,$month=false,$year=false) {
        $time  = $time - time();
        if($time > 0) {
            $years  = ($year ? floor($time/$this->sec_in_year) : 0);
            $months = (($month || $year == 0) ? floor(($time-($years*$this->sec_in_mo))): 0);
            $weeks  = 0;
            $days   = ($day ? floor($time/$this->sec_in_day) : 0);
            $hours  = (($hr  ||  $days == 0) ? floor(($time-($days*86400))/3600) : 0);
            $mins   = (($min || ($days == 0 && $hours == 0)) ? floor (($time-($days*86400)-($hours*3600))/60) : 0);
            $secs   = (($sec || ($days == 0 && $hours == 0 && $mins == 0)) ? floor ($time-($days*86400)-($hours*3600)-($mins*60)) : 0);
            $expires_in = ($days > 0?"$days days, ":'').($hours > 0?"$hours hours, ":'').($mins > 0?"$mins mins, ":'').($secs > 0?"$secs secs. ":'');
            $expires_in = rtrim($expires_in,',. ').'.';
            return $expires_in;
        } else {
            return 'Time is up!';
        }
    }
    
    // *****************************************************************************
    // A more advanced/elaborate version of get_time_left method.
    // -----
    // @param   Integer $future     The timestamp that we will compare to now.
    // @return  String              Legible amount of time until the $future date.
    // *****************************************************************************
    // $original should be the future date and time in unix format
    public function time_to($future,$options=array(),$bad_wrap=array('<span class="error" style="display:inline">Your item is ',' old!</span>')) {
        $defaults = array(
            'year'  => false,
            'month' => false,
            'week'  => false,
            'day'   => false,
            'hour'  => false,
            'min'   => false,
            'sec'   => true,
        );
        $options = array_merge($defaults,$options);
        if(!isset($this->time_left)) $this->time_left = '';
        // Common time periods as an array of arrays
        if(!isset($this->periods)) {
            $this->periods = array(
                ($options['year']   ? array('year',   $this->sec_in_yr) : false),
                ($options['month']  ? array('month',  $this->sec_in_mo) : false),
                ($options['week']   ? array('week',   $this->sec_in_wk) : false),
                ($options['day']    ? array('day',    $this->sec_in_day): false),
                ($options['hour']   ? array('hour',   $this->sec_in_hr) : false),
                ($options['min']    ? array('minute', $this->sec_in_min): false),
                ($options['sec']    ? array('second', 1): false),
            );
        }
        $today = strtotime(date('Y-m-d',time()) . '00:00:00');
        $since = $future - $today; // Find the difference of time between now and the future
        if($since < 0) return $bad_wrap[0] . $this->convert_to_easy_time($future,-1,'') . $bad_wrap[1];
        // Loop around the periods, starting with the biggest
        if(count($this->periods) > 0) {
            $chunk = array_shift($this->periods);
            if(empty($chunk))
                return $this->time_to($future,$options);
            list($period,$seconds) = $chunk;
            // Find the biggest whole period
            $count = floor($since / $seconds);
            if($count == 0) {
                return $this->time_to($future,$options);
            } else {
                $this->time_left .= ($count == 1 ? "1 {$period}" : "{$count} {$period}s") . ', ';
                return $this->time_to($future - ($count * $seconds),$options);
            }
        } else {
            $time_left = $this->time_left;
            unset($this->time_left);
            unset($this->periods);
            return rtrim($time_left,' ,');
        }
    }

    // ****************************************************************************
    // Basic Description
    // -----
    // @param
    // @return
    // ****************************************************************************
    public function get_weekdays_in_timerange($date1=false,$date2=false) {
        if(!empty($date1) && !empty($date2)) {
            $seconds = $this->get_time_between($date1,$date2);
        } else {
            $seconds = $date1;
        }
        if($this->sec_in_day >= $seconds) {
            if($this->sec_in_hr > $seconds) {
                return 'Less than one work hour';
            } else {
                return round($seconds / $this->sec_in_hr) . ' work hours';
            }
        } else {
            $final = '';
            $days = $seconds / $this->sec_in_day;
            $work_days = $days - (2 * floor($days / 7));
            $remainder = $seconds - (($work_days * $this->sec_in_day) + (2 * floor($days / 7)));
            if($this->sec_in_hr > $remainder) {
                $final = "{$work_days} work days and less than one work hour";
            } else {
                $hours = round($remainder / $this->sec_in_hr) . ' work hours';
                $final = "{$work_days} work days and {$hours} work hours";
            }
        }
    }
    
    // ****************************************************************************
    // Uses Regex to determine if a given string is a valid MySQL datetime stamp.
    // @param String $datetime - the MySQL datetime to be verified
    // @hint - it should be yyyy-mm-dd hh:mm:ss
    // @Return Boolean - true if valid, false if not.
    // *****************************************************************************
    public function is_valid_datetime($datetime) {
        if ( !isset($datetime) ) return false;
        if ( strlen($datetime) != 19 ) return false;
        $array = explode( ' ', $datetime );
        if ( count($array) != 2 ) return false;
        if ( preg_match( '/^(\d\d\d\d)-(\d\d)-(\d\d)$/', $array[0], $matches ) ) {
            if ( !checkdate($matches[2], $matches[3], $matches[1]) ) return false;
        }
        else {
            return false;
        }
        if ( !preg_match( '/^(?:0\d|1\d|2[0-4]):(?:0\d|[1-5]\d):(?:0\d|[1-5]\d)$/', $array[1] ) )
            return false;
        return true;
    }
    
    
    // *****************************************************************************
    // Tests to see if a time is a valid time (and not the epoch). 
    // -----
    // @param   String  $datetime   the MySQL datetime to be verified
    // @return  Boolean             true if valid, false if not.
    // *****************************************************************************
    public function is_valid_date($datetime) {
        if(!isset($datetime)) return false;
        if(!date('U', strtotime($datetime)) == '0')
            return false;
        return true;
    }
    
    // ***************************************************************************************
    // Converts crappy, fake date formats into whatever format you want...
    // ***************************************************************************************
    function nice_date($bad_date='',$format='Y-m-d') {
        if(empty($bad_date))
            return 'Unknown';
        // Date like: YYYYMM
        if(preg_match('/^\d{6}$/',$bad_date)) {
            //echo $bad_date." ";
            if(in_array(substr($bad_date,0,2),array('19','20'))) {
                $year  = substr($bad_date,0,4);
                $month   = substr($bad_date,4,2);
            } else {
                $month  = substr($bad_date,0,2);
                $year   = substr($bad_date,2,4);
            }
            return date($format,strtotime($year . '-' . $month . '-01'));
            
        }
        // Date Like: YYYYMMDD
        if(preg_match('/^\d{8}$/',$bad_date)) {
            $month = substr($bad_date,0,2);
            $day   = substr($bad_date,2,2);
            $year  = substr($bad_date,4,4);
            return date($format,strtotime($month . '/01/' . $year));
        }
        // Date Like: MM-DD-YYYY __or__ M-D-YYYY (or anything in between)
        if(preg_match('/^\d{1,2}-\d{1,2}-\d{4}$/',$bad_date)) { 
            list($m,$d,$y) = explode('-',$bad_date);
            return date($format,strtotime("{$y}-{$m}-{$d}"));
        }
        // Any other kind of string, when converted into UNIX time,
        // produces "0 seconds after epoc..." is probably bad...
        // return "Invalid Date".
        if(!date('U', strtotime($bad_date)) == '0') { 
            return "Invalid Date"; //date($format,strtotime($bad_date));
        }
        // It's probably a valid-ish date format already
        if($format != 'U')
            return date($format,strtotime($bad_date));
        return strtotime($bad_date);
    }
}
