1: <?php
2: declare(strict_types=1);
3:
4: namespace om\ICal\Value;
5:
6: use DateInterval;
7: use Exception;
8:
9: /**
10: * DURATION values (RFC 5545, section 3.3.6), e.g. "PT1H30M", "-P1W" or "P1DT12H".
11: */
12: final class Duration {
13: public static function parse(string $value): ?DateInterval {
14: $value = strtoupper(trim($value));
15: // at most 9 digits per part: longer durations (thousands of years) are not meaningful
16: if (!preg_match('/^([+-])?P(?:(\d{1,9})W)?(?:(\d{1,9})D)?(?:T(?:(\d{1,9})H)?(?:(\d{1,9})M)?(?:(\d{1,9})S)?)?$/D', $value, $match) || !preg_match('/\d/', $value)) {
17: return null;
18: }
19: $days = (int) ($match[2] ?? 0) * 7 + (int) ($match[3] ?? 0);
20: try {
21: $interval = new DateInterval(sprintf('P%dDT%dH%dM%dS', $days, (int) ($match[4] ?? 0), (int) ($match[5] ?? 0), (int) ($match[6] ?? 0)));
22: } catch (Exception) {
23: return null;
24: }
25: $interval->invert = ($match[1] ?? '') === '-' ? 1 : 0;
26: return $interval;
27: }
28:
29: /**
30: * Format an interval; months and years are not allowed by the RFC and are converted
31: * to days only when the interval was created by DateTime::diff().
32: */
33: public static function format(DateInterval $interval): string {
34: $days = $interval->days !== false && ($interval->y || $interval->m) ? $interval->days : $interval->d;
35: $time = ($interval->h ? $interval->h . 'H' : '') . ($interval->i ? $interval->i . 'M' : '') . ($interval->s ? $interval->s . 'S' : '');
36: $result = 'P' . ($days ? $days . 'D' : '') . ($time !== '' ? 'T' . $time : '');
37: return ($interval->invert ? '-' : '') . ($result === 'P' ? 'PT0S' : $result);
38: }
39: }
40: