mirror of
https://codeberg.org/streams/streams.git
synced 2024-09-23 08:15:14 +00:00
745515b11f
We use composer already to install SabreDAV. Include config composer.(json|lock) to install and manage more dependencies in future. Also provide PSR-4 autoloading for our namespaced classes, e.g. "Zotlabs\". To regenerate autoloader maps use: $ composer install --optimize-autoloader --no-dev We could also remove the whole vendor/ folder from our repository, but that would need changes in deployment and how to install hubs and needs more discussion first.
580 lines
16 KiB
PHP
580 lines
16 KiB
PHP
<?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');
|
|
}
|
|
|
|
try {
|
|
$date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] . ':' . $matches[6], $tz);
|
|
} catch (\Exception $e) {
|
|
throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt);
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
try {
|
|
$date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz);
|
|
} catch (\Exception $e) {
|
|
throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date);
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
}
|