Path: blob/master/src/view/form/control/AphrontFormDateControlValue.php
12262 views
<?php12final class AphrontFormDateControlValue extends Phobject {34private $valueDate;5private $valueTime;6private $valueEnabled;78private $viewer;9private $zone;10private $optional;1112public function getValueDate() {13return $this->valueDate;14}1516public function getValueTime() {17return $this->valueTime;18}1920public function isValid() {21if ($this->isDisabled()) {22return true;23}24return ($this->getEpoch() !== null);25}2627public function isEmpty() {28if ($this->valueDate) {29return false;30}3132if ($this->valueTime) {33return false;34}3536return true;37}3839public function isDisabled() {40return ($this->optional && !$this->valueEnabled);41}4243public function setEnabled($enabled) {44$this->valueEnabled = $enabled;45return $this;46}4748public function setOptional($optional) {49$this->optional = $optional;50return $this;51}5253public function getOptional() {54return $this->optional;55}5657public function getViewer() {58return $this->viewer;59}6061public static function newFromRequest(AphrontRequest $request, $key) {62$value = new AphrontFormDateControlValue();63$value->viewer = $request->getViewer();6465$date = $request->getStr($key.'_d');66$time = $request->getStr($key.'_t');6768// If we have the individual parts, we read them preferentially. If we do69// not, try to read the key as a raw value. This makes it so that HTTP70// prefilling is overwritten by the control value if the user changes it.71if (!strlen($date) && !strlen($time)) {72$date = $request->getStr($key);73$time = null;74}7576$value->valueDate = $date;77$value->valueTime = $time;7879$formatted = $value->getFormattedDateFromDate(80$value->valueDate,81$value->valueTime);8283if ($formatted) {84list($value->valueDate, $value->valueTime) = $formatted;85}8687$value->valueEnabled = $request->getStr($key.'_e');88return $value;89}9091public static function newFromEpoch(PhabricatorUser $viewer, $epoch) {92$value = new AphrontFormDateControlValue();93$value->viewer = $viewer;9495if (!$epoch) {96return $value;97}9899$readable = $value->formatTime($epoch, 'Y!m!d!g:i A');100$readable = explode('!', $readable, 4);101102$year = $readable[0];103$month = $readable[1];104$day = $readable[2];105$time = $readable[3];106107list($value->valueDate, $value->valueTime) =108$value->getFormattedDateFromParts(109$year,110$month,111$day,112$time);113114return $value;115}116117public static function newFromDictionary(118PhabricatorUser $viewer,119array $dictionary) {120$value = new AphrontFormDateControlValue();121$value->viewer = $viewer;122123$value->valueDate = idx($dictionary, 'd');124$value->valueTime = idx($dictionary, 't');125126$formatted = $value->getFormattedDateFromDate(127$value->valueDate,128$value->valueTime);129130if ($formatted) {131list($value->valueDate, $value->valueTime) = $formatted;132}133134$value->valueEnabled = idx($dictionary, 'e');135136return $value;137}138139public static function newFromWild(PhabricatorUser $viewer, $wild) {140if (is_array($wild)) {141return self::newFromDictionary($viewer, $wild);142} else if (is_numeric($wild)) {143return self::newFromEpoch($viewer, $wild);144} else {145throw new Exception(146pht(147'Unable to construct a date value from value of type "%s".',148gettype($wild)));149}150}151152public function getDictionary() {153return array(154'd' => $this->valueDate,155't' => $this->valueTime,156'e' => $this->valueEnabled,157);158}159160public function getValueAsFormat($format) {161return phabricator_format_local_time(162$this->getEpoch(),163$this->viewer,164$format);165}166167private function formatTime($epoch, $format) {168return phabricator_format_local_time(169$epoch,170$this->viewer,171$format);172}173174public function getEpoch() {175if ($this->isDisabled()) {176return null;177}178179$datetime = $this->newDateTime($this->valueDate, $this->valueTime);180if (!$datetime) {181return null;182}183184return (int)$datetime->format('U');185}186187private function getTimeFormat() {188$viewer = $this->getViewer();189$time_key = PhabricatorTimeFormatSetting::SETTINGKEY;190return $viewer->getUserSetting($time_key);191}192193private function getDateFormat() {194$viewer = $this->getViewer();195$date_key = PhabricatorDateFormatSetting::SETTINGKEY;196return $viewer->getUserSetting($date_key);197}198199private function getFormattedDateFromDate($date, $time) {200$datetime = $this->newDateTime($date, $time);201if (!$datetime) {202return null;203}204205return array(206$datetime->format($this->getDateFormat()),207$datetime->format($this->getTimeFormat()),208);209210return array($date, $time);211}212213private function newDateTime($date, $time) {214$date = $this->getStandardDateFormat($date);215$time = $this->getStandardTimeFormat($time);216217try {218// We need to provide the timezone in the constructor, and also set it219// explicitly. If the date is an epoch timestamp, the timezone in the220// constructor is ignored. If the date is not an epoch timestamp, it is221// used to parse the date.222$zone = $this->getTimezone();223$datetime = new DateTime("{$date} {$time}", $zone);224$datetime->setTimezone($zone);225} catch (Exception $ex) {226return null;227}228229230return $datetime;231}232233public function newPhutilDateTime() {234$datetime = $this->getDateTime();235if (!$datetime) {236return null;237}238239$all_day = !strlen($this->valueTime);240$zone_identifier = $this->viewer->getTimezoneIdentifier();241242$result = id(new PhutilCalendarAbsoluteDateTime())243->setYear((int)$datetime->format('Y'))244->setMonth((int)$datetime->format('m'))245->setDay((int)$datetime->format('d'))246->setHour((int)$datetime->format('G'))247->setMinute((int)$datetime->format('i'))248->setSecond((int)$datetime->format('s'))249->setTimezone($zone_identifier);250251if ($all_day) {252$result->setIsAllDay(true);253}254255return $result;256}257258259private function getFormattedDateFromParts(260$year,261$month,262$day,263$time) {264265$zone = $this->getTimezone();266$date_time = id(new DateTime("{$year}-{$month}-{$day} {$time}", $zone));267268return array(269$date_time->format($this->getDateFormat()),270$date_time->format($this->getTimeFormat()),271);272}273274private function getFormatSeparator() {275$format = $this->getDateFormat();276switch ($format) {277case 'n/j/Y':278return '/';279default:280return '-';281}282}283284public function getDateTime() {285return $this->newDateTime($this->valueDate, $this->valueTime);286}287288private function getTimezone() {289if ($this->zone) {290return $this->zone;291}292293$viewer_zone = $this->viewer->getTimezoneIdentifier();294$this->zone = new DateTimeZone($viewer_zone);295return $this->zone;296}297298private function getStandardDateFormat($date) {299$colloquial = array(300'newyear' => 'January 1',301'valentine' => 'February 14',302'pi' => 'March 14',303'christma' => 'December 25',304);305306// Lowercase the input, then remove punctuation, a "day" suffix, and an307// "s" if one is present. This allows all of these to match. This allows308// variations like "New Year's Day" and "New Year" to both match.309$normalized = phutil_utf8_strtolower($date);310$normalized = preg_replace('/[^a-z]/', '', $normalized);311$normalized = preg_replace('/day\z/', '', $normalized);312$normalized = preg_replace('/s\z/', '', $normalized);313314if (isset($colloquial[$normalized])) {315return $colloquial[$normalized];316}317318// If this looks like an epoch timestamp, prefix it with "@" so that319// DateTime() reads it as one. Assume small numbers are a "Ymd" digit320// string instead of an epoch timestamp for a time in 1970.321if (ctype_digit($date) && ($date > 30000000)) {322$date = '@'.$date;323}324325$separator = $this->getFormatSeparator();326$parts = preg_split('@[,./:-]@', $date);327return implode($separator, $parts);328}329330private function getStandardTimeFormat($time) {331$colloquial = array(332'crack of dawn' => '5:00 AM',333'dawn' => '6:00 AM',334'early' => '7:00 AM',335'morning' => '8:00 AM',336'elevenses' => '11:00 AM',337'morning tea' => '11:00 AM',338'noon' => '12:00 PM',339'high noon' => '12:00 PM',340'lunch' => '12:00 PM',341'afternoon' => '2:00 PM',342'tea time' => '3:00 PM',343'evening' => '7:00 PM',344'late' => '11:00 PM',345'witching hour' => '12:00 AM',346'midnight' => '12:00 AM',347);348349$normalized = phutil_utf8_strtolower($time);350if (isset($colloquial[$normalized])) {351$time = $colloquial[$normalized];352}353354return $time;355}356357}358359360