<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 

/*
 * Copyright 2011-2012 Jorge López Pérez <jorge@adobo.org>
 *
 *  This file is part of AgenDAV.
 *
 *  AgenDAV is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  any later version.
 *
 *  AgenDAV is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with AgenDAV.  If not, see <http://www.gnu.org/licenses/>.
 */

class Dates {

    // Possible time formats
    static $timeformats = array(
            '24' => array(
                'strftime' => '%H:%M',
                'date' => 'H:i',
                'fullcalendar' => 'HH:mm',
                ),
            '12' => array(
                'strftime' => '%l:%M',  // %P will be simulated, not working
                                        // in all systems
                'date' => 'h:i A', // Match timepicker format
                'fullcalendar' => 'h(:mm)tt',
                ));

    // Possible date formats (not needed for strftime)
    static $dateformats = array(
            'ymd' => array(
                'date' => 'Y-m-d',
                'datepicker' => 'yy-mm-dd',
                'strftime' => '%Y-%m-%d',
                ),
            'dmy' => array(
                'date' => 'd/m/Y',
                'datepicker' => 'dd/mm/yy',
                'strftime' => '%d/%m/%Y',
                ),
            'mdy' => array(
                'date' => 'm/d/Y',
                'datepicker' => 'mm/dd/yy',
                'strftime' => '%m/%d/%Y',
                ),
            );

    private $CI;
    private $cfg_time;
    private $cfg_date;

    function __construct() {
        $this->CI =& get_instance();

        // Load time and date formats
        $cfg_time = $this->CI->config->item('default_time_format');
        if ($cfg_time === FALSE 
                || ($cfg_time != '12' && $cfg_time != '24')) {
            log_message('ERROR', 
                    'Invalid default_time_format configuration value');
            $this->cfg_time = '24';
        } else {
            $this->cfg_time = $cfg_time;
        }

        $cfg_date = $this->CI->config->item('default_date_format');
        if ($cfg_date === FALSE 
                || ($cfg_date != 'ymd' && $cfg_date != 'dmy'
                    && $cfg_date != 'mdy')) {
            log_message('ERROR', 
                    'Invalid default_date_format configuration value');
            $this->cfg_date = 'ymd';
        }  else {
            $this->cfg_date = $cfg_date;
        }

    }

    /**
     * Returns a DatTime object with date approximated by factor seconds.
     * Defaults to 30 minutes (60*30 = 1800)
     */

    function approx_by_factor($time = null, $tz = null, $factor = 1800) {
        if (is_null($time)) {
            $time = time();
        }

        if (is_null($tz)) {
            $tz =
                $this->CI->timezonemanager->getTz(
                        $this->CI->config->item('default_timezone'));
        }

        $rounded = (round($time/$factor))*$factor;

        return $this->ts2datetime($rounded, $tz);
    }

    /**
     * Creates a DateTime object from an UNIX timestamp using the specified
     * Timezone. If no TZ is specified then default one is used
     */
    function ts2datetime($ts, $tz = null) {
        if (is_null($tz)) {
            $tz = $this->CI->timezonemanager->getTz(
                    $this->CI->config->item('default_timezone'));
        }

        $obj = new DateTime('@' . $ts);
        // When creating by timestamp, DateTime ignores current timezone
        $obj->setTimeZone($tz);

        return $obj;
    }

    /**
     * Creates a DateTime object from a date formatted by frontend (such as
     * m/d/Y H:i).
     *
     * Returns FALSE on date parsing error
     */
    function frontend2datetime($str, $tz = null) {
        if (is_null($tz)) {
            $tz = $this->CI->timezonemanager->getTz(
                    $this->CI->config->item('default_timezone'));
        }

        $format = $this->date_format_string('date') . ' '.
            $this->time_format_string('date');

        $obj = $this->create_datetime($format, $str, $tz);

        return $obj;
    }

    /**
     * Creates a DateTime object from a date formatted by Fullcalendar
     * events: yyyymmddHHii).
     *
     * Returns FALSE on date parsing error
     */
    function fullcalendar2datetime($str, $tz = null) {
        if (is_null($tz)) {
            $tz = $this->CI->timezonemanager->getTz(
                    $this->CI->config->item('default_timezone'));
        }

        $format = 'YmdHis';

        $obj = $this->create_datetime($format, $str, $tz);

        return $obj;
    }

    /**
     * Converts a DateTime to DATE-TIME format 
     * in UTC time by default
     *
     * If no object is passed, current time is used
     */
    function datetime2idt($dt = null, $tz = null, $format = '') {

        if (is_null($tz)) {
            $tz = $this->CI->timezonemanager->getTz('UTC');
        }

        if (is_null($dt)) {
            $dt = new DateTime('now', $tz);
        } else {
            $dt->setTimeZone($tz);
        }

        if (empty($format)) {
            $format = $this->format_for('DATE-TIME', $tz);
        }

        $str = $dt->format($format);

        return $str;
    }

    /**
     * Converts a DATE-TIME/DATE formatted string to DateTime
     * in UTC time by default.
     *
     * Default timezone is used if not specified
     */
    function idt2datetime($id_arr, $tz = null) {
        if (is_null($tz)) {
            $tz = $this->CI->timezonemanager->getTz('UTC');
        }

        $format = 'YmdHis';

        // $tz should be enough
        unset($id_arr['tz']);

        $str = '';
        foreach ($id_arr as $k => $v) {
            $str .= $v;
        }

        // VALUE=DATE
        if (!isset($id_arr['hour'])) {
            $str .= '000000';
        }

        $obj = $this->create_datetime($format, $str, $tz);

        return $obj;
    }

    /**
     * Returns a suitable date() format string according to specified
     * timezone to parse a DATE-TIME/DATE iCalendar string.
     *
     * Defaults to UTC
     */

    function format_for($type = 'DATE-TIME', $tz = null) {
        $format = '';

        if (is_null($tz)) {
            $tz = $this->CI->timezonemanager->getTz('UTC');
        }

        if ($type == 'DATE') {
            $format = 'Ymd';
        } else {
            $format = 'Ymd\THis';
        }

        if ($tz->getName() == 'UTC' && $type != 'DATE') {
            $format .= '\Z';
        }

        return $format;
    }

    /**
     * Converts a DateInterval to a DURATION string
     *
     * Parameter has to be the result of add() or diff() to an existing
     * DateTime object
     */
    function di2duration($di) {
        if ($obj->days === FALSE) {
            // We have a problem
            return FALSE;
        }

        $days = $obj->days;
        $seconds = $obj->s + $obj->i*60 + $obj->h*3600;
        $str = '';

        // Simplest case
        if ($days%7 == 0 && $seconds == 0) {
            $str = ($days/7) . 'W';
        } else {
            $time_units = array(
                    '3600' => 'H',
                    '60' => 'M',
                    '1' => 'S',
                    );
            $str_time = '';
            foreach ($time_units as $k => $v) {
                if ($seconds >= $k) {
                    $str_time .= floor($seconds/$k) . $v;
                    $seconds %= $k;
                }
            }

            // No days
            if ($days == 0) {
                $str = 'T' . $str_time;
            } else {
                $str = $days . 'D' . (empty($str_time) ? '' : 'T' . $str_time);
            }
        }

        return ($obj->invert == '1' ? '-' : '') . 'P' . $str;
    }

    /**
     * Convertes a DURATION string to a DateInterval
     * Allows the use of '-' in front of the string
     */
    function duration2di($str) {
        $minus;
        $new_str = preg_replace('/^(-)/', '', $str, -1, $minus);

        $interval = new DateInterval($new_str);
        if ($minus == 1) {
            $interval->invert = 1;
        }

        return $interval;
    }

    /**
     * Converts a X-CURRENT-DTSTART/X-CURRENT-DTEND string to a DateTime
     * object
     */
    function x_current2datetime($str, $tz) {
        $matches = array();
        $res = preg_match('/^(\d+)-(\d+)-(\d+)( (\d+):(\d+):(\d+)( (\S+))?)?$/', $str, $matches);

        if ($res === FALSE || $res != 1) {
            log_message('ERROR',
                    'Error processing [' . $str . '] as X-CURRENT-*'
                    .' string');
            return new DateTime();
        }

        $y = $matches[1];
        $m = $matches[2];
        $d = $matches[3];
        $h = isset($matches[5]) ? $matches[5] : '00';
        $i = isset($matches[6]) ? $matches[6] : '00';
        $s = isset($matches[7]) ? $matches[7] : '00';
        // Timezone is ignored, we already have $tz
        //$e = isset($matches[9]) ? $matches[9] : $tz;
    
        $format = 'dmY His';
        $new_str = $d.$m.$y.' '.$h.$i.$s;

        $dt = $this->create_datetime($format, $new_str, $tz);

        if ($dt === FALSE) {
            $this->CI->extended_logs->message('ERROR',
                    'Error processing ' . $new_str . ' (post) as a string'
                    .' for X-CURRENT-*');
            return new DateTime();
        }

        return $dt;
    }

    /**
     * Returns a time format string for the current user
     *
     * @param   string  Type of format (fullcalendar, date, strftime)
     * @return  string  Format string. Default formats on invalid params
     */
    function time_format_string($type) {
        switch($type) {
            case 'fullcalendar':
            case 'date':
            case 'strftime':
                return Dates::$timeformats[$this->cfg_time][$type];
                break;
            default:
                $this->CI->extended_logs->message('ERROR', 
                        'Invalid type for time_format_string() passed'
                        .' ('.$type.')');
                break;
        }
    }

    /**
     * Returns a date format string for the current user
     *
     * @param   string  Type of format (date, datepicker)
     * @return  string  Format string. Default formats on invalid params
     */
    function date_format_string($type) {
        switch($type) {
            case 'date':
            case 'datepicker':
            case 'strftime':
                return Dates::$dateformats[$this->cfg_date][$type];
                break;
            default:
                $this->CI->extended_logs->message('ERROR', 
                        'Invalid type for date_format_string() passed'
                        .' ('.$type.')');
                break;
        }

    }


    /**
     * Formats a timestamp time using strftime
     *
     * @param   int Timestamp
     * @param   DateTime    DateTime object (used to calculate am/pm)
     * @return  string  Formatted time string with strftime
     */
    function strftime_time($timestamp, $dt) {
        $format = Dates::$timeformats[$this->cfg_time]['strftime'];
        $result = strftime($format, $timestamp);
        if ($this->cfg_time == '12') {
            $result .= $dt->format('a');
        }
        
        return $result;
    }

    /**
     * Returns a DateTime object using createFromFormat
     *
     * @param   string  Format used to parse the given string
     * @param   string  String that contains date-time
     * @param   DateTimeZone    Timezone
     * @return  DateTime/boolean    FALSE on error
     */
    function create_datetime($format, $str, $tz) {
        $dt = DateTime::createFromFormat($format, $str, $tz);

        // Check for errors
        $err = DateTime::getLastErrors();

        if (FALSE === $dt || $err['warning_count']>0) {
            $dt = FALSE;
        }

        return $dt;
    }

}
