@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
at upstream/main 287 lines 6.7 kB view raw
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}