<?php

if (!defined('BASEPATH')) {
    exit('No direct script access allowed');
}

/**
 *      Date and Time Converter by Elac v0.9.3
 *      elacdude@gmail.com
 *      www.elacdude.com
 *
 *      You are free to use this code free of charge, modify it, and distrubute it,
 *      just leave this comment block at the top of this file.
 *
 *
 *      Changes/Modifications
 *      6/24/08 - Version 0.9.2 released.  Minor additions
 *                  - Added "S" support. (th, rd, st, nd.  example: 5th)
 *                  - Added a few more abbreviations for units of time in calculate()  (s.  sec. secs. min. mins. m.  and more)
 *                  - Added example.php (php examples and usage) and date_time_formats.html (list of supported date/time formats) to the package.
 *      6/25/08 - Version 0.9.3 released.  Bug fixes
 *                  - Fixed month subtraction (wrap to previous year) bug
 *                  - Fixed month and year "$only_return_the_value=true" bug.  If you calculated by months or years, and set
 *                    $only_return_the_value=true, it would overwrite the values instead of just returning them.
 *                  - Fixed the "D" (Sun, Mon, Tue) bug.  If you supplied "D" and "d" in the same mask, it would not return the correct output.
 *                  - Changed the names of public variables "day", "month", and "year" added "s" at the end for consistency purposes
 *      11/14/08 - Version 0.9.4 released.  Bug fix
 *                  - Got rid of the _one_dig_num function and used ltrim($num "0") instead
 */
class Date_Time_Converter
{
    /*      PUBLIC VARIABLES        */

    /**
     * The date to be calculated in timestamp format
     * @var mixed
     */
    public $date_time_stamp;

    /**
     * The date to be calculated. ex: 12/30/2008 17:40:00
     * @var string
     */
    public $date_time;

    /**
     * The php date() style format that $date_time is in.  ex: m/d/Y H:i:s
     * @var string
     */
    public $date_time_mask;

    public $seconds;
    public $minutes;
    public $hours;
    public $days;
    public $months;
    public $years;
    public $ampm;

    /*      CONSTRUCTOR and DESTRUCTOR */

    /** Constructor.  This is where you supply the date.  Accepts almost any format of
     *   date as long as you supply the correct mask.  DOES accept dates
     *  without leading zeros (n,j,g,G) as long as they aren't bunched together.
     *   ie: ("1152008", "njY") wont work;   ("1/15/2008", "n/j/2008") will work.
     *   Example: $obj = new Date_Time_Calc('12/30/2008 17:40:00', 'm/d/Y H:i:s');  */
    public function __construct($sDate, $sFormat)
    {
        require_once(APPPATH . '/helpers/adodb/adodb-time.inc_helper.php');
        $this->_default_date_time_units(); //set date&time units to default values
        $this->date_time = $sDate;
        $this->date_time_mask = $sFormat;

        //convert date to timestamp
        $this->date_time_stamp = $this->_date_to_timestamp($sDate, $sFormat);
    }

    public function __destruct()
    {
        unset($this->date_time_stamp);
        unset($this->date_time);
        unset($this->date_time_mask);
        unset($this->seconds);
        unset($this->minutes);
        unset($this->hours);
        unset($this->days);
        unset($this->months);
        unset($this->years);
        unset($this->ampm);
    }

    /*      PRIVATE FUNCTIONS       */

    /**
     * Private function. Resets date and time unit variables to default
     */
    private function _default_date_time_units()
    {
        $this->seconds      = '00';
        $this->minutes      = '00';
        $this->hours        = '00';

        // Default must be set to "today", otherwise
        // saving only hour will calculate wrong timestamp.
        // See bug #11606.
        $this->days         = date('d');
        $this->months       = date('m');
        $this->years        = date('Y');
        $this->ampm         = 'am';
    }

    /**
     * Private Function.  Converts a textual month into a digit.  Accepts almost any
     * textual format of a month including abbreviations.
     * Example: _month_num("jan"); //returns '1'   Example2: _month_num("january", true);  //returns '01'
     */
    private function _month_num($themonth, $return_two_digit = false)
    {
        switch (strtolower((string) $themonth)) {
            case 'jan':
            case 'jan.':
            case 'january':
                return ($return_two_digit == true ? '01' : '1');
                break;
            case 'feb':
            case 'feb.':
            case 'february':
            case 'febuary':
                return ($return_two_digit == true ? '02' : '2');
                break;
            case 'mar':
            case 'mar.':
            case 'march':
                return ($return_two_digit == true ? '03' : '3');
                break;
            case 'apr':
            case 'apr.':
            case 'april':
                return ($return_two_digit == true ? '04' : '4');
                break;
            case 'may':
            case 'may.':
                return ($return_two_digit == true ? '05' : '5');
                break;
            case 'jun':
            case 'jun.':
            case 'june':
                return ($return_two_digit == true ? '06' : '6');
                break;
            case 'jul':
            case 'jul.':
            case 'july':
                return ($return_two_digit == true ? '07' : '7');
                break;
            case 'aug':
            case 'aug.':
            case 'august':
                return ($return_two_digit == true ? '08' : '8');
                break;
            case 'sep':
            case 'sep.':
            case 'sept':
            case 'sept.':
            case 'september':
                return ($return_two_digit == true ? '09' : '9');
                break;
            case 'oct':
            case 'oct.':
            case 'october':
                return '10';
                break;
            case 'nov':
            case 'nov.':
            case 'november':
                return '11';
                break;
            case 'dec':
            case 'dec.':
            case 'december':
                return '12';
                break;
            default:
                return false;
                break;
        }
    }

    /**
     * Private Function. Converts a date into a timestamp.  Accepts almost any
     * format of date as long as you supply the correct mask.  DOES accept dates
     * without leading zeros (n,j,g,G) as long as they aren't bunched together.
     * ie: ("1152008", "njY") wont work;   ("1/15/2008", "n/j/2008") will work
     */
    private function _date_to_timestamp($thedate, $mask)
    {
        $mask_orig = $mask;
        // define the valid values that we will use to check
        // value => length
        $all = array(

        //time
            's' => 'ss', // Seconds, with leading zeros
            'i' => 'ii', // Minutes with leading zeros
            'H' => 'HH', // 24-hour format of an hour with leading zeros
            'h' => 'hh', // 12-hour format of an hour with leading zeros
            'G' => 'GG', // 24-hour format of an hour without leading zeros
            'g' => 'gg', // 12-hour format of an hour without leading zeros
            'A' => 'AA', // Uppercase Ante meridiem and Post meridiem
            'a' => 'aa', // Lowercase Ante meridiem and Post meridiem

        //year
            'y' => 'yy', // A full numeric representation of a year, 4 digits
            'Y' => 'YYYY', // A two digit representation of a year

        //month
            'm' => 'mm', // A numeric representation of a month with leading zeros.
            'M' => 'MMM', // A textual representation of a month.  3 letters.  ex: Jan, Feb, Mar, Apr...
            'n' => 'nn', // Numeric representation of a month, without leading zeros

        //days
            'd' => 'dd', // Day of the month, 2 digits with leading zeros
            'j' => 'jj', // Day of the month without leading zeros
            'S' => 'SS', // English ordinal suffix for the day of the month, 2 characters (st, nd, rd, or th. works well with j)
            'D' => 'DDD'        // Textual representation of day of the week (Sun, Mon, Tue, Wed)

        );

        // this will give us a mask with full length fields
        $mask = str_replace(array_keys($all), $all, (string) $mask);

        $vals = array();

        //loop through each character of $mask starting at the beginning
        for ($i = 0; $i < strlen((string) $mask_orig); $i++) {
            //get the current character
            $thischar = substr((string) $mask_orig, $i, 1);

            //if the character is not in the $all array, skip it
            if (array_key_exists($thischar, $all)) {
                $type = $thischar;
                $chars = $all[$type];

                // get position of the current char
                if (($pos = strpos($mask, $chars)) === false) {
                    continue;
                }

                // find the value from $thedate
                $val = substr(trim((string) $thedate), $pos, strlen($chars));

                /*      START FIX FOR UNITS WITHOUT LEADING ZEROS       */
                if ($type == "n" || $type == "j" || $type == "g" || $type == "G") {
                    //if its not numeric, try a shorter digit
                    if (!is_numeric($val) || strval(intval($val)) !== $val) {
                        $val = substr((string) $thedate, $pos, strlen($chars) - 1);
                        $mask = str_replace($chars, $type, $mask);
                    } else {
                        //try numeric value checking
                        switch ($type) {
                            case "n":
                                if ($val > 12 || $val < 1) {
//month must be between 1-12
                                    $val = substr((string) $thedate, $pos, strlen($chars) - 1);
                                    $mask = str_replace($chars, $type, $mask);
                                }
                                break;
                            case "j":
                                if ($val > 31 || $val < 1) {
//day must be between 1-31
                                    $val = substr((string) $thedate, $pos, strlen($chars) - 1);
                                    $mask = str_replace($chars, $type, $mask);
                                }
                                break;
                            case "g":
                                if ($val > 12 || $val < 1) {
//day must be between 1-12
                                    $val = substr((string) $thedate, $pos, strlen($chars) - 1);
                                    $mask = str_replace($chars, $type, $mask);
                                }
                                break;
                            case "G":
                                if ($val > 24 || $val < 1) {
//day must be between 1-24
                                    $val = substr((string) $thedate, $pos, strlen($chars) - 1);
                                    $mask = str_replace($chars, $type, $mask);
                                }
                                break;
                        }
                    }
                }

                /*      END FIX FOR UNITS WITHOUT LEADING ZEROS     */

                //save this value
                $vals[$type] = $val;
            }
        }

        foreach ($vals as $type => $val) {
            switch ($type) {
                case 's':
                    $this->seconds = $val;
                    break;
                case 'i':
                    $this->minutes = $val;
                    break;
                case 'H':
                case 'h':
                    $this->hours = $val;
                    break;
                case 'A':
                case 'a':
                    $this->ampm = $val;
                    break;
                case 'y':
                    $this->years = '20' . $val;
                    break;
                case 'Y':
                    $this->years = $val;
                    break;
                case 'm':
                    $this->months = $val;
                    break;
                case 'M':
                    $this->months = $this->_month_num($val, true);
                    break;
                case 'd':
                    $this->days = $val;
                    break;
                    //ones without leading zeros:
                case 'n':
                    $this->months = $val;
                    break;
                case 'j':
                    $this->days = $val;
                    break;
                case 'g':
                    $this->hours = $val;
                    break;
                case 'G':
                    $this->hours = $val;
                    break;
            }
        }

        if (strtolower((string) $this->ampm) == "pm" && $this->hours < 12) {
//if its pm, add 12 hours
            $this->hours = $this->hours + 12;
        }

        $make_stamp = adodb_mktime(
            (int) ltrim((string) $this->hours, "0"),
            (int) ltrim((string) $this->minutes, "0"),
            (int) ltrim((string) $this->seconds, "0"),
            (int) ltrim((string) $this->months, "0"),
            (int) ltrim((string) $this->days, "0"),
            (int) ltrim((string) $this->years, "0")
        );

        return $make_stamp;
    }

    /**     PUBLIC FUNCTIONS            */

    /** Sets a new format/mask for the date using the php date() style formatting
     *  Example: $obj->convert("M j Y H:i:s A");
     */
    public function convert($new_mask, $save = true)
    {
        $newdate = adodb_date($new_mask, $this->date_time_stamp);
        //if they want to save and apply this new mask to $this->date_time, save it
        if ($save == true) {
            $this->date_time_mask = $new_mask;
            $this->date_time = $newdate;
        }
        return $newdate;
    }

    /** Changes the date to a new one.
     *   Example: $obj->set_date_time('11/20/2005 07:40:00 AM', 'm/d/Y H:i:s A');
     */
    public function set_date_time($start_date_time, $mask)
    {
        $this->__construct($start_date_time, $mask);
    }
}
