]> git.mxchange.org Git - friendica.git/blob - src/Util/Temporal.php
formating
[friendica.git] / src / Util / Temporal.php
1 <?php
2
3 /**
4  * @file src/Util/Temporal.php
5  */
6
7 namespace Friendica\Util;
8
9 use DateTime;
10 use DateTimeZone;
11 use Friendica\Core\Renderer;
12 use Friendica\Database\DBA;
13 use Friendica\DI;
14
15 /**
16  * Temporal class
17  */
18 class Temporal
19 {
20         /**
21          * Two-level sort for timezones.
22          *
23          * @param string $a
24          * @param string $b
25          * @return int
26          */
27         private static function timezoneCompareCallback($a, $b)
28         {
29                 if (strstr($a, '/') && strstr($b, '/')) {
30                         if (DI::l10n()->t($a) == DI::l10n()->t($b)) {
31                                 return 0;
32                         }
33                         return (DI::l10n()->t($a) < DI::l10n()->t($b)) ? -1 : 1;
34                 }
35
36                 if (strstr($a, '/')) {
37                         return -1;
38                 } elseif (strstr($b, '/')) {
39                         return 1;
40                 } elseif (DI::l10n()->t($a) == DI::l10n()->t($b)) {
41                         return 0;
42                 }
43
44                 return (DI::l10n()->t($a) < DI::l10n()->t($b)) ? -1 : 1;
45         }
46
47         /**
48          * Emit a timezone selector grouped (primarily) by continent
49          *
50          * @param string $current Timezone
51          * @return string Parsed HTML output
52          */
53         public static function getTimezoneSelect($current = 'America/Los_Angeles')
54         {
55                 $timezone_identifiers = DateTimeZone::listIdentifiers();
56
57                 $o = '<select id="timezone_select" name="timezone">';
58
59                 usort($timezone_identifiers, [__CLASS__, 'timezoneCompareCallback']);
60                 $continent = '';
61                 foreach ($timezone_identifiers as $value) {
62                         $ex = explode("/", $value);
63                         if (count($ex) > 1) {
64                                 if ($ex[0] != $continent) {
65                                         if ($continent != '') {
66                                                 $o .= '</optgroup>';
67                                         }
68                                         $continent = $ex[0];
69                                         $o .= '<optgroup label="' . DI::l10n()->t($continent) . '">';
70                                 }
71                                 if (count($ex) > 2) {
72                                         $city = substr($value, strpos($value, '/') + 1);
73                                 } else {
74                                         $city = $ex[1];
75                                 }
76                         } else {
77                                 $city = $ex[0];
78                                 if ($continent != DI::l10n()->t('Miscellaneous')) {
79                                         $o .= '</optgroup>';
80                                         $continent = DI::l10n()->t('Miscellaneous');
81                                         $o .= '<optgroup label="' . DI::l10n()->t($continent) . '">';
82                                 }
83                         }
84                         $city = str_replace('_', ' ', DI::l10n()->t($city));
85                         $selected = (($value == $current) ? " selected=\"selected\" " : "");
86                         $o .= "<option value=\"$value\" $selected >$city</option>";
87                 }
88                 $o .= '</optgroup></select>';
89                 return $o;
90         }
91
92         /**
93          * Generating a Timezone selector
94          *
95          * Return a select using 'field_select_raw' template, with timezones
96          * grouped (primarily) by continent
97          * arguments follow convention as other field_* template array:
98          * 'name', 'label', $value, 'help'
99          *
100          * @param string $name    Name of the selector
101          * @param string $label   Label for the selector
102          * @param string $current Timezone
103          * @param string $help    Help text
104          *
105          * @return string Parsed HTML
106          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
107          */
108         public static function getTimezoneField($name = 'timezone', $label = '', $current = 'America/Los_Angeles', $help = '')
109         {
110                 $options = self::getTimezoneSelect($current);
111                 $options = str_replace('<select id="timezone_select" name="timezone">', '', $options);
112                 $options = str_replace('</select>', '', $options);
113
114                 $tpl = Renderer::getMarkupTemplate('field_select_raw.tpl');
115                 return Renderer::replaceMacros($tpl, [
116                         '$field' => [$name, $label, $current, $help, $options],
117                 ]);
118         }
119
120         /**
121          * Wrapper for date selector, tailored for use in birthday fields.
122          *
123          * @param string $dob Date of Birth
124          * @param string $timezone
125          * @return string Formatted HTML
126          * @throws \Exception
127          */
128         public static function getDateofBirthField(string $dob, string $timezone = 'UTC')
129         {
130                 list($year, $month, $day) = sscanf($dob, '%4d-%2d-%2d');
131
132                 if ($dob < '0000-01-01') {
133                         $value = '';
134                         $age = 0;
135                 } elseif ($dob < '0001-00-00') {
136                         $value = substr($dob, 5);
137                         $age = 0;
138                 } else {
139                         $value = DateTimeFormat::utc($dob, 'Y-m-d');
140                         $age = self::getAgeByTimezone($value, $timezone);
141                 }
142
143                 $tpl = Renderer::getMarkupTemplate("field_input.tpl");
144                 $o = Renderer::replaceMacros($tpl,
145                         [
146                         '$field' => [
147                                 'dob',
148                                 DI::l10n()->t('Birthday:'),
149                                 $value,
150                                 intval($age) > 0 ? DI::l10n()->t('Age: ') . DI::l10n()->tt('%d year old', '%d years old', $age) : '',
151                                 '',
152                                 'placeholder="' . DI::l10n()->t('YYYY-MM-DD or MM-DD') . '"'
153                         ]
154                 ]);
155
156                 return $o;
157         }
158
159         /**
160          * Returns a date selector
161          *
162          * @param DateTime $min     Minimum date
163          * @param DateTime $max     Maximum date
164          * @param DateTime $default Default date
165          * @param string   $id      ID and name of datetimepicker (defaults to "datetimepicker")
166          *
167          * @return string Parsed HTML output.
168          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
169          */
170         public static function getDateField($min, $max, $default, $id = 'datepicker')
171         {
172                 return self::getDateTimeField($min, $max, $default, '', $id, true, false, '', '');
173         }
174
175         /**
176          * Returns a time selector
177          *
178          * @param string $h  Already selected hour
179          * @param string $m  Already selected minute
180          * @param string $id ID and name of datetimepicker (defaults to "timepicker")
181          *
182          * @return string Parsed HTML output.
183          * @throws \Exception
184          */
185         public static function getTimeField($h, $m, $id = 'timepicker')
186         {
187                 return self::getDateTimeField(new DateTime(), new DateTime(), new DateTime("$h:$m"), '', $id, false, true);
188         }
189
190         /**
191          * Returns a datetime selector.
192          *
193          * @param DateTime $minDate     Minimum date
194          * @param DateTime $maxDate     Maximum date
195          * @param DateTime $defaultDate Default date
196          * @param          $label
197          * @param string   $id          Id and name of datetimepicker (defaults to "datetimepicker")
198          * @param bool     $pickdate    true to show date picker (default)
199          * @param bool     $picktime    true to show time picker (default)
200          * @param string   $minfrom     set minimum date from picker with id $minfrom (none by default)
201          * @param string   $maxfrom     set maximum date from picker with id $maxfrom (none by default)
202          * @param bool     $required    default false
203          *
204          * @return string Parsed HTML output.
205          *
206          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
207          * @todo  Once browser support is better this could probably be replaced with
208          * native HTML5 date picker.
209          */
210         public static function getDateTimeField(
211                 DateTime $minDate,
212                 DateTime $maxDate,
213                 DateTime $defaultDate,
214                 $label,
215                 $id       = 'datetimepicker',
216                 $pickdate = true,
217                 $picktime = true,
218                 $minfrom  = '',
219                 $maxfrom  = '',
220                 $required = false)
221         {
222                 // First day of the week (0 = Sunday)
223                 $firstDay = DI::pConfig()->get(local_user(), 'system', 'first_day_of_week', 0);
224
225                 $lang = substr(DI::l10n()->getCurrentLang(), 0, 2);
226
227                 // Check if the detected language is supported by the picker
228                 if (!in_array($lang,
229                                 ["ar", "ro", "id", "bg", "fa", "ru", "uk", "en", "el", "de", "nl", "tr", "fr", "es", "th", "pl", "pt", "ch", "se", "kr",
230                                 "it", "da", "no", "ja", "vi", "sl", "cs", "hu"])) {
231                         $lang = 'en';
232                 }
233
234                 $o = '';
235                 $dateformat = '';
236
237                 if ($pickdate) {
238                         $dateformat .= 'Y-m-d';
239                 }
240
241                 if ($pickdate && $picktime) {
242                         $dateformat .= ' ';
243                 }
244
245                 if ($picktime) {
246                         $dateformat .= 'H:i';
247                 }
248
249                 $input_text = $defaultDate ? date($dateformat, $defaultDate->getTimestamp()) : '';
250
251                 $readable_format = str_replace(['Y', 'm', 'd', 'H', 'i'], ['yyyy', 'mm', 'dd', 'HH', 'MM'], $dateformat);
252
253                 $tpl = Renderer::getMarkupTemplate('field_datetime.tpl');
254                 $o .= Renderer::replaceMacros($tpl, [
255                         '$field' => [
256                                 $id,
257                                 $label,
258                                 $input_text,
259                                 '',
260                                 $required ? '*' : '',
261                                 'placeholder="' . $readable_format . '"'
262                         ],
263                         '$datetimepicker' => [
264                                 'minDate' => $minDate,
265                                 'maxDate' => $maxDate,
266                                 'defaultDate' => $defaultDate,
267                                 'dateformat' => $dateformat,
268                                 'firstDay' => $firstDay,
269                                 'lang' => $lang,
270                                 'minfrom' => $minfrom,
271                                 'maxfrom' => $maxfrom,
272                         ]
273                 ]);
274
275                 return $o;
276         }
277
278         /**
279          * Returns a relative date string.
280          *
281          * Implements "3 seconds ago" etc.
282          * Based on $posted_date, (UTC).
283          * Results relative to current timezone.
284          * Limited to range of timestamps.
285          *
286          * @param string $posted_date MySQL-formatted date string (YYYY-MM-DD HH:MM:SS)
287          * @param string $format (optional) Parsed with sprintf()
288          *    <tt>%1$d %2$s ago</tt>, e.g. 22 hours ago, 1 minute ago
289          *
290          * @return string with relative date
291          */
292         public static function getRelativeDate($posted_date, $format = null)
293         {
294                 $localtime = $posted_date . ' UTC';
295
296                 $abs = strtotime($localtime);
297
298                 if (is_null($posted_date) || $posted_date <= DBA::NULL_DATETIME || $abs === false) {
299                         return DI::l10n()->t('never');
300                 }
301
302                 $isfuture = false;
303                 $etime = time() - $abs;
304
305                 if ($etime < 1 && $etime >= 0) {
306                         return DI::l10n()->t('less than a second ago');
307                 }
308
309                 if ($etime < 0){
310                         $etime = -$etime;
311                         $isfuture = true;
312                 }
313
314                 $a = [12 * 30 * 24 * 60 * 60 => [DI::l10n()->t('year'), DI::l10n()->t('years')],
315                         30 * 24 * 60 * 60 => [DI::l10n()->t('month'), DI::l10n()->t('months')],
316                         7 * 24 * 60 * 60 => [DI::l10n()->t('week'), DI::l10n()->t('weeks')],
317                         24 * 60 * 60 => [DI::l10n()->t('day'), DI::l10n()->t('days')],
318                         60 * 60 => [DI::l10n()->t('hour'), DI::l10n()->t('hours')],
319                         60 => [DI::l10n()->t('minute'), DI::l10n()->t('minutes')],
320                         1 => [DI::l10n()->t('second'), DI::l10n()->t('seconds')]
321                 ];
322
323                 foreach ($a as $secs => $str) {
324                         $d = $etime / $secs;
325                         if ($d >= 1) {
326                                 $r = round($d);
327                                 // translators - e.g. 22 hours ago, 1 minute ago
328                                 if (!$format) {
329                                         if($isfuture){
330                                                 $format = DI::l10n()->t('in %1$d %2$s');
331                                         }
332                                         else {
333                                                 $format = DI::l10n()->t('%1$d %2$s ago');
334                                         }
335                                 }
336
337                                 return sprintf($format, $r, (($r == 1) ? $str[0] : $str[1]));
338                         }
339                 }
340         }
341
342         /**
343          * Returns timezone correct age in years.
344          *
345          * Returns the age in years, given a date of birth and the timezone of the person
346          * whose date of birth is provided.
347          *
348          * @param string $dob       Date of Birth
349          * @param string $owner_tz  (optional) Timezone of the person of interest
350          *
351          * @return int Age in years
352          * @throws \Exception
353          */
354         public static function getAgeByTimezone($dob, $owner_tz = '')
355         {
356                 if (!intval($dob)) {
357                         return 0;
358                 }
359
360                 if (!$owner_tz) {
361                         $owner_tz = date_default_timezone_get();
362                 }
363
364                 $birthdate = new DateTime($dob . ' 00:00:00', new DateTimeZone($owner_tz));
365                 $currentDate = new DateTime('now', new DateTimeZone('UTC'));
366
367                 $interval = $birthdate->diff($currentDate);
368
369                 return $interval->format('%y');
370         }
371
372         /**
373          * Get days of a month in a given year.
374          *
375          * Returns number of days in the month of the given year.
376          * $m = 1 is 'January' to match human usage.
377          *
378          * @param int $y Year
379          * @param int $m Month (1=January, 12=December)
380          *
381          * @return int Number of days in the given month
382          */
383         public static function getDaysInMonth($y, $m)
384         {
385                 return date('t', mktime(0, 0, 0, $m, 1, $y));
386         }
387
388         /**
389          * Returns the first day in month for a given month, year.
390          *
391          * Months start at 1.
392          *
393          * @param int $y Year
394          * @param int $m Month (1=January, 12=December)
395          *
396          * @return string day 0 = Sunday through 6 = Saturday
397          * @throws \Exception
398          */
399         private static function getFirstDayInMonth($y, $m)
400         {
401                 $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m));
402
403                 return DateTimeFormat::utc($d, 'w');
404         }
405
406         /**
407          * Output a calendar for the given month, year.
408          *
409          * If $links are provided (array), e.g. $links[12] => 'http://mylink' ,
410          * date 12 will be linked appropriately. Today's date is also noted by
411          * altering td class.
412          * Months count from 1.
413          *
414          * @param int    $y     Year
415          * @param int    $m     Month
416          * @param array  $links (default null)
417          * @param string $class
418          *
419          * @return string
420          *
421          * @throws \Exception
422          * @todo  Provide (prev, next) links, define class variations for different size calendars
423          */
424         public static function getCalendarTable($y = 0, $m = 0, $links = null, $class = '')
425         {
426                 // month table - start at 1 to match human usage.
427                 $mtab = [' ',
428                         'January', 'February', 'March',
429                         'April', 'May', 'June',
430                         'July', 'August', 'September',
431                         'October', 'November', 'December'
432                 ];
433
434                 $thisyear = DateTimeFormat::localNow('Y');
435                 $thismonth = DateTimeFormat::localNow('m');
436                 if (!$y) {
437                         $y = $thisyear;
438                 }
439
440                 if (!$m) {
441                         $m = intval($thismonth);
442                 }
443
444                 $dn = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
445                 $f = self::getFirstDayInMonth($y, $m);
446                 $l = self::getDaysInMonth($y, $m);
447                 $d = 1;
448                 $dow = 0;
449                 $started = false;
450
451                 if (($y == $thisyear) && ($m == $thismonth)) {
452                         $tddate = intval(DateTimeFormat::localNow('j'));
453                 }
454
455                 $str_month = DI::l10n()->getDay($mtab[$m]);
456                 $o = '<table class="calendar' . $class . '">';
457                 $o .= "<caption>$str_month $y</caption><tr>";
458                 for ($a = 0; $a < 7; $a ++) {
459                         $o .= '<th>' . mb_substr(DI::l10n()->getDay($dn[$a]), 0, 3, 'UTF-8') . '</th>';
460                 }
461
462                 $o .= '</tr><tr>';
463
464                 while ($d <= $l) {
465                         if (($dow == $f) && (!$started)) {
466                                 $started = true;
467                         }
468
469                         $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : '');
470                         $o .= "<td $today>";
471                         $day = str_replace(' ', '&nbsp;', sprintf('%2.2d', $d));
472                         if ($started) {
473                                 if (isset($links[$d])) {
474                                         $o .= "<a href=\"{$links[$d]}\">$day</a>";
475                                 } else {
476                                         $o .= $day;
477                                 }
478
479                                 $d ++;
480                         } else {
481                                 $o .= '&nbsp;';
482                         }
483
484                         $o .= '</td>';
485                         $dow ++;
486                         if (($dow == 7) && ($d <= $l)) {
487                                 $dow = 0;
488                                 $o .= '</tr><tr>';
489                         }
490                 }
491
492                 if ($dow) {
493                         for ($a = $dow; $a < 7; $a ++) {
494                                 $o .= '<td>&nbsp;</td>';
495                         }
496                 }
497
498                 $o .= '</tr></table>' . "\r\n";
499
500                 return $o;
501         }
502 }