<?php

namespace Sabre\VObject;

/**
 * DateTimeParser
 *
 * This class is responsible for parsing the several different date and time
 * formats iCalendar and vCards have.
 *
 * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class DateTimeParser {

    /**
     * Parses an iCalendar (rfc5545) formatted datetime and returns a DateTime object
     *
     * Specifying a reference timezone is optional. It will only be used
     * if the non-UTC format is used. The argument is used as a reference, the
     * returned DateTime object will still be in the UTC timezone.
     *
     * @param string $dt
     * @param DateTimeZone $tz
     * @return DateTime
     */
    static public function parseDateTime($dt,\DateTimeZone $tz = null) {

        // Format is YYYYMMDD + "T" + hhmmss
        $result = preg_match('/^([1-4][0-9]{3})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/',$dt,$matches);

        if (!$result) {
            throw new \LogicException('The supplied iCalendar datetime value is incorrect: ' . $dt);
        }

        if ($matches[7]==='Z' || is_null($tz)) {
            $tz = new \DateTimeZone('UTC');
        }
        $date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] .':' . $matches[6], $tz);

        // Still resetting the timezone, to normalize everything to UTC
        $date->setTimeZone(new \DateTimeZone('UTC'));
        return $date;

    }

    /**
     * Parses an iCalendar (rfc5545) formatted date and returns a DateTime object
     *
     * @param string $date
     * @return DateTime
     */
    static public function parseDate($date) {

        // Format is YYYYMMDD
        $result = preg_match('/^([1-4][0-9]{3})([0-1][0-9])([0-3][0-9])$/',$date,$matches);

        if (!$result) {
            throw new \LogicException('The supplied iCalendar date value is incorrect: ' . $date);
        }

        $date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3], new \DateTimeZone('UTC'));
        return $date;

    }

    /**
     * Parses an iCalendar (RFC5545) formatted duration value.
     *
     * This method will either return a DateTimeInterval object, or a string
     * suitable for strtotime or DateTime::modify.
     *
     * @param string $duration
     * @param bool $asString
     * @return DateInterval|string
     */
    static public function parseDuration($duration, $asString = false) {

        $result = preg_match('/^(?P<plusminus>\+|-)?P((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$/', $duration, $matches);
        if (!$result) {
            throw new \LogicException('The supplied iCalendar duration value is incorrect: ' . $duration);
        }

        if (!$asString) {
            $invert = false;
            if ($matches['plusminus']==='-') {
                $invert = true;
            }


            $parts = array(
                'week',
                'day',
                'hour',
                'minute',
                'second',
            );
            foreach($parts as $part) {
                $matches[$part] = isset($matches[$part])&&$matches[$part]?(int)$matches[$part]:0;
            }


            // We need to re-construct the $duration string, because weeks and
            // days are not supported by DateInterval in the same string.
            $duration = 'P';
            $days = $matches['day'];
            if ($matches['week']) {
                $days+=$matches['week']*7;
            }
            if ($days)
                $duration.=$days . 'D';

            if ($matches['minute'] || $matches['second'] || $matches['hour']) {
                $duration.='T';

                if ($matches['hour'])
                    $duration.=$matches['hour'].'H';

                if ($matches['minute'])
                    $duration.=$matches['minute'].'M';

                if ($matches['second'])
                    $duration.=$matches['second'].'S';

            }

            if ($duration==='P') {
                $duration = 'PT0S';
            }
            $iv = new \DateInterval($duration);
            if ($invert) $iv->invert = true;

            return $iv;

        }



        $parts = array(
            'week',
            'day',
            'hour',
            'minute',
            'second',
        );

        $newDur = '';
        foreach($parts as $part) {
            if (isset($matches[$part]) && $matches[$part]) {
                $newDur.=' '.$matches[$part] . ' ' . $part . 's';
            }
        }

        $newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
        if ($newDur === '+') { $newDur = '+0 seconds'; };
        return $newDur;

    }

    /**
     * Parses either a Date or DateTime, or Duration value.
     *
     * @param string $date
     * @param DateTimeZone|string $referenceTZ
     * @return DateTime|DateInterval
     */
    static public function parse($date, $referenceTZ = null) {

        if ($date[0]==='P' || ($date[0]==='-' && $date[1]==='P')) {
            return self::parseDuration($date);
        } elseif (strlen($date)===8) {
            return self::parseDate($date);
        } else {
            return self::parseDateTime($date, $referenceTZ);
        }

    }


}
