Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/view/form/control/AphrontFormDateControlValue.php
12262 views
1
<?php
2
3
final class AphrontFormDateControlValue extends Phobject {
4
5
private $valueDate;
6
private $valueTime;
7
private $valueEnabled;
8
9
private $viewer;
10
private $zone;
11
private $optional;
12
13
public function getValueDate() {
14
return $this->valueDate;
15
}
16
17
public function getValueTime() {
18
return $this->valueTime;
19
}
20
21
public function isValid() {
22
if ($this->isDisabled()) {
23
return true;
24
}
25
return ($this->getEpoch() !== null);
26
}
27
28
public function isEmpty() {
29
if ($this->valueDate) {
30
return false;
31
}
32
33
if ($this->valueTime) {
34
return false;
35
}
36
37
return true;
38
}
39
40
public function isDisabled() {
41
return ($this->optional && !$this->valueEnabled);
42
}
43
44
public function setEnabled($enabled) {
45
$this->valueEnabled = $enabled;
46
return $this;
47
}
48
49
public function setOptional($optional) {
50
$this->optional = $optional;
51
return $this;
52
}
53
54
public function getOptional() {
55
return $this->optional;
56
}
57
58
public function getViewer() {
59
return $this->viewer;
60
}
61
62
public static function newFromRequest(AphrontRequest $request, $key) {
63
$value = new AphrontFormDateControlValue();
64
$value->viewer = $request->getViewer();
65
66
$date = $request->getStr($key.'_d');
67
$time = $request->getStr($key.'_t');
68
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);
74
$time = null;
75
}
76
77
$value->valueDate = $date;
78
$value->valueTime = $time;
79
80
$formatted = $value->getFormattedDateFromDate(
81
$value->valueDate,
82
$value->valueTime);
83
84
if ($formatted) {
85
list($value->valueDate, $value->valueTime) = $formatted;
86
}
87
88
$value->valueEnabled = $request->getStr($key.'_e');
89
return $value;
90
}
91
92
public static function newFromEpoch(PhabricatorUser $viewer, $epoch) {
93
$value = new AphrontFormDateControlValue();
94
$value->viewer = $viewer;
95
96
if (!$epoch) {
97
return $value;
98
}
99
100
$readable = $value->formatTime($epoch, 'Y!m!d!g:i A');
101
$readable = explode('!', $readable, 4);
102
103
$year = $readable[0];
104
$month = $readable[1];
105
$day = $readable[2];
106
$time = $readable[3];
107
108
list($value->valueDate, $value->valueTime) =
109
$value->getFormattedDateFromParts(
110
$year,
111
$month,
112
$day,
113
$time);
114
115
return $value;
116
}
117
118
public static function newFromDictionary(
119
PhabricatorUser $viewer,
120
array $dictionary) {
121
$value = new AphrontFormDateControlValue();
122
$value->viewer = $viewer;
123
124
$value->valueDate = idx($dictionary, 'd');
125
$value->valueTime = idx($dictionary, 't');
126
127
$formatted = $value->getFormattedDateFromDate(
128
$value->valueDate,
129
$value->valueTime);
130
131
if ($formatted) {
132
list($value->valueDate, $value->valueTime) = $formatted;
133
}
134
135
$value->valueEnabled = idx($dictionary, 'e');
136
137
return $value;
138
}
139
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);
145
} else {
146
throw new Exception(
147
pht(
148
'Unable to construct a date value from value of type "%s".',
149
gettype($wild)));
150
}
151
}
152
153
public function getDictionary() {
154
return array(
155
'd' => $this->valueDate,
156
't' => $this->valueTime,
157
'e' => $this->valueEnabled,
158
);
159
}
160
161
public function getValueAsFormat($format) {
162
return phabricator_format_local_time(
163
$this->getEpoch(),
164
$this->viewer,
165
$format);
166
}
167
168
private function formatTime($epoch, $format) {
169
return phabricator_format_local_time(
170
$epoch,
171
$this->viewer,
172
$format);
173
}
174
175
public function getEpoch() {
176
if ($this->isDisabled()) {
177
return null;
178
}
179
180
$datetime = $this->newDateTime($this->valueDate, $this->valueTime);
181
if (!$datetime) {
182
return null;
183
}
184
185
return (int)$datetime->format('U');
186
}
187
188
private function getTimeFormat() {
189
$viewer = $this->getViewer();
190
$time_key = PhabricatorTimeFormatSetting::SETTINGKEY;
191
return $viewer->getUserSetting($time_key);
192
}
193
194
private function getDateFormat() {
195
$viewer = $this->getViewer();
196
$date_key = PhabricatorDateFormatSetting::SETTINGKEY;
197
return $viewer->getUserSetting($date_key);
198
}
199
200
private function getFormattedDateFromDate($date, $time) {
201
$datetime = $this->newDateTime($date, $time);
202
if (!$datetime) {
203
return null;
204
}
205
206
return array(
207
$datetime->format($this->getDateFormat()),
208
$datetime->format($this->getTimeFormat()),
209
);
210
211
return array($date, $time);
212
}
213
214
private function newDateTime($date, $time) {
215
$date = $this->getStandardDateFormat($date);
216
$time = $this->getStandardTimeFormat($time);
217
218
try {
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) {
227
return null;
228
}
229
230
231
return $datetime;
232
}
233
234
public function newPhutilDateTime() {
235
$datetime = $this->getDateTime();
236
if (!$datetime) {
237
return null;
238
}
239
240
$all_day = !strlen($this->valueTime);
241
$zone_identifier = $this->viewer->getTimezoneIdentifier();
242
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);
251
252
if ($all_day) {
253
$result->setIsAllDay(true);
254
}
255
256
return $result;
257
}
258
259
260
private function getFormattedDateFromParts(
261
$year,
262
$month,
263
$day,
264
$time) {
265
266
$zone = $this->getTimezone();
267
$date_time = id(new DateTime("{$year}-{$month}-{$day} {$time}", $zone));
268
269
return array(
270
$date_time->format($this->getDateFormat()),
271
$date_time->format($this->getTimeFormat()),
272
);
273
}
274
275
private function getFormatSeparator() {
276
$format = $this->getDateFormat();
277
switch ($format) {
278
case 'n/j/Y':
279
return '/';
280
default:
281
return '-';
282
}
283
}
284
285
public function getDateTime() {
286
return $this->newDateTime($this->valueDate, $this->valueTime);
287
}
288
289
private function getTimezone() {
290
if ($this->zone) {
291
return $this->zone;
292
}
293
294
$viewer_zone = $this->viewer->getTimezoneIdentifier();
295
$this->zone = new DateTimeZone($viewer_zone);
296
return $this->zone;
297
}
298
299
private function getStandardDateFormat($date) {
300
$colloquial = array(
301
'newyear' => 'January 1',
302
'valentine' => 'February 14',
303
'pi' => 'March 14',
304
'christma' => 'December 25',
305
);
306
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);
314
315
if (isset($colloquial[$normalized])) {
316
return $colloquial[$normalized];
317
}
318
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)) {
323
$date = '@'.$date;
324
}
325
326
$separator = $this->getFormatSeparator();
327
$parts = preg_split('@[,./:-]@', $date);
328
return implode($separator, $parts);
329
}
330
331
private function getStandardTimeFormat($time) {
332
$colloquial = array(
333
'crack of dawn' => '5:00 AM',
334
'dawn' => '6: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',
348
);
349
350
$normalized = phutil_utf8_strtolower($time);
351
if (isset($colloquial[$normalized])) {
352
$time = $colloquial[$normalized];
353
}
354
355
return $time;
356
}
357
358
}
359
360