@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhutilCalendarAbsoluteDateTime
4 extends PhutilCalendarDateTime {
5
6 private $year;
7 private $month;
8 private $day;
9 private $hour = 0;
10 private $minute = 0;
11 private $second = 0;
12 private $timezone;
13
14 public static function newFromISO8601($value, $timezone = 'UTC') {
15 $pattern =
16 '/^'.
17 '(?P<y>\d{4})(?P<m>\d{2})(?P<d>\d{2})'.
18 '(?:'.
19 'T(?P<h>\d{2})(?P<i>\d{2})(?P<s>\d{2})(?<z>Z)?'.
20 ')?'.
21 '\z/';
22
23 $matches = null;
24 $ok = preg_match($pattern, $value, $matches);
25 if (!$ok) {
26 throw new Exception(
27 pht(
28 'Expected ISO8601 datetime in the format "19990105T112233Z", '.
29 'found "%s".',
30 $value));
31 }
32
33 if (isset($matches['z'])) {
34 if ($timezone != 'UTC') {
35 throw new Exception(
36 pht(
37 'ISO8601 date ends in "Z" indicating UTC, but a timezone other '.
38 'than UTC ("%s") was specified.',
39 $timezone));
40 }
41 }
42
43 $datetime = id(new self())
44 ->setYear((int)$matches['y'])
45 ->setMonth((int)$matches['m'])
46 ->setDay((int)$matches['d'])
47 ->setTimezone($timezone);
48
49 if (isset($matches['h'])) {
50 $datetime
51 ->setHour((int)$matches['h'])
52 ->setMinute((int)$matches['i'])
53 ->setSecond((int)$matches['s']);
54 } else {
55 $datetime
56 ->setIsAllDay(true);
57 }
58
59 return $datetime;
60 }
61
62 public static function newFromEpoch($epoch, $timezone = 'UTC') {
63 $date = new DateTime('@'.$epoch);
64
65 $zone = new DateTimeZone($timezone);
66 $date->setTimezone($zone);
67
68 return id(new self())
69 ->setYear((int)$date->format('Y'))
70 ->setMonth((int)$date->format('m'))
71 ->setDay((int)$date->format('d'))
72 ->setHour((int)$date->format('H'))
73 ->setMinute((int)$date->format('i'))
74 ->setSecond((int)$date->format('s'))
75 ->setTimezone($timezone);
76 }
77
78 public static function newFromDictionary(array $dict) {
79 static $keys;
80 if ($keys === null) {
81 $keys = array_fuse(
82 array(
83 'kind',
84 'year',
85 'month',
86 'day',
87 'hour',
88 'minute',
89 'second',
90 'timezone',
91 'isAllDay',
92 ));
93 }
94
95 foreach ($dict as $key => $value) {
96 if (!isset($keys[$key])) {
97 throw new Exception(
98 pht(
99 'Unexpected key "%s" in datetime dictionary, expected keys: %s.',
100 $key,
101 implode(', ', array_keys($keys))));
102 }
103 }
104
105 if (idx($dict, 'kind') !== 'absolute') {
106 throw new Exception(
107 pht(
108 'Expected key "%s" with value "%s" in datetime dictionary.',
109 'kind',
110 'absolute'));
111 }
112
113 if (!isset($dict['year'])) {
114 throw new Exception(
115 pht(
116 'Expected key "%s" in datetime dictionary.',
117 'year'));
118 }
119
120 $datetime = id(new self())
121 ->setYear(idx($dict, 'year'))
122 ->setMonth(idx($dict, 'month', 1))
123 ->setDay(idx($dict, 'day', 1))
124 ->setHour(idx($dict, 'hour', 0))
125 ->setMinute(idx($dict, 'minute', 0))
126 ->setSecond(idx($dict, 'second', 0))
127 ->setTimezone(idx($dict, 'timezone'))
128 ->setIsAllDay((bool)idx($dict, 'isAllDay', false));
129
130 return $datetime;
131 }
132
133 public function newRelativeDateTime($duration) {
134 if (is_string($duration)) {
135 $duration = PhutilCalendarDuration::newFromISO8601($duration);
136 }
137
138 if (!($duration instanceof PhutilCalendarDuration)) {
139 throw new Exception(
140 pht(
141 'Expected "PhutilCalendarDuration" object or ISO8601 duration '.
142 'string.'));
143 }
144
145 return id(new PhutilCalendarRelativeDateTime())
146 ->setOrigin($this)
147 ->setDuration($duration);
148 }
149
150 public function toDictionary() {
151 return array(
152 'kind' => 'absolute',
153 'year' => (int)$this->getYear(),
154 'month' => (int)$this->getMonth(),
155 'day' => (int)$this->getDay(),
156 'hour' => (int)$this->getHour(),
157 'minute' => (int)$this->getMinute(),
158 'second' => (int)$this->getSecond(),
159 'timezone' => $this->getTimezone(),
160 'isAllDay' => (bool)$this->getIsAllDay(),
161 );
162 }
163
164 public function setYear($year) {
165 $this->year = $year;
166 return $this;
167 }
168
169 public function getYear() {
170 return $this->year;
171 }
172
173 public function setMonth($month) {
174 $this->month = $month;
175 return $this;
176 }
177
178 public function getMonth() {
179 return $this->month;
180 }
181
182 public function setDay($day) {
183 $this->day = $day;
184 return $this;
185 }
186
187 public function getDay() {
188 return $this->day;
189 }
190
191 public function setHour($hour) {
192 $this->hour = $hour;
193 return $this;
194 }
195
196 public function getHour() {
197 return $this->hour;
198 }
199
200 public function setMinute($minute) {
201 $this->minute = $minute;
202 return $this;
203 }
204
205 public function getMinute() {
206 return $this->minute;
207 }
208
209 public function setSecond($second) {
210 $this->second = $second;
211 return $this;
212 }
213
214 public function getSecond() {
215 return $this->second;
216 }
217
218 public function setTimezone($timezone) {
219 $this->timezone = $timezone;
220 return $this;
221 }
222
223 public function getTimezone() {
224 return $this->timezone;
225 }
226
227 private function getEffectiveTimezone() {
228 $date_timezone = $this->getTimezone();
229 $viewer_timezone = $this->getViewerTimezone();
230
231 // Because all-day events are always "floating", the effective timezone
232 // is the viewer timezone if it is available. Otherwise, we'll return a
233 // DateTime object with the correct values, but it will be incorrectly
234 // adjusted forward or backward to the viewer's zone later.
235
236 $zones = array();
237 if ($this->getIsAllDay()) {
238 $zones[] = $viewer_timezone;
239 $zones[] = $date_timezone;
240 } else {
241 $zones[] = $date_timezone;
242 $zones[] = $viewer_timezone;
243 }
244 $zones = array_filter($zones);
245
246 if (!$zones) {
247 throw new Exception(
248 pht(
249 'Datetime has no timezone or viewer timezone.'));
250 }
251
252 return head($zones);
253 }
254
255 public function newPHPDateTimeZone() {
256 $zone = $this->getEffectiveTimezone();
257 return new DateTimeZone($zone);
258 }
259
260 public function newPHPDateTime() {
261 $zone = $this->newPHPDateTimeZone();
262
263 $y = $this->getYear();
264 $m = $this->getMonth();
265 $d = $this->getDay();
266
267 if ($this->getIsAllDay()) {
268 $h = 0;
269 $i = 0;
270 $s = 0;
271 } else {
272 $h = $this->getHour();
273 $i = $this->getMinute();
274 $s = $this->getSecond();
275 }
276
277 $format = sprintf('%04d-%02d-%02d %02d:%02d:%02d', $y, $m, $d, $h, $i, $s);
278
279 return new DateTime($format, $zone);
280 }
281
282
283 public function newAbsoluteDateTime() {
284 return clone $this;
285 }
286
287}