1: <?php
2: declare(strict_types=1);
3:
4: namespace om\ICal;
5:
6: use DateInterval;
7: use DateTimeInterface;
8: use InvalidArgumentException;
9: use om\ICal\Value\CalAddress;
10: use om\ICal\Value\Classification;
11: use om\ICal\Value\Conference;
12: use om\ICal\Value\DateTimeValue;
13: use om\ICal\Value\Image;
14: use om\ICal\Value\Link;
15: use om\ICal\Value\Period;
16: use om\ICal\Value\PropertyFactory;
17: use om\ICal\Value\Relation;
18: use om\ICal\Value\Status;
19: use om\ICal\Value\Transparency;
20: use om\RRule\Rule;
21:
22: /**
23: * VEVENT (RFC 5545, section 3.6.1).
24: *
25: * @phpstan-import-type PropertyList from PropertyFactory
26: */
27: final class Event extends Item {
28: /**
29: * A new event; all arguments are optional, other properties are given by $properties.
30: *
31: * Event::new(summary: 'Standup', start: new DateTimeImmutable('2026-01-05 09:30', new DateTimeZone('Europe/Prague')), duration: 'PT15M')
32: *
33: * @param ?string $uid UID, a random UUID when null
34: * @param DateTimeInterface|DateTimeValue|string|null $stamp DTSTAMP, now when null; written in UTC
35: * @param DateTimeInterface|DateTimeValue|string|null $start DTSTART: a DateTimeInterface (TZID of its IANA timezone or UTC), a DateTimeValue (also a DATE or a floating time) or an iCalendar string
36: * @param DateTimeInterface|DateTimeValue|string|null $end DTEND, not earlier than the start and of the same value type
37: * @param DateInterval|string|null $duration DURATION instead of the end
38: * @param ?string $url URI
39: * @param Status|string|null $status TENTATIVE, CONFIRMED or CANCELLED
40: * @param Transparency|string|null $transparency OPAQUE (busy) or TRANSPARENT (free)
41: * @param Classification|string|null $classification PUBLIC, PRIVATE, CONFIDENTIAL
42: * @param ?int $priority 0 (undefined) to 9 (lowest), 1 is the highest
43: * @param iterable<string> $categories CATEGORIES, written as one property
44: * @param CalAddress|string|null $organizer a CalAddress or a URI (an e-mail address becomes "mailto:")
45: * @param iterable<CalAddress|string> $attendees
46: * @param Rule|string|null $rrule RRULE, validated; UNTIL has the value type of the start (UTC for zoned times)
47: * @param iterable<DateTimeInterface|DateTimeValue|Period|string> $rdates RDATE, of the value type of the start
48: * @param iterable<DateTimeInterface|DateTimeValue|string> $exdates EXDATE, of the value type of the start
49: * @param DateTimeInterface|DateTimeValue|string|null $recurrenceId RECURRENCE-ID of an override
50: * @param array{float|int, float|int}|null $geo latitude and longitude
51: * @param ?string $color COLOR (RFC 7986), a CSS3 color name
52: * @param iterable<Image|string> $images IMAGE (RFC 7986), URIs or Image objects
53: * @param iterable<Conference|string> $conferences CONFERENCE (RFC 7986), URIs or Conference objects
54: * @param iterable<Link|string> $links LINK (RFC 9253), URIs or Link objects
55: * @param iterable<Relation|string> $relatedTo RELATED-TO, UIDs or Relation objects
56: * @param iterable<Alarm|Component> $alarms VALARM components, see Alarm::display()
57: * @param iterable<Location|Component> $locations VLOCATION components (RFC 9073), see Location::new()
58: * @param PropertyList $properties other properties: Property objects, or name => value where a string is the raw
59: * (escaped) value and other PHP values are converted, see the documentation
60: * @throws InvalidArgumentException for an invalid value or combination of arguments
61: */
62: public static function new(
63: ?string $uid = null,
64: DateTimeInterface|DateTimeValue|string|null $stamp = null,
65: DateTimeInterface|DateTimeValue|string|null $start = null,
66: DateTimeInterface|DateTimeValue|string|null $end = null,
67: DateInterval|string|null $duration = null,
68: ?string $summary = null,
69: ?string $description = null,
70: ?string $location = null,
71: ?string $url = null,
72: Status|string|null $status = null,
73: Transparency|string|null $transparency = null,
74: Classification|string|null $classification = null,
75: ?int $priority = null,
76: ?int $sequence = null,
77: iterable $categories = [],
78: CalAddress|string|null $organizer = null,
79: iterable $attendees = [],
80: Rule|string|null $rrule = null,
81: iterable $rdates = [],
82: iterable $exdates = [],
83: DateTimeInterface|DateTimeValue|string|null $recurrenceId = null,
84: ?array $geo = null,
85: ?string $color = null,
86: iterable $images = [],
87: iterable $conferences = [],
88: iterable $links = [],
89: iterable $relatedTo = [],
90: iterable $alarms = [],
91: iterable $locations = [],
92: array $properties = [],
93: ): self {
94: $builder = ComponentBuilder::item('VEVENT', $uid, $stamp);
95: $startValue = $builder->start($start);
96: $endValue = $builder->end('DTEND', $end, $duration, $startValue);
97: $builder->recurrence($startValue, $rrule, $rdates, $exdates, $recurrenceId)
98: ->descriptive($summary, $description, $location, $url, $classification, $priority, $sequence, $categories, $organizer, $attendees, $geo, $color, $images, $conferences, $links, $relatedTo)
99: ->status($status)
100: ->transparency($transparency)
101: ->alarms($alarms, $startValue !== null, $endValue !== null, 'DTEND')
102: ->locations($locations);
103: return new self($builder->build($properties), new Calendar());
104: }
105:
106: /**
107: * DTEND, DTSTART + DURATION, or one day after an all-day DTSTART (RFC 5545, section 3.6.1).
108: */
109: public function end(): ?DateTimeValue {
110: return $this->date('DTEND') ?? parent::end();
111: }
112:
113: /**
114: * From DTEND or DURATION; one day for all-day events, zero otherwise.
115: */
116: public function duration(): DateInterval {
117: return $this->remember('duration', $this->calculateDuration(...));
118: }
119:
120: private function calculateDuration(): DateInterval {
121: $start = $this->start();
122: $end = $this->date('DTEND');
123: if ($start !== null && $end !== null) {
124: return self::between($start, $end);
125: }
126: $duration = $this->property('DURATION');
127: if ($duration !== null && ($interval = $this->calendar->values()->duration($duration)) !== null) {
128: return $interval;
129: }
130: return new DateInterval($start?->isDate() ? 'P1D' : 'PT0S');
131: }
132:
133: /**
134: * TRANSP: OPAQUE (busy, default) or TRANSPARENT (free).
135: */
136: public function transparency(): string {
137: return strtoupper($this->text('TRANSP') ?? 'OPAQUE');
138: }
139:
140: /**
141: * @return array{float, float}|null latitude and longitude
142: */
143: public function geo(): ?array {
144: $property = $this->property('GEO');
145: return $property === null ? null : $this->calendar->values()->geo($property);
146: }
147: }
148: