3 final class AphrontFormDateControlValue
extends Phobject
{
13 public function getValueDate() {
14 return $this->valueDate
;
17 public function getValueTime() {
18 return $this->valueTime
;
21 public function isValid() {
22 if ($this->isDisabled()) {
25 return ($this->getEpoch() !== null);
28 public function isEmpty() {
29 if ($this->valueDate
) {
33 if ($this->valueTime
) {
40 public function isDisabled() {
41 return ($this->optional
&& !$this->valueEnabled
);
44 public function setEnabled($enabled) {
45 $this->valueEnabled
= $enabled;
49 public function setOptional($optional) {
50 $this->optional
= $optional;
54 public function getOptional() {
55 return $this->optional
;
58 public function getViewer() {
62 public static function newFromRequest(AphrontRequest
$request, $key) {
63 $value = new AphrontFormDateControlValue();
64 $value->viewer
= $request->getViewer();
66 $date = $request->getStr($key.'_d');
67 $time = $request->getStr($key.'_t');
69 // If we have the individual parts, we read them preferentially. If we do
70 // not, try to read the key as a raw value. This makes it so that HTTP
71 // prefilling is overwritten by the control value if the user changes it.
72 if (!strlen($date) && !strlen($time)) {
73 $date = $request->getStr($key);
77 $value->valueDate
= $date;
78 $value->valueTime
= $time;
80 $formatted = $value->getFormattedDateFromDate(
85 list($value->valueDate
, $value->valueTime
) = $formatted;
88 $value->valueEnabled
= $request->getStr($key.'_e');
92 public static function newFromEpoch(PhabricatorUser
$viewer, $epoch) {
93 $value = new AphrontFormDateControlValue();
94 $value->viewer
= $viewer;
100 $readable = $value->formatTime($epoch, 'Y!m!d!g:i A');
101 $readable = explode('!', $readable, 4);
103 $year = $readable[0];
104 $month = $readable[1];
106 $time = $readable[3];
108 list($value->valueDate
, $value->valueTime
) =
109 $value->getFormattedDateFromParts(
118 public static function newFromDictionary(
119 PhabricatorUser
$viewer,
121 $value = new AphrontFormDateControlValue();
122 $value->viewer
= $viewer;
124 $value->valueDate
= idx($dictionary, 'd');
125 $value->valueTime
= idx($dictionary, 't');
127 $formatted = $value->getFormattedDateFromDate(
132 list($value->valueDate
, $value->valueTime
) = $formatted;
135 $value->valueEnabled
= idx($dictionary, 'e');
140 public static function newFromWild(PhabricatorUser
$viewer, $wild) {
141 if (is_array($wild)) {
142 return self
::newFromDictionary($viewer, $wild);
143 } else if (is_numeric($wild)) {
144 return self
::newFromEpoch($viewer, $wild);
148 'Unable to construct a date value from value of type "%s".',
153 public function getDictionary() {
155 'd' => $this->valueDate
,
156 't' => $this->valueTime
,
157 'e' => $this->valueEnabled
,
161 public function getValueAsFormat($format) {
162 return phabricator_format_local_time(
168 private function formatTime($epoch, $format) {
169 return phabricator_format_local_time(
175 public function getEpoch() {
176 if ($this->isDisabled()) {
180 $datetime = $this->newDateTime($this->valueDate
, $this->valueTime
);
185 return (int)$datetime->format('U');
188 private function getTimeFormat() {
189 $viewer = $this->getViewer();
190 $time_key = PhabricatorTimeFormatSetting
::SETTINGKEY
;
191 return $viewer->getUserSetting($time_key);
194 private function getDateFormat() {
195 $viewer = $this->getViewer();
196 $date_key = PhabricatorDateFormatSetting
::SETTINGKEY
;
197 return $viewer->getUserSetting($date_key);
200 private function getFormattedDateFromDate($date, $time) {
201 $datetime = $this->newDateTime($date, $time);
207 $datetime->format($this->getDateFormat()),
208 $datetime->format($this->getTimeFormat()),
211 return array($date, $time);
214 private function newDateTime($date, $time) {
215 $date = $this->getStandardDateFormat($date);
216 $time = $this->getStandardTimeFormat($time);
219 // We need to provide the timezone in the constructor, and also set it
220 // explicitly. If the date is an epoch timestamp, the timezone in the
221 // constructor is ignored. If the date is not an epoch timestamp, it is
222 // used to parse the date.
223 $zone = $this->getTimezone();
224 $datetime = new DateTime("{$date} {$time}", $zone);
225 $datetime->setTimezone($zone);
226 } catch (Exception
$ex) {
234 public function newPhutilDateTime() {
235 $datetime = $this->getDateTime();
240 $all_day = !strlen($this->valueTime
);
241 $zone_identifier = $this->viewer
->getTimezoneIdentifier();
243 $result = id(new PhutilCalendarAbsoluteDateTime())
244 ->setYear((int)$datetime->format('Y'))
245 ->setMonth((int)$datetime->format('m'))
246 ->setDay((int)$datetime->format('d'))
247 ->setHour((int)$datetime->format('G'))
248 ->setMinute((int)$datetime->format('i'))
249 ->setSecond((int)$datetime->format('s'))
250 ->setTimezone($zone_identifier);
253 $result->setIsAllDay(true);
260 private function getFormattedDateFromParts(
266 $zone = $this->getTimezone();
267 $date_time = id(new DateTime("{$year}-{$month}-{$day} {$time}", $zone));
270 $date_time->format($this->getDateFormat()),
271 $date_time->format($this->getTimeFormat()),
275 private function getFormatSeparator() {
276 $format = $this->getDateFormat();
285 public function getDateTime() {
286 return $this->newDateTime($this->valueDate
, $this->valueTime
);
289 private function getTimezone() {
294 $viewer_zone = $this->viewer
->getTimezoneIdentifier();
295 $this->zone
= new DateTimeZone($viewer_zone);
299 private function getStandardDateFormat($date) {
301 'newyear' => 'January 1',
302 'valentine' => 'February 14',
304 'christma' => 'December 25',
307 // Lowercase the input, then remove punctuation, a "day" suffix, and an
308 // "s" if one is present. This allows all of these to match. This allows
309 // variations like "New Year's Day" and "New Year" to both match.
310 $normalized = phutil_utf8_strtolower($date);
311 $normalized = preg_replace('/[^a-z]/', '', $normalized);
312 $normalized = preg_replace('/day\z/', '', $normalized);
313 $normalized = preg_replace('/s\z/', '', $normalized);
315 if (isset($colloquial[$normalized])) {
316 return $colloquial[$normalized];
319 // If this looks like an epoch timestamp, prefix it with "@" so that
320 // DateTime() reads it as one. Assume small numbers are a "Ymd" digit
321 // string instead of an epoch timestamp for a time in 1970.
322 if (ctype_digit($date) && ($date > 30000000)) {
326 $separator = $this->getFormatSeparator();
327 $parts = preg_split('@[,./:-]@', $date);
328 return implode($separator, $parts);
331 private function getStandardTimeFormat($time) {
333 'crack of dawn' => '5:00 AM',
335 'early' => '7:00 AM',
336 'morning' => '8:00 AM',
337 'elevenses' => '11:00 AM',
338 'morning tea' => '11:00 AM',
339 'noon' => '12:00 PM',
340 'high noon' => '12:00 PM',
341 'lunch' => '12:00 PM',
342 'afternoon' => '2:00 PM',
343 'tea time' => '3:00 PM',
344 'evening' => '7:00 PM',
345 'late' => '11:00 PM',
346 'witching hour' => '12:00 AM',
347 'midnight' => '12:00 AM',
350 $normalized = phutil_utf8_strtolower($time);
351 if (isset($colloquial[$normalized])) {
352 $time = $colloquial[$normalized];