<?php
/**
 * Link         :   http://www.phpcorner.net
 * User         :   qingbing<780042175@qq.com>
 * Date         :   2018-11-07
 * Version      :   1.0
 */

namespace Helper;

defined('APP_CHARSET') or define('APP_CHARSET', 'utf-8');

class DateTimeParser
{
    /* @var bool 标志 php 的 "mbstring" 扩展是否为可用状态 */
    static private $_mbstringAvailable;

    /**
     * 将日期转化成时间戳
     * @param string $value
     * @param string $pattern
     * @param array $defaults 年，月，日，小时，分钟和秒的默认值
     * @return int|false 如果解析失败，则返回False
     */
    static public function parse($value, $pattern = 'MM/dd/yyyy', $defaults = [])
    {
        if (null === self::$_mbstringAvailable) {
            self::$_mbstringAvailable = extension_loaded('mbstring');
        }
        $ps = self::analysisPattern($pattern);
        $i = 0;
        $n = self::$_mbstringAvailable ? mb_strlen($value, APP_CHARSET) : strlen($value);
        foreach ($ps as $p) {
            switch ($p) {
                case 'yyyy':
                    {
                        if (false === ($year = self::parseInteger($value, $i, 4, 4)))
                            return false;
                        $i += 4;
                        break;
                    }
                case 'yy':
                    {
                        if (false === ($year = self::parseInteger($value, $i, 1, 2)))
                            return false;
                        $i += strlen($year);
                        break;
                    }
                case 'MM':
                    {
                        if (false === ($month = self::parseInteger($value, $i, 2, 2)))
                            return false;
                        $i += 2;
                        break;
                    }
                case 'M':
                    {
                        if (false === ($month = self::parseInteger($value, $i, 1, 2)))
                            return false;
                        $i += strlen($month);
                        break;
                    }
                case 'dd':
                    {
                        if (false === ($day = self::parseInteger($value, $i, 2, 2)))
                            return false;
                        $i += 2;
                        break;
                    }
                case 'd':
                    {
                        if (false === ($day = self::parseInteger($value, $i, 1, 2)))
                            return false;
                        $i += strlen($day);
                        break;
                    }
                case 'h':
                case 'H':
                    {
                        if (false === ($hour = self::parseInteger($value, $i, 1, 2)))
                            return false;
                        $i += strlen($hour);
                        break;
                    }
                case 'hh':
                case 'HH':
                    {
                        if (false === ($hour = self::parseInteger($value, $i, 2, 2)))
                            return false;
                        $i += 2;
                        break;
                    }
                case 'm':
                    {
                        if (false === ($minute = self::parseInteger($value, $i, 1, 2)))
                            return false;
                        $i += strlen($minute);
                        break;
                    }
                case 'mm':
                    {
                        if (false === ($minute = self::parseInteger($value, $i, 2, 2)))
                            return false;
                        $i += 2;
                        break;
                    }
                case 's':
                    {
                        if (false === ($second = self::parseInteger($value, $i, 1, 2)))
                            return false;
                        $i += strlen($second);
                        break;
                    }
                case 'ss':
                    {
                        if (false === ($second = self::parseInteger($value, $i, 2, 2)))
                            return false;
                        $i += 2;
                        break;
                    }
                case 'a':
                    {
                        if (false === ($ampm = self::parseAmPm($value, $i)))
                            return false;
                        if (isset($hour)) {
                            if ($hour == 12 && $ampm === 'am')
                                $hour = 0;
                            else if ($hour < 12 && $ampm === 'pm')
                                $hour += 12;
                        }
                        $i += 2;
                        break;
                    }
                default:
                    {
                        $tn = self::$_mbstringAvailable ? mb_strlen($p, APP_CHARSET) : strlen($p);
                        if ($i >= $n || ($p{0} != '?' && (self::$_mbstringAvailable ? mb_substr($value, $i, $tn, APP_CHARSET) : substr($value, $i, $tn)) !== $p))
                            return false;
                        $i += $tn;
                        break;
                    }
            }
        }
        if ($i < $n)
            return false;

        if (!isset($year))
            $year = isset($defaults['year']) ? $defaults['year'] : date('Y');
        if (!isset($month))
            $month = isset($defaults['month']) ? $defaults['month'] : date('n');
        if (!isset($day))
            $day = isset($defaults['day']) ? $defaults['day'] : date('j');
        if (strlen($year) === 2) {
            $year = Unit::getFullYear($year);
        }
        $year = (int)$year;
        $month = (int)$month;
        $day = (int)$day;

        if (
            !isset($hour) && !isset($minute)
            && !isset($second) && !isset($defaults['hour'])
            && !isset($defaults['minute']) && !isset($defaults['second'])
        ) {
            $hour = $minute = $second = 0;
        } else {
            if (!isset($hour)) {
                $hour = isset($defaults['hour']) ? $defaults['hour'] : date('H');
            }
            if (!isset($minute)) {
                $minute = isset($defaults['minute']) ? $defaults['minute'] : date('i');
            }
            if (!isset($second)) {
                $second = isset($defaults['second']) ? $defaults['second'] : date('s');
            }
            $hour = (int)$hour;
            $minute = (int)$minute;
            $second = (int)$second;
        }

        if (Unit::isValidDate($year, $month, $day) && Unit::isValidTime($hour, $minute, $second)) {
            return Unit::getTimestamp($hour, $minute, $second, $month, $day, $year);
        }
        return false;
    }

    /**
     * 解析日期或时间的格式串
     * @param string $pattern
     * @return array
     */
    static protected function analysisPattern($pattern)
    {
        if (!($n = self::$_mbstringAvailable ? mb_strlen($pattern, APP_CHARSET) : strlen($pattern))) {
            return [];
        }
        $ps = [];
        $c0 = self::$_mbstringAvailable ? mb_substr($pattern, 0, 1, APP_CHARSET) : substr($pattern, 0, 1);

        for ($start = 0, $i = 1; $i < $n; ++$i) {
            $c = self::$_mbstringAvailable ? mb_substr($pattern, $i, 1, APP_CHARSET) : substr($pattern, $i, 1);
            if ($c !== $c0) {
                $ps[] = self::$_mbstringAvailable ? mb_substr($pattern, $start, $i - $start, APP_CHARSET) : substr($pattern, $start, $i - $start);
                $c0 = $c;
                $start = $i;
            }
        }
        $ps[] = self::$_mbstringAvailable ? mb_substr($pattern, $start, $n - $start, APP_CHARSET) : substr($pattern, $start, $n - $start);
        return $ps;
    }

    /**
     * 将相应的字符串转换成数字
     * @param string $value the date string to be parsed
     * @param int $offset starting offset
     * @param int $minLength minimum length
     * @param int $maxLength maximum length
     * @return string parsed integer value
     */
    static protected function parseInteger($value, $offset, $minLength, $maxLength)
    {
        for ($len = $maxLength; $len >= $minLength; --$len) {
            $v = self::$_mbstringAvailable ? mb_substr($value, $offset, $len, APP_CHARSET) : substr($value, $offset, $len);
            if (ctype_digit($v) && (self::$_mbstringAvailable ? mb_strlen($v, APP_CHARSET) : strlen($v)) >= $minLength) {
                return $v;
            }
        }
        return false;
    }

    /**
     * 解析上午"am"和下午"pm"
     * @param string $value the date string to be parsed
     * @param int $offset starting offset
     * @return string parsed day period value
     */
    static protected function parseAmPm($value, $offset)
    {
        $v = strtolower(self::$_mbstringAvailable ? mb_substr($value, $offset, 2, APP_CHARSET) : substr($value, $offset, 2));
        return $v === 'am' || $v === 'pm' ? $v : false;
    }
}