diff options
Diffstat (limited to 'vendor/sabre/vobject/lib/DateTimeParser.php')
-rw-r--r-- | vendor/sabre/vobject/lib/DateTimeParser.php | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/vendor/sabre/vobject/lib/DateTimeParser.php b/vendor/sabre/vobject/lib/DateTimeParser.php new file mode 100644 index 000000000..fc568abb0 --- /dev/null +++ b/vendor/sabre/vobject/lib/DateTimeParser.php @@ -0,0 +1,571 @@ +<?php + +namespace Sabre\VObject; + +use DateTimeImmutable; +use DateTimeZone; +use DateInterval; + +/** + * DateTimeParser. + * + * This class is responsible for parsing the several different date and time + * formats iCalendar and vCards have. + * + * @copyright Copyright (C) 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 + * DateTimeImmutable 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 DateTimeImmutable object will still be in the UTC timezone. + * + * @param string $dt + * @param DateTimeZone $tz + * + * @return DateTimeImmutable + */ + static function parseDateTime($dt, DateTimeZone $tz = null) { + + // Format is YYYYMMDD + "T" + hhmmss + $result = preg_match('/^([0-9]{4})([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 InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt); + } + + if ($matches[7] === 'Z' || is_null($tz)) { + $tz = new DateTimeZone('UTC'); + } + $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] . ':' . $matches[6], $tz); + + return $date; + + } + + /** + * Parses an iCalendar (rfc5545) formatted date and returns a DateTimeImmutable object. + * + * @param string $date + * @param DateTimeZone $tz + * + * @return DateTimeImmutable + */ + static function parseDate($date, DateTimeZone $tz = null) { + + // Format is YYYYMMDD + $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/', $date, $matches); + + if (!$result) { + throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date); + } + + if (is_null($tz)) { + $tz = new DateTimeZone('UTC'); + } + + $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz); + + 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 function parseDuration($duration, $asString = false) { + + $result = preg_match('/^(?<plusminus>\+|-)?P((?<week>\d+)W)?((?<day>\d+)D)?(T((?<hour>\d+)H)?((?<minute>\d+)M)?((?<second>\d+)S)?)?$/', $duration, $matches); + if (!$result) { + throw new InvalidDataException('The supplied iCalendar duration value is incorrect: ' . $duration); + } + + if (!$asString) { + + $invert = false; + + if ($matches['plusminus'] === '-') { + $invert = true; + } + + $parts = [ + '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 = [ + '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 DateTimeImmutable|DateInterval + */ + static 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, $referenceTz); + } else { + return self::parseDateTime($date, $referenceTz); + } + + } + + /** + * This method parses a vCard date and or time value. + * + * This can be used for the DATE, DATE-TIME, TIMESTAMP and + * DATE-AND-OR-TIME value. + * + * This method returns an array, not a DateTime value. + * + * The elements in the array are in the following order: + * year, month, date, hour, minute, second, timezone + * + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the year, etc. + * + * Timezone is either returned as 'Z' or as '+0800' + * + * For any non-specified values null is returned. + * + * List of date formats that are supported: + * YYYY + * YYYY-MM + * YYYYMMDD + * --MMDD + * ---DD + * + * YYYY-MM-DD + * --MM-DD + * ---DD + * + * List of supported time formats: + * + * HH + * HHMM + * HHMMSS + * -MMSS + * --SS + * + * HH + * HH:MM + * HH:MM:SS + * -MM:SS + * --SS + * + * A full basic-format date-time string looks like : + * 20130603T133901 + * + * A full extended-format date-time string looks like : + * 2013-06-03T13:39:01 + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +1100. + * + * @param string $date + * + * @return array + */ + static function parseVCardDateTime($date) { + + $regex = '/^ + (?: # date part + (?: + (?: (?<year> [0-9]{4}) (?: -)?| --) + (?<month> [0-9]{2})? + |---) + (?<date> [0-9]{2})? + )? + (?:T # time part + (?<hour> [0-9]{2} | -) + (?<minute> [0-9]{2} | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{4}) + + )? + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + + // Attempting to parse the extended format. + $regex = '/^ + (?: # date part + (?: (?<year> [0-9]{4}) - | -- ) + (?<month> [0-9]{2}) - + (?<date> [0-9]{2}) + )? + (?:T # time part + + (?: (?<hour> [0-9]{2}) : | -) + (?: (?<minute> [0-9]{2}) : | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) + + )? + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + throw new InvalidDataException('Invalid vCard date-time string: ' . $date); + } + + } + $parts = [ + 'year', + 'month', + 'date', + 'hour', + 'minute', + 'second', + 'timezone' + ]; + + $result = []; + foreach ($parts as $part) { + + if (empty($matches[$part])) { + $result[$part] = null; + } elseif ($matches[$part] === '-' || $matches[$part] === '--') { + $result[$part] = null; + } else { + $result[$part] = $matches[$part]; + } + + } + + return $result; + + } + + /** + * This method parses a vCard TIME value. + * + * This method returns an array, not a DateTime value. + * + * The elements in the array are in the following order: + * hour, minute, second, timezone + * + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the hour etc. + * + * Timezone is either returned as 'Z' or as '+08:00' + * + * For any non-specified values null is returned. + * + * List of supported time formats: + * + * HH + * HHMM + * HHMMSS + * -MMSS + * --SS + * + * HH + * HH:MM + * HH:MM:SS + * -MM:SS + * --SS + * + * A full basic-format time string looks like : + * 133901 + * + * A full extended-format time string looks like : + * 13:39:01 + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +11:00. + * + * @param string $date + * + * @return array + */ + static function parseVCardTime($date) { + + $regex = '/^ + (?<hour> [0-9]{2} | -) + (?<minute> [0-9]{2} | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{4}) + + )? + $/x'; + + + if (!preg_match($regex, $date, $matches)) { + + // Attempting to parse the extended format. + $regex = '/^ + (?: (?<hour> [0-9]{2}) : | -) + (?: (?<minute> [0-9]{2}) : | -)? + (?<second> [0-9]{2})? + + (?: \.[0-9]{3})? # milliseconds + (?P<timezone> # timezone offset + + Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2}) + + )? + $/x'; + + if (!preg_match($regex, $date, $matches)) { + throw new InvalidDataException('Invalid vCard time string: ' . $date); + } + + } + $parts = [ + 'hour', + 'minute', + 'second', + 'timezone' + ]; + + $result = []; + foreach ($parts as $part) { + + if (empty($matches[$part])) { + $result[$part] = null; + } elseif ($matches[$part] === '-') { + $result[$part] = null; + } else { + $result[$part] = $matches[$part]; + } + + } + + return $result; + + } + + /** + * This method parses a vCard date and or time value. + * + * This can be used for the DATE, DATE-TIME and + * DATE-AND-OR-TIME value. + * + * This method returns an array, not a DateTime value. + * The elements in the array are in the following order: + * year, month, date, hour, minute, second, timezone + * Almost any part of the string may be omitted. It's for example legal to + * just specify seconds, leave out the year, etc. + * + * Timezone is either returned as 'Z' or as '+0800' + * + * For any non-specified values null is returned. + * + * List of date formats that are supported: + * 20150128 + * 2015-01 + * --01 + * --0128 + * ---28 + * + * List of supported time formats: + * 13 + * 1353 + * 135301 + * -53 + * -5301 + * --01 (unreachable, see the tests) + * --01Z + * --01+1234 + * + * List of supported date-time formats: + * 20150128T13 + * --0128T13 + * ---28T13 + * ---28T1353 + * ---28T135301 + * ---28T13Z + * ---28T13+1234 + * + * See the regular expressions for all the possible patterns. + * + * Times may be postfixed by a timezone offset. This can be either 'Z' for + * UTC, or a string like -0500 or +1100. + * + * @param string $date + * + * @return array + */ + static function parseVCardDateAndOrTime($date) { + + // \d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d + $valueDate = '/^(?J)(?:' . + '(?<year>\d{4})(?<month>\d\d)(?<date>\d\d)' . + '|(?<year>\d{4})-(?<month>\d\d)' . + '|--(?<month>\d\d)(?<date>\d\d)?' . + '|---(?<date>\d\d)' . + ')$/'; + + // (\d\d(\d\d(\d\d)?)?|-\d\d(\d\d)?|--\d\d)(Z|[+\-]\d\d(\d\d)?)? + $valueTime = '/^(?J)(?:' . + '((?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' . + '|-(?<minute>\d\d)(?<second>\d\d)?' . + '|--(?<second>\d\d))' . + '(?<timezone>(Z|[+\-]\d\d(\d\d)?))?' . + ')$/'; + + // (\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?(Z|[+\-]\d\d(\d\d?)? + $valueDateTime = '/^(?:' . + '((?<year0>\d{4})(?<month0>\d\d)(?<date0>\d\d)' . + '|--(?<month1>\d\d)(?<date1>\d\d)' . + '|---(?<date2>\d\d))' . + 'T' . + '(?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' . + '(?<timezone>(Z|[+\-]\d\d(\d\d?)))?' . + ')$/'; + + // date-and-or-time is date | date-time | time + // in this strict order. + + if (0 === preg_match($valueDate, $date, $matches) + && 0 === preg_match($valueDateTime, $date, $matches) + && 0 === preg_match($valueTime, $date, $matches)) { + throw new InvalidDataException('Invalid vCard date-time string: ' . $date); + } + + $parts = [ + 'year' => null, + 'month' => null, + 'date' => null, + 'hour' => null, + 'minute' => null, + 'second' => null, + 'timezone' => null + ]; + + // The $valueDateTime expression has a bug with (?J) so we simulate it. + $parts['date0'] = &$parts['date']; + $parts['date1'] = &$parts['date']; + $parts['date2'] = &$parts['date']; + $parts['month0'] = &$parts['month']; + $parts['month1'] = &$parts['month']; + $parts['year0'] = &$parts['year']; + + foreach ($parts as $part => &$value) { + if (!empty($matches[$part])) { + $value = $matches[$part]; + } + } + + unset($parts['date0']); + unset($parts['date1']); + unset($parts['date2']); + unset($parts['month0']); + unset($parts['month1']); + unset($parts['year0']); + + return $parts; + + } +} |