]> git.mxchange.org Git - friendica.git/blob - src/Util/Temporal.php
Move L10n::getDay() calls to DI::l10n()->getDay() calls
[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\L10n;
12 use Friendica\Core\Renderer;
13 use Friendica\Database\DBA;
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                 } else {
135                         $value = DateTimeFormat::utc(($year > 1000) ? $dob : '1000-' . $month . '-' . $day, 'Y-m-d');
136                 }
137
138                 $age = (intval($value) ? self::getAgeByTimezone($value, $timezone, $timezone) : "");
139
140                 $tpl = Renderer::getMarkupTemplate("field_input.tpl");
141                 $o = Renderer::replaceMacros($tpl,
142                         [
143                         '$field' => [
144                                 'dob',
145                                 DI::l10n()->t('Birthday:'),
146                                 $value,
147                                 intval($age) > 0 ? DI::l10n()->t('Age: ') . $age : "",
148                                 '',
149                                 'placeholder="' . DI::l10n()->t('YYYY-MM-DD or MM-DD') . '"'
150                         ]
151                 ]);
152
153                 return $o;
154         }
155
156         /**
157          * Returns a date selector
158          *
159          * @param DateTime $min     Minimum date
160          * @param DateTime $max     Maximum date
161          * @param DateTime $default Default date
162          * @param string   $id      ID and name of datetimepicker (defaults to "datetimepicker")
163          *
164          * @return string Parsed HTML output.
165          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
166          */
167         public static function getDateField($min, $max, $default, $id = 'datepicker')
168         {
169                 return self::getDateTimeField($min, $max, $default, '', $id, true, false, '', '');
170         }
171
172         /**
173          * Returns a time selector
174          *
175          * @param string $h  Already selected hour
176          * @param string $m  Already selected minute
177          * @param string $id ID and name of datetimepicker (defaults to "timepicker")
178          *
179          * @return string Parsed HTML output.
180          * @throws \Exception
181          */
182         public static function getTimeField($h, $m, $id = 'timepicker')
183         {
184                 return self::getDateTimeField(new DateTime(), new DateTime(), new DateTime("$h:$m"), '', $id, false, true);
185         }
186
187         /**
188          * Returns a datetime selector.
189          *
190          * @param DateTime $minDate     Minimum date
191          * @param DateTime $maxDate     Maximum date
192          * @param DateTime $defaultDate Default date
193          * @param          $label
194          * @param string   $id          Id and name of datetimepicker (defaults to "datetimepicker")
195          * @param bool     $pickdate    true to show date picker (default)
196          * @param bool     $picktime    true to show time picker (default)
197          * @param string   $minfrom     set minimum date from picker with id $minfrom (none by default)
198          * @param string   $maxfrom     set maximum date from picker with id $maxfrom (none by default)
199          * @param bool     $required    default false
200          *
201          * @return string Parsed HTML output.
202          *
203          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
204          * @todo  Once browser support is better this could probably be replaced with
205          * native HTML5 date picker.
206          */
207         public static function getDateTimeField(
208                 DateTime $minDate,
209                 DateTime $maxDate,
210                 DateTime $defaultDate,
211                 $label,
212                 $id       = 'datetimepicker',
213                 $pickdate = true,
214                 $picktime = true,
215                 $minfrom  = '',
216                 $maxfrom  = '',
217                 $required = false)
218         {
219                 // First day of the week (0 = Sunday)
220                 $firstDay = DI::pConfig()->get(local_user(), 'system', 'first_day_of_week', 0);
221
222                 $lang = substr(L10n::getCurrentLang(), 0, 2);
223
224                 // Check if the detected language is supported by the picker
225                 if (!in_array($lang,
226                                 ["ar", "ro", "id", "bg", "fa", "ru", "uk", "en", "el", "de", "nl", "tr", "fr", "es", "th", "pl", "pt", "ch", "se", "kr",
227                                 "it", "da", "no", "ja", "vi", "sl", "cs", "hu"])) {
228                         $lang = 'en';
229                 }
230
231                 $o = '';
232                 $dateformat = '';
233
234                 if ($pickdate) {
235                         $dateformat .= 'Y-m-d';
236                 }
237
238                 if ($pickdate && $picktime) {
239                         $dateformat .= ' ';
240                 }
241
242                 if ($picktime) {
243                         $dateformat .= 'H:i';
244                 }
245
246                 $input_text = $defaultDate ? date($dateformat, $defaultDate->getTimestamp()) : '';
247
248                 $readable_format = str_replace(['Y', 'm', 'd', 'H', 'i'], ['yyyy', 'mm', 'dd', 'HH', 'MM'], $dateformat);
249
250                 $tpl = Renderer::getMarkupTemplate('field_datetime.tpl');
251                 $o .= Renderer::replaceMacros($tpl, [
252                         '$field' => [
253                                 $id,
254                                 $label,
255                                 $input_text,
256                                 '',
257                                 $required ? '*' : '',
258                                 'placeholder="' . $readable_format . '"'
259                         ],
260                         '$datetimepicker' => [
261                                 'minDate' => $minDate,
262                                 'maxDate' => $maxDate,
263                                 'defaultDate' => $defaultDate,
264                                 'dateformat' => $dateformat,
265                                 'firstDay' => $firstDay,
266                                 'lang' => $lang,
267                                 'minfrom' => $minfrom,
268                                 'maxfrom' => $maxfrom,
269                         ]
270                 ]);
271
272                 return $o;
273         }
274
275         /**
276          * Returns a relative date string.
277          *
278          * Implements "3 seconds ago" etc.
279          * Based on $posted_date, (UTC).
280          * Results relative to current timezone.
281          * Limited to range of timestamps.
282          *
283          * @param string $posted_date MySQL-formatted date string (YYYY-MM-DD HH:MM:SS)
284          * @param string $format (optional) Parsed with sprintf()
285          *    <tt>%1$d %2$s ago</tt>, e.g. 22 hours ago, 1 minute ago
286          *
287          * @return string with relative date
288          */
289         public static function getRelativeDate($posted_date, $format = null)
290         {
291                 $localtime = $posted_date . ' UTC';
292
293                 $abs = strtotime($localtime);
294
295                 if (is_null($posted_date) || $posted_date <= DBA::NULL_DATETIME || $abs === false) {
296                         return DI::l10n()->t('never');
297                 }
298
299                 $isfuture = false;
300                 $etime = time() - $abs;
301
302                 if ($etime < 1 && $etime >= 0) {
303                         return DI::l10n()->t('less than a second ago');
304                 }
305
306                 if ($etime < 0){
307                         $etime = -$etime;
308                         $isfuture = true;
309                 }
310
311                 $a = [12 * 30 * 24 * 60 * 60 => [DI::l10n()->t('year'), DI::l10n()->t('years')],
312                         30 * 24 * 60 * 60 => [DI::l10n()->t('month'), DI::l10n()->t('months')],
313                         7 * 24 * 60 * 60 => [DI::l10n()->t('week'), DI::l10n()->t('weeks')],
314                         24 * 60 * 60 => [DI::l10n()->t('day'), DI::l10n()->t('days')],
315                         60 * 60 => [DI::l10n()->t('hour'), DI::l10n()->t('hours')],
316                         60 => [DI::l10n()->t('minute'), DI::l10n()->t('minutes')],
317                         1 => [DI::l10n()->t('second'), DI::l10n()->t('seconds')]
318                 ];
319
320                 foreach ($a as $secs => $str) {
321                         $d = $etime / $secs;
322                         if ($d >= 1) {
323                                 $r = round($d);
324                                 // translators - e.g. 22 hours ago, 1 minute ago
325                                 if (!$format) {
326                                         if($isfuture){
327                                                 $format = DI::l10n()->t('in %1$d %2$s');
328                                         }
329                                         else {
330                                                 $format = DI::l10n()->t('%1$d %2$s ago');
331                                         }
332                                 }
333
334                                 return sprintf($format, $r, (($r == 1) ? $str[0] : $str[1]));
335                         }
336                 }
337         }
338
339         /**
340          * Returns timezone correct age in years.
341          *
342          * Returns the age in years, given a date of birth, the timezone of the person
343          * whose date of birth is provided, and the timezone of the person viewing the
344          * result.
345          *
346          * Why? Bear with me. Let's say I live in Mittagong, Australia, and my birthday
347          * is on New Year's. You live in San Bruno, California.
348          * When exactly are you going to see my age increase?
349          *
350          * A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start celebrating
351          * and become a year older. If you wish me happy birthday on January 1
352          * (San Bruno time), you'll be a day late.
353          *
354          * @param string $dob       Date of Birth
355          * @param string $owner_tz  (optional) Timezone of the person of interest
356          * @param string $viewer_tz (optional) Timezone of the person viewing
357          *
358          * @return int Age in years
359          * @throws \Exception
360          */
361         public static function getAgeByTimezone($dob, $owner_tz = '', $viewer_tz = '')
362         {
363                 if (!intval($dob)) {
364                         return 0;
365                 }
366                 if (!$owner_tz) {
367                         $owner_tz = date_default_timezone_get();
368                 }
369                 if (!$viewer_tz) {
370                         $viewer_tz = date_default_timezone_get();
371                 }
372
373                 $birthdate = DateTimeFormat::convert($dob . ' 00:00:00+00:00', $owner_tz, 'UTC', 'Y-m-d');
374                 list($year, $month, $day) = explode("-", $birthdate);
375                 $year_diff  = DateTimeFormat::timezoneNow($viewer_tz, 'Y') - $year;
376                 $curr_month = DateTimeFormat::timezoneNow($viewer_tz, 'm');
377                 $curr_day   = DateTimeFormat::timezoneNow($viewer_tz, 'd');
378
379                 if (($curr_month < $month) || (($curr_month == $month) && ($curr_day < $day))) {
380                         $year_diff--;
381                 }
382
383                 return $year_diff;
384         }
385
386         /**
387          * Get days of a month in a given year.
388          *
389          * Returns number of days in the month of the given year.
390          * $m = 1 is 'January' to match human usage.
391          *
392          * @param int $y Year
393          * @param int $m Month (1=January, 12=December)
394          *
395          * @return int Number of days in the given month
396          */
397         public static function getDaysInMonth($y, $m)
398         {
399                 return date('t', mktime(0, 0, 0, $m, 1, $y));
400         }
401
402         /**
403          * Returns the first day in month for a given month, year.
404          *
405          * Months start at 1.
406          *
407          * @param int $y Year
408          * @param int $m Month (1=January, 12=December)
409          *
410          * @return string day 0 = Sunday through 6 = Saturday
411          * @throws \Exception
412          */
413         private static function getFirstDayInMonth($y, $m)
414         {
415                 $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m));
416
417                 return DateTimeFormat::utc($d, 'w');
418         }
419
420         /**
421          * Output a calendar for the given month, year.
422          *
423          * If $links are provided (array), e.g. $links[12] => 'http://mylink' ,
424          * date 12 will be linked appropriately. Today's date is also noted by
425          * altering td class.
426          * Months count from 1.
427          *
428          * @param int    $y     Year
429          * @param int    $m     Month
430          * @param array  $links (default null)
431          * @param string $class
432          *
433          * @return string
434          *
435          * @throws \Exception
436          * @todo  Provide (prev, next) links, define class variations for different size calendars
437          */
438         public static function getCalendarTable($y = 0, $m = 0, $links = null, $class = '')
439         {
440                 // month table - start at 1 to match human usage.
441                 $mtab = [' ',
442                         'January', 'February', 'March',
443                         'April', 'May', 'June',
444                         'July', 'August', 'September',
445                         'October', 'November', 'December'
446                 ];
447
448                 $thisyear = DateTimeFormat::localNow('Y');
449                 $thismonth = DateTimeFormat::localNow('m');
450                 if (!$y) {
451                         $y = $thisyear;
452                 }
453
454                 if (!$m) {
455                         $m = intval($thismonth);
456                 }
457
458                 $dn = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
459                 $f = self::getFirstDayInMonth($y, $m);
460                 $l = self::getDaysInMonth($y, $m);
461                 $d = 1;
462                 $dow = 0;
463                 $started = false;
464
465                 if (($y == $thisyear) && ($m == $thismonth)) {
466                         $tddate = intval(DateTimeFormat::localNow('j'));
467                 }
468
469                 $str_month = DI::l10n()->getDay($mtab[$m]);
470                 $o = '<table class="calendar' . $class . '">';
471                 $o .= "<caption>$str_month $y</caption><tr>";
472                 for ($a = 0; $a < 7; $a ++) {
473                         $o .= '<th>' . mb_substr(DI::l10n()->getDay($dn[$a]), 0, 3, 'UTF-8') . '</th>';
474                 }
475
476                 $o .= '</tr><tr>';
477
478                 while ($d <= $l) {
479                         if (($dow == $f) && (!$started)) {
480                                 $started = true;
481                         }
482
483                         $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : '');
484                         $o .= "<td $today>";
485                         $day = str_replace(' ', '&nbsp;', sprintf('%2.2d', $d));
486                         if ($started) {
487                                 if (isset($links[$d])) {
488                                         $o .= "<a href=\"{$links[$d]}\">$day</a>";
489                                 } else {
490                                         $o .= $day;
491                                 }
492
493                                 $d ++;
494                         } else {
495                                 $o .= '&nbsp;';
496                         }
497
498                         $o .= '</td>';
499                         $dow ++;
500                         if (($dow == 7) && ($d <= $l)) {
501                                 $dow = 0;
502                                 $o .= '</tr><tr>';
503                         }
504                 }
505
506                 if ($dow) {
507                         for ($a = $dow; $a < 7; $a ++) {
508                                 $o .= '<td>&nbsp;</td>';
509                         }
510                 }
511
512                 $o .= '</tr></table>' . "\r\n";
513
514                 return $o;
515         }
516 }