[REF][PHP8.1] Replace usage of the date_format modifer with crmDate to resolve issues...
[civicrm-core.git] / CRM / Utils / Date.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Date utilties
20 */
21 class CRM_Utils_Date {
22
23 /**
24 * Format a date by padding it with leading '0'.
25 *
26 * @param array $date
27 * ('Y', 'M', 'd').
28 * @param string $separator
29 * The separator to use when formatting the date.
30 * @param int|string $invalidDate what to return if the date is invalid
31 *
32 * @return string
33 * formatted string for date
34 */
35 public static function format($date, $separator = '', $invalidDate = 0) {
36 if (is_numeric($date) &&
37 ((strlen($date) == 8) || (strlen($date) == 14))
38 ) {
39 return $date;
40 }
41
42 if (!is_array($date) ||
43 CRM_Utils_System::isNull($date) ||
44 empty($date['Y'])
45 ) {
46 return $invalidDate;
47 }
48
49 $date['Y'] = (int ) $date['Y'];
50 if ($date['Y'] < 1000 || $date['Y'] > 2999) {
51 return $invalidDate;
52 }
53
54 if (array_key_exists('m', $date)) {
55 $date['M'] = $date['m'];
56 }
57 elseif (array_key_exists('F', $date)) {
58 $date['M'] = $date['F'];
59 }
60
61 if (!empty($date['M'])) {
62 $date['M'] = (int ) $date['M'];
63 if ($date['M'] < 1 || $date['M'] > 12) {
64 return $invalidDate;
65 }
66 }
67 else {
68 $date['M'] = 1;
69 }
70
71 if (!empty($date['d'])) {
72 $date['d'] = (int ) $date['d'];
73 }
74 else {
75 $date['d'] = 1;
76 }
77
78 if (!checkdate($date['M'], $date['d'], $date['Y'])) {
79 return $invalidDate;
80 }
81
82 $date['M'] = sprintf('%02d', $date['M']);
83 $date['d'] = sprintf('%02d', $date['d']);
84
85 $time = '';
86 if (CRM_Utils_Array::value('H', $date) != NULL ||
87 CRM_Utils_Array::value('h', $date) != NULL ||
88 CRM_Utils_Array::value('i', $date) != NULL ||
89 CRM_Utils_Array::value('s', $date) != NULL
90 ) {
91 // we have time too..
92 if (!empty($date['h'])) {
93 if (CRM_Utils_Array::value('A', $date) == 'PM' or CRM_Utils_Array::value('a', $date) == 'pm') {
94 if ($date['h'] != 12) {
95 $date['h'] = $date['h'] + 12;
96 }
97 }
98 if ((CRM_Utils_Array::value('A', $date) == 'AM' or CRM_Utils_Array::value('a', $date) == 'am') &&
99 CRM_Utils_Array::value('h', $date) == 12
100 ) {
101 $date['h'] = '00';
102 }
103
104 $date['h'] = (int ) $date['h'];
105 }
106 else {
107 $date['h'] = 0;
108 }
109
110 // in 24-hour format the hour is under the 'H' key
111 if (!empty($date['H'])) {
112 $date['H'] = (int) $date['H'];
113 }
114 else {
115 $date['H'] = 0;
116 }
117
118 if (!empty($date['i'])) {
119 $date['i'] = (int ) $date['i'];
120 }
121 else {
122 $date['i'] = 0;
123 }
124
125 if ($date['h'] == 0 && $date['H'] != 0) {
126 $date['h'] = $date['H'];
127 }
128
129 if (!empty($date['s'])) {
130 $date['s'] = (int ) $date['s'];
131 }
132 else {
133 $date['s'] = 0;
134 }
135
136 $date['h'] = sprintf('%02d', $date['h']);
137 $date['i'] = sprintf('%02d', $date['i']);
138 $date['s'] = sprintf('%02d', $date['s']);
139
140 if ($separator) {
141 $time = '&nbsp;';
142 }
143 $time .= $date['h'] . $separator . $date['i'] . $separator . $date['s'];
144 }
145
146 return $date['Y'] . $separator . $date['M'] . $separator . $date['d'] . $time;
147 }
148
149 /**
150 * Return abbreviated weekday names according to the locale.
151 *
152 * Array will be in localized order according to 'weekBegins' setting,
153 * but array keys will always match to:
154 * 0 => Sun
155 * 1 => Mon
156 * etc.
157 *
158 * @return array
159 * 0-based array with abbreviated weekday names
160 *
161 */
162 public static function getAbbrWeekdayNames() {
163 $key = 'abbrDays_' . \CRM_Core_I18n::getLocale();
164 if (empty(\Civi::$statics[__CLASS__][$key])) {
165 $intl_formatter = IntlDateFormatter::create(CRM_Core_I18n::getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::MEDIUM, NULL, IntlDateFormatter::GREGORIAN, 'E');
166 $days = [
167 0 => $intl_formatter->format(strtotime('Sunday')),
168 1 => $intl_formatter->format(strtotime('Monday')),
169 2 => $intl_formatter->format(strtotime('Tuesday')),
170 3 => $intl_formatter->format(strtotime('Wednesday')),
171 4 => $intl_formatter->format(strtotime('Thursday')),
172 5 => $intl_formatter->format(strtotime('Friday')),
173 6 => $intl_formatter->format(strtotime('Saturday')),
174 ];
175 // First day of the week
176 $firstDay = Civi::settings()->get('weekBegins');
177
178 \Civi::$statics[__CLASS__][$key] = [];
179 for ($i = $firstDay; count(\Civi::$statics[__CLASS__][$key]) < 7; $i = $i > 5 ? 0 : $i + 1) {
180 \Civi::$statics[__CLASS__][$key][$i] = $days[$i];
181 }
182 }
183 return \Civi::$statics[__CLASS__][$key];
184 }
185
186 /**
187 * Return full weekday names according to the locale.
188 *
189 * Array will be in localized order according to 'weekBegins' setting,
190 * but array keys will always match to:
191 * 0 => Sunday
192 * 1 => Monday
193 * etc.
194 *
195 * @return array
196 * 0-based array with full weekday names
197 *
198 */
199 public static function getFullWeekdayNames() {
200 $key = 'fullDays_' . \CRM_Core_I18n::getLocale();
201 if (empty(\Civi::$statics[__CLASS__][$key])) {
202 $intl_formatter = IntlDateFormatter::create(CRM_Core_I18n::getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::MEDIUM, NULL, IntlDateFormatter::GREGORIAN, 'EEEE');
203 $days = [
204 0 => $intl_formatter->format(strtotime('Sunday')),
205 1 => $intl_formatter->format(strtotime('Monday')),
206 2 => $intl_formatter->format(strtotime('Tuesday')),
207 3 => $intl_formatter->format(strtotime('Wednesday')),
208 4 => $intl_formatter->format(strtotime('Thursday')),
209 5 => $intl_formatter->format(strtotime('Friday')),
210 6 => $intl_formatter->format(strtotime('Saturday')),
211 ];
212 // First day of the week
213 $firstDay = Civi::settings()->get('weekBegins');
214
215 \Civi::$statics[__CLASS__][$key] = [];
216 for ($i = $firstDay; count(\Civi::$statics[__CLASS__][$key]) < 7; $i = $i > 5 ? 0 : $i + 1) {
217 \Civi::$statics[__CLASS__][$key][$i] = $days[$i];
218 }
219 }
220 return \Civi::$statics[__CLASS__][$key];
221 }
222
223 /**
224 * Return abbreviated month names according to the locale.
225 *
226 * @param bool $month
227 *
228 * @return array
229 * 1-based array with abbreviated month names
230 *
231 */
232 public static function &getAbbrMonthNames($month = FALSE) {
233 $key = 'abbrMonthNames_' . \CRM_Core_I18n::getLocale();
234 if (empty(\Civi::$statics[__CLASS__][$key])) {
235 $intl_formatter = IntlDateFormatter::create(CRM_Core_I18n::getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::MEDIUM, NULL, IntlDateFormatter::GREGORIAN, 'MMM');
236 \Civi::$statics[__CLASS__][$key] = [
237 1 => $intl_formatter->format(strtotime('January')),
238 2 => $intl_formatter->format(strtotime('February')),
239 3 => $intl_formatter->format(strtotime('March')),
240 4 => $intl_formatter->format(strtotime('April')),
241 5 => $intl_formatter->format(strtotime('May')),
242 6 => $intl_formatter->format(strtotime('June')),
243 7 => $intl_formatter->format(strtotime('July')),
244 8 => $intl_formatter->format(strtotime('August')),
245 9 => $intl_formatter->format(strtotime('September')),
246 10 => $intl_formatter->format(strtotime('October')),
247 11 => $intl_formatter->format(strtotime('November')),
248 12 => $intl_formatter->format(strtotime('December')),
249 ];
250 }
251 if ($month) {
252 return \Civi::$statics[__CLASS__][$key][$month];
253 }
254 return \Civi::$statics[__CLASS__][$key];
255 }
256
257 /**
258 * Return full month names according to the locale.
259 *
260 * @return array
261 * 1-based array with full month names
262 *
263 */
264 public static function &getFullMonthNames() {
265 $key = 'fullMonthNames_' . \CRM_Core_I18n::getLocale();
266 if (empty(\Civi::$statics[__CLASS__][$key])) {
267 // Not relying on strftime because it depends on the operating system
268 // and most people will not have a non-US locale configured out of the box
269 // Ignoring other date names for now, since less visible by default
270 \Civi::$statics[__CLASS__][$key] = [
271 1 => ts('January'),
272 2 => ts('February'),
273 3 => ts('March'),
274 4 => ts('April'),
275 5 => ts('May'),
276 6 => ts('June'),
277 7 => ts('July'),
278 8 => ts('August'),
279 9 => ts('September'),
280 10 => ts('October'),
281 11 => ts('November'),
282 12 => ts('December'),
283 ];
284 }
285
286 return \Civi::$statics[__CLASS__][$key];
287 }
288
289 /**
290 * @param string $string
291 *
292 * @return int
293 */
294 public static function unixTime($string) {
295 if (empty($string)) {
296 return 0;
297 }
298 $parsedDate = date_parse($string);
299 return mktime(CRM_Utils_Array::value('hour', $parsedDate),
300 CRM_Utils_Array::value('minute', $parsedDate),
301 59,
302 CRM_Utils_Array::value('month', $parsedDate),
303 CRM_Utils_Array::value('day', $parsedDate),
304 CRM_Utils_Array::value('year', $parsedDate)
305 );
306 }
307
308 /**
309 * Create a date and time string in a provided format.
310 * %A - Full day name ('Saturday'..'Sunday')
311 * %a - abbreviated day name ('Sat'..'Sun')
312 * %b - abbreviated month name ('Jan'..'Dec')
313 * %B - full month name ('January'..'December')
314 * %d - day of the month as a decimal number, 0-padded ('01'..'31')
315 * %e - day of the month as a decimal number, blank-padded (' 1'..'31')
316 * %E - day of the month as a decimal number ('1'..'31')
317 * %f - English ordinal suffix for the day of the month ('st', 'nd', 'rd', 'th')
318 * %H - hour in 24-hour format, 0-padded ('00'..'23')
319 * %I - hour in 12-hour format, 0-padded ('01'..'12')
320 * %k - hour in 24-hour format, blank-padded (' 0'..'23')
321 * %l - hour in 12-hour format, blank-padded (' 1'..'12')
322 * %m - month as a decimal number, 0-padded ('01'..'12')
323 * %M - minute, 0-padded ('00'..'60')
324 * %p - lowercase ante/post meridiem ('am', 'pm')
325 * %P - uppercase ante/post meridiem ('AM', 'PM')
326 * %Y - year as a decimal number including the century ('2005')
327 *
328 * @param string $dateString
329 * Date and time in 'YYYY-MM-DD hh:mm:ss' format.
330 * @param string $format
331 * The output format.
332 * @param array $dateParts
333 * An array with the desired date parts.
334 *
335 * @return string
336 * the $format-formatted $date
337 */
338 public static function customFormat($dateString, $format = NULL, $dateParts = NULL) {
339 // 1-based (January) month names arrays
340 $abbrMonths = self::getAbbrMonthNames();
341 $fullMonths = self::getFullMonthNames();
342 $fullWeekdayNames = self::getFullWeekdayNames();
343 $abbrWeekdayNames = self::getAbbrWeekdayNames();
344
345 // backwards compatability with %D being the equivilant of %m/%d/%y
346 $format = str_replace('%D', '%m/%d/%y', $format);
347
348 if (!$format) {
349 $config = CRM_Core_Config::singleton();
350
351 if ($dateParts) {
352 if (array_intersect(['h', 'H'], $dateParts)) {
353 $format = $config->dateformatDatetime;
354 }
355 elseif (array_intersect(['d', 'j'], $dateParts)) {
356 $format = $config->dateformatFull;
357 }
358 elseif (array_intersect(['m', 'M'], $dateParts)) {
359 $format = $config->dateformatPartial;
360 }
361 else {
362 $format = $config->dateformatYear;
363 }
364 }
365 else {
366 if (strpos(($dateString ?? ''), '-')) {
367 $month = (int) substr($dateString, 5, 2);
368 $day = (int) substr($dateString, 8, 2);
369 }
370 else {
371 $month = (int) substr(($dateString ?? ''), 4, 2);
372 $day = (int) substr(($dateString ?? ''), 6, 2);
373 }
374
375 if (strlen(($dateString ?? '')) > 10) {
376 $format = $config->dateformatDatetime;
377 }
378 elseif ($day > 0) {
379 $format = $config->dateformatFull;
380 }
381 elseif ($month > 0) {
382 $format = $config->dateformatPartial;
383 }
384 else {
385 $format = $config->dateformatYear;
386 }
387 }
388 }
389
390 if (!CRM_Utils_System::isNull($dateString)) {
391 if (strpos($dateString, '-')) {
392 $year = (int) substr($dateString, 0, 4);
393 $month = (int) substr($dateString, 5, 2);
394 $day = (int) substr($dateString, 8, 2);
395
396 $hour24 = (int) substr($dateString, 11, 2);
397 $minute = (int) substr($dateString, 14, 2);
398 $second = (int) substr($dateString, 17, 2);
399 }
400 else {
401 $year = (int) substr($dateString, 0, 4);
402 $month = (int) substr($dateString, 4, 2);
403 $day = (int) substr($dateString, 6, 2);
404
405 $hour24 = (int) substr($dateString, 8, 2);
406 $minute = (int) substr($dateString, 10, 2);
407 $second = (int) substr($dateString, 12, 2);
408 }
409
410 $dayInt = date('w', strtotime($dateString));
411
412 if ($day % 10 == 1 and $day != 11) {
413 $suffix = 'st';
414 }
415 elseif ($day % 10 == 2 and $day != 12) {
416 $suffix = 'nd';
417 }
418 elseif ($day % 10 == 3 and $day != 13) {
419 $suffix = 'rd';
420 }
421 else {
422 $suffix = 'th';
423 }
424
425 if ($hour24 < 12) {
426 if ($hour24 == 00) {
427 $hour12 = 12;
428 }
429 else {
430 $hour12 = $hour24;
431 }
432 $type = 'AM';
433 }
434 else {
435 if ($hour24 == 12) {
436 $hour12 = 12;
437 }
438 else {
439 $hour12 = $hour24 - 12;
440 }
441 $type = 'PM';
442 }
443
444 $date = [
445 '%A' => $fullWeekdayNames[$dayInt] ?? NULL,
446 '%a' => $abbrWeekdayNames[$dayInt] ?? NULL,
447 '%b' => $abbrMonths[$month] ?? NULL,
448 '%B' => $fullMonths[$month] ?? NULL,
449 '%d' => $day > 9 ? $day : '0' . $day,
450 '%e' => $day > 9 ? $day : ' ' . $day,
451 '%E' => $day,
452 '%f' => $suffix,
453 '%H' => $hour24 > 9 ? $hour24 : '0' . $hour24,
454 '%h' => $hour12 > 9 ? $hour12 : '0' . $hour12,
455 '%I' => $hour12 > 9 ? $hour12 : '0' . $hour12,
456 '%k' => $hour24 > 9 ? $hour24 : ' ' . $hour24,
457 '%l' => $hour12 > 9 ? $hour12 : ' ' . $hour12,
458 '%m' => $month > 9 ? $month : '0' . $month,
459 '%M' => $minute > 9 ? $minute : '0' . $minute,
460 '%i' => $minute > 9 ? $minute : '0' . $minute,
461 '%p' => strtolower($type),
462 '%P' => $type,
463 '%Y' => $year,
464 '%y' => substr($year, 2),
465 '%s' => str_pad($second, 2, 0, STR_PAD_LEFT),
466 '%S' => str_pad($second, 2, 0, STR_PAD_LEFT),
467 '%Z' => date('T', strtotime($dateString)),
468 ];
469
470 return strtr($format, $date);
471 }
472 return '';
473 }
474
475 /**
476 * Format the field according to the site's preferred date format.
477 *
478 * This is likely to look something like December 31st, 2020.
479 *
480 * @param string $date
481 *
482 * @return string
483 */
484 public static function formatDateOnlyLong(string $date):string {
485 return CRM_Utils_Date::customFormat($date, Civi::settings()->get('dateformatFull'));
486 }
487
488 /**
489 * Wrapper for customFormat that takes a timestamp
490 *
491 * @param int $timestamp
492 * Date and time in timestamp format.
493 * @param string $format
494 * The output format.
495 * @param array $dateParts
496 * An array with the desired date parts.
497 *
498 * @return string
499 * the $format-formatted $date
500 */
501 public static function customFormatTs($timestamp, $format = NULL, $dateParts = NULL) {
502 return CRM_Utils_Date::customFormat(date("Y-m-d H:i:s", $timestamp), $format, $dateParts);
503 }
504
505 /**
506 * Converts the date/datetime from MySQL format to ISO format
507 *
508 * @param string $mysql
509 * Date/datetime in MySQL format.
510 *
511 * @return string
512 * date/datetime in ISO format
513 */
514 public static function mysqlToIso($mysql) {
515 $year = substr(($mysql ?? ''), 0, 4);
516 $month = substr(($mysql ?? ''), 4, 2);
517 $day = substr(($mysql ?? ''), 6, 2);
518 $hour = substr(($mysql ?? ''), 8, 2);
519 $minute = substr(($mysql ?? ''), 10, 2);
520 $second = substr(($mysql ?? ''), 12, 2);
521
522 $iso = '';
523 if ($year) {
524 $iso .= "$year";
525 }
526 if ($month) {
527 $iso .= "-$month";
528 if ($day) {
529 $iso .= "-$day";
530 }
531 }
532
533 if ($hour) {
534 $iso .= " $hour";
535 if ($minute) {
536 $iso .= ":$minute";
537 if ($second) {
538 $iso .= ":$second";
539 }
540 }
541 }
542 return $iso;
543 }
544
545 /**
546 * Converts the date/datetime from ISO format to MySQL format
547 * Note that until CRM-14986/ 4.4.7 this was required whenever the pattern $dao->find(TRUE): $dao->save(); was
548 * used to update an object with a date field was used. The DAO now checks for a '-' in date field strings
549 * & runs this function if the - appears - meaning it is likely redundant in the form & BAO layers
550 *
551 * @param string $iso
552 * Date/datetime in ISO format.
553 *
554 * @return string
555 * date/datetime in MySQL format
556 */
557 public static function isoToMysql($iso) {
558 $dropArray = ['-' => '', ':' => '', ' ' => ''];
559 return strtr(($iso ?? ''), $dropArray);
560 }
561
562 /**
563 * Converts the any given date to default date format.
564 *
565 * @param array $params
566 * Has given date-format.
567 * @param int $dateType
568 * Type of date.
569 * @param string $dateParam
570 * Index of params.
571 *
572 * @return bool
573 */
574 public static function convertToDefaultDate(&$params, $dateType, $dateParam) {
575 $now = getdate();
576
577 $value = '';
578 if (!empty($params[$dateParam])) {
579 // suppress hh:mm or hh:mm:ss if it exists CRM-7957
580 $value = preg_replace("/(\s(([01]\d)|[2][0-3])(:([0-5]\d)){1,2})$/", "", $params[$dateParam]);
581 }
582
583 switch ($dateType) {
584 case 1:
585 if (!preg_match('/^\d\d\d\d-?(\d|\d\d)-?(\d|\d\d)$/', $value)) {
586 return FALSE;
587 }
588 break;
589
590 case 2:
591 if (!preg_match('/^(\d|\d\d)[-\/](\d|\d\d)[-\/]\d\d$/', $value)) {
592 return FALSE;
593 }
594 break;
595
596 case 4:
597 if (!preg_match('/^(\d|\d\d)[-\/](\d|\d\d)[-\/]\d\d\d\d$/', $value)) {
598 return FALSE;
599 }
600 break;
601
602 case 8:
603 if (!preg_match('/^[A-Za-z]*.[ \t]?\d\d\,[ \t]?\d\d\d\d$/', $value)) {
604 return FALSE;
605 }
606 break;
607
608 case 16:
609 if (!preg_match('/^\d\d-[A-Za-z]{3}.*-\d\d$/', $value) && !preg_match('/^\d\d[-\/]\d\d[-\/]\d\d$/', $value)) {
610 return FALSE;
611 }
612 break;
613
614 case 32:
615 if (!preg_match('/^(\d|\d\d)[-\/](\d|\d\d)[-\/]\d\d\d\d/', $value)) {
616 return FALSE;
617 }
618 break;
619 }
620
621 if ($dateType == 1) {
622 $formattedDate = explode("-", $value);
623 if (count($formattedDate) == 3) {
624 $year = (int) $formattedDate[0];
625 $month = (int) $formattedDate[1];
626 $day = (int) $formattedDate[2];
627 }
628 elseif (count($formattedDate) == 1 && (strlen($value) == 8)) {
629 return TRUE;
630 }
631 else {
632 return FALSE;
633 }
634 }
635
636 if ($dateType == 2 || $dateType == 4) {
637 $formattedDate = explode("/", $value);
638 if (count($formattedDate) != 3) {
639 $formattedDate = explode("-", $value);
640 }
641 if (count($formattedDate) == 3) {
642 $year = (int) $formattedDate[2];
643 $month = (int) $formattedDate[0];
644 $day = (int) $formattedDate[1];
645 }
646 else {
647 return FALSE;
648 }
649 }
650 if ($dateType == 8) {
651 $dateArray = explode(' ', $value);
652 // ignore comma(,)
653 $dateArray[1] = (int) substr($dateArray[1], 0, 2);
654
655 $monthInt = 0;
656 $fullMonths = self::getFullMonthNames();
657 foreach ($fullMonths as $key => $val) {
658 if (strtolower($dateArray[0]) == strtolower($val)) {
659 $monthInt = $key;
660 break;
661 }
662 }
663 if (!$monthInt) {
664 $abbrMonths = self::getAbbrMonthNames();
665 foreach ($abbrMonths as $key => $val) {
666 if (strtolower(trim($dateArray[0], ".")) == strtolower($val)) {
667 $monthInt = $key;
668 break;
669 }
670 }
671 }
672 $year = (int) $dateArray[2];
673 $day = (int) $dateArray[1];
674 $month = (int) $monthInt;
675 }
676 if ($dateType == 16) {
677 $dateArray = explode('-', $value);
678 if (count($dateArray) != 3) {
679 $dateArray = explode('/', $value);
680 }
681
682 if (count($dateArray) == 3) {
683 $monthInt = 0;
684 $fullMonths = self::getFullMonthNames();
685 foreach ($fullMonths as $key => $val) {
686 if (strtolower($dateArray[1]) == strtolower($val)) {
687 $monthInt = $key;
688 break;
689 }
690 }
691 if (!$monthInt) {
692 $abbrMonths = self::getAbbrMonthNames();
693 foreach ($abbrMonths as $key => $val) {
694 if (strtolower(trim($dateArray[1], ".")) == strtolower($val)) {
695 $monthInt = $key;
696 break;
697 }
698 }
699 }
700 if (!$monthInt) {
701 $monthInt = $dateArray[1];
702 }
703
704 $year = (int) $dateArray[2];
705 $day = (int) $dateArray[0];
706 $month = (int) $monthInt;
707 }
708 else {
709 return FALSE;
710 }
711 }
712 if ($dateType == 32) {
713 $formattedDate = explode("/", $value);
714 if (count($formattedDate) == 3) {
715 $year = (int) $formattedDate[2];
716 $month = (int) $formattedDate[1];
717 $day = (int) $formattedDate[0];
718 }
719 else {
720 return FALSE;
721 }
722 }
723
724 $month = ($month < 10) ? "0" . "$month" : $month;
725 $day = ($day < 10) ? "0" . "$day" : $day;
726
727 $year = (int) $year;
728 if ($year < 100) {
729 $year = substr($now['year'], 0, 2) * 100 + $year;
730 if ($year > ($now['year'] + 5)) {
731 $year = $year - 100;
732 }
733 elseif ($year <= ($now['year'] - 95)) {
734 $year = $year + 100;
735 }
736 }
737
738 if ($params[$dateParam]) {
739 $params[$dateParam] = "$year$month$day";
740 }
741 // if month is invalid return as error
742 if ($month !== '00' && $month <= 12) {
743 return TRUE;
744 }
745 return FALSE;
746 }
747
748 /**
749 * Translate a TTL to a concrete expiration time.
750 *
751 * @param null|int|DateInterval $ttl
752 * @param int $default
753 * The value to use if $ttl is not specified (NULL).
754 * @return int
755 * Timestamp (seconds since epoch).
756 * @throws \CRM_Utils_Cache_InvalidArgumentException
757 */
758 public static function convertCacheTtlToExpires($ttl, $default) {
759 if ($ttl === NULL) {
760 $ttl = $default;
761 }
762
763 if (is_int($ttl)) {
764 return time() + $ttl;
765 }
766 elseif ($ttl instanceof DateInterval) {
767 return date_add(new DateTime(), $ttl)->getTimestamp();
768 }
769 else {
770 throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache TTL");
771 }
772 }
773
774 /**
775 * Normalize a TTL.
776 *
777 * @param null|int|DateInterval $ttl
778 * @param int $default
779 * The value to use if $ttl is not specified (NULL).
780 * @return int
781 * Seconds until expiration.
782 * @throws \CRM_Utils_Cache_InvalidArgumentException
783 */
784 public static function convertCacheTtl($ttl, $default) {
785 if ($ttl === NULL) {
786 return $default;
787 }
788 elseif (is_int($ttl)) {
789 return $ttl;
790 }
791 elseif ($ttl instanceof DateInterval) {
792 return date_add(new DateTime(), $ttl)->getTimestamp() - time();
793 }
794 else {
795 throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache TTL");
796 }
797 }
798
799 /**
800 * @param int|false|null $timeStamp
801 *
802 * @return bool|string
803 */
804 public static function currentDBDate($timeStamp = NULL) {
805 return $timeStamp ? date('YmdHis', $timeStamp) : date('YmdHis');
806 }
807
808 /**
809 * @param $date
810 * @param null $now
811 *
812 * @return bool
813 */
814 public static function overdue($date, $now = NULL) {
815 $mysqlDate = self::isoToMysql($date);
816 if (!$now) {
817 $now = self::currentDBDate();
818 }
819 else {
820 $now = self::isoToMysql($now);
821 }
822
823 return !(strtotime($mysqlDate) >= strtotime($now));
824 }
825
826 /**
827 * Get customized today.
828 *
829 * This function is used for getting customized today. To get
830 * actuall today pass 'dayParams' as null. or else pass the day,
831 * month, year values as array values
832 * Example: $dayParams = array(
833 * 'day' => '25', 'month' => '10',
834 * 'year' => '2007' );
835 *
836 * @param array $dayParams of the day, month, year.
837 * Array of the day, month, year.
838 * values.
839 * @param string $format
840 * Expected date format( default.
841 * format is 2007-12-21 )
842 *
843 * @return string
844 * Return the customized today's date (Y-m-d)
845 */
846 public static function getToday($dayParams = NULL, $format = "Y-m-d") {
847 if (is_null($dayParams) || empty($dayParams)) {
848 $today = date($format);
849 }
850 else {
851 $today = date($format, mktime(0, 0, 0,
852 $dayParams['month'],
853 $dayParams['day'],
854 $dayParams['year']
855 ));
856 }
857
858 return $today;
859 }
860
861 /**
862 * Find whether today's date lies in
863 * the given range
864 *
865 * @param date $startDate
866 * Start date for the range.
867 * @param date $endDate
868 * End date for the range.
869 *
870 * @return bool
871 * true if today's date is in the given date range
872 */
873 public static function getRange($startDate, $endDate) {
874 $today = date("Y-m-d");
875 $mysqlStartDate = self::isoToMysql($startDate);
876 $mysqlEndDate = self::isoToMysql($endDate);
877 $mysqlToday = self::isoToMysql($today);
878
879 if ((isset($mysqlStartDate) && isset($mysqlEndDate)) && (($mysqlToday >= $mysqlStartDate) && ($mysqlToday <= $mysqlEndDate))) {
880 return TRUE;
881 }
882 elseif ((isset($mysqlStartDate) && !isset($mysqlEndDate)) && (($mysqlToday >= $mysqlStartDate))) {
883 return TRUE;
884 }
885 elseif ((!isset($mysqlStartDate) && isset($mysqlEndDate)) && (($mysqlToday <= $mysqlEndDate))) {
886 return TRUE;
887 }
888 return FALSE;
889 }
890
891 /**
892 * Get start date and end from
893 * the given relative term and unit
894 *
895 * @param string $relative Relative format in the format term.unit.
896 * Eg: previous.day
897 *
898 * @param string $from
899 * @param string $to
900 * @param string $fromTime
901 * @param string $toTime
902 *
903 * @return array
904 * start date, end date
905 */
906 public static function getFromTo($relative, $from = NULL, $to = NULL, $fromTime = NULL, $toTime = '235959') {
907 if ($relative) {
908 list($term, $unit) = explode('.', $relative, 2);
909 $dateRange = self::relativeToAbsolute($term, $unit);
910 $from = substr(($dateRange['from'] ?? ''), 0, 8);
911 $to = substr(($dateRange['to'] ?? ''), 0, 8);
912 // @todo fix relativeToAbsolute & add tests
913 // relativeToAbsolute returns 8 char date strings
914 // or 14 char date + time strings.
915 // We should use those. However, it turns out to be unreliable.
916 // e.g. this.week does NOT return 235959 for 'from'
917 // so our defaults are more reliable.
918 // Currently relativeToAbsolute only supports 'whole' days so that is ok
919 }
920
921 $from = self::processDate($from, $fromTime);
922 $to = self::processDate($to, $toTime);
923
924 return [$from, $to];
925 }
926
927 /**
928 * Calculate Age in Years if greater than one year else in months.
929 *
930 * @param date $birthDate
931 * Birth Date.
932 * @param date $targetDate
933 * Target Date. (show age on specific date)
934 *
935 * @return array
936 * array $results contains years or months
937 */
938 public static function calculateAge($birthDate, $targetDate = NULL) {
939 $results = [];
940 $formatedBirthDate = CRM_Utils_Date::customFormat($birthDate, '%Y-%m-%d');
941
942 $bDate = explode('-', $formatedBirthDate);
943 $birthYear = $bDate[0];
944 $birthMonth = $bDate[1];
945 $birthDay = $bDate[2];
946 $targetDate = strtotime($targetDate ?? date('Y-m-d'));
947
948 $year_diff = date("Y", $targetDate) - $birthYear;
949
950 // don't calculate age CRM-3143
951 if ($birthYear == '1902') {
952 return $results;
953 }
954 switch ($year_diff) {
955 case 1:
956 $month = (12 - $birthMonth) + date("m", $targetDate);
957 if ($month < 12) {
958 if (date("d", $targetDate) < $birthDay) {
959 $month--;
960 }
961 $results['months'] = $month;
962 }
963 elseif ($month == 12 && (date("d", $targetDate) < $birthDay)) {
964 $results['months'] = $month - 1;
965 }
966 else {
967 $results['years'] = $year_diff;
968 }
969 break;
970
971 case 0:
972 $month = date("m", $targetDate) - $birthMonth;
973 $results['months'] = $month;
974 break;
975
976 default:
977 $results['years'] = $year_diff;
978 if ((date("m", $targetDate) < $birthMonth) || (date("m", $targetDate) == $birthMonth) && (date("d", $targetDate) < $birthDay)) {
979 $results['years']--;
980 }
981 }
982
983 return $results;
984 }
985
986 /**
987 * Calculate next payment date according to provided unit & interval
988 *
989 * @param string $unit
990 * Frequency unit like year,month, week etc.
991 *
992 * @param int $interval
993 * Frequency interval.
994 *
995 * @param array $date
996 * Start date of pledge.
997 *
998 * @param bool $dontCareTime
999 *
1000 * @return array
1001 * contains new date with added interval
1002 */
1003 public static function intervalAdd($unit, $interval, $date, $dontCareTime = FALSE) {
1004 if (is_array($date)) {
1005 $hour = $date['H'] ?? '00';
1006 $minute = $date['i'] ?? '00';
1007 $second = $date['s'] ?? '00';
1008 $month = $date['M'] ?? NULL;
1009 $day = $date['d'] ?? NULL;
1010 $year = $date['Y'] ?? NULL;
1011 }
1012 else {
1013 extract(date_parse($date));
1014 }
1015 $date = mktime($hour, $minute, $second, $month, $day, $year);
1016 switch ($unit) {
1017 case 'year':
1018 $date = mktime($hour, $minute, $second, $month, $day, $year + $interval);
1019 break;
1020
1021 case 'month':
1022 $date = mktime($hour, $minute, $second, $month + $interval, $day, $year);
1023 break;
1024
1025 case 'week':
1026 $interval = $interval * 7;
1027 $date = mktime($hour, $minute, $second, $month, $day + $interval, $year);
1028 break;
1029
1030 case 'day':
1031 $date = mktime($hour, $minute, $second, $month, $day + $interval, $year);
1032 break;
1033
1034 case 'second':
1035 $date = mktime($hour, $minute, $second + $interval, $month, $day, $year);
1036 break;
1037 }
1038
1039 $scheduleDate = explode("-", date("n-j-Y-H-i-s", $date));
1040
1041 $date = [];
1042 $date['M'] = $scheduleDate[0];
1043 $date['d'] = $scheduleDate[1];
1044 $date['Y'] = $scheduleDate[2];
1045 if ($dontCareTime == FALSE) {
1046 $date['H'] = $scheduleDate[3];
1047 $date['i'] = $scheduleDate[4];
1048 $date['s'] = $scheduleDate[5];
1049 }
1050 return $date;
1051 }
1052
1053 /**
1054 * Get the smarty view presentation mapping for the given format.
1055 *
1056 * Historically it was decided that where the view format is 'dd/mm/yy' or 'mm/dd/yy'
1057 * they should be rendered using a longer date format. This is likely as much to
1058 * do with the earlier date widget being unable to handle some formats as usablity.
1059 * However, we continue to respect this.
1060 *
1061 * @param $format
1062 * Given format ( eg 'M Y', 'Y M' ).
1063 *
1064 * @return string|null
1065 * Smarty translation of the date format. Null is also valid and is translated
1066 * according to the available parts at the smarty layer.
1067 */
1068 public static function getDateFieldViewFormat($format) {
1069 $supportableFormats = [
1070 'mm/dd' => '%B %E%f',
1071 'dd-mm' => '%E%f %B',
1072 'yy-mm' => '%Y %B',
1073 'M yy' => '%b %Y',
1074 'yy' => '%Y',
1075 'dd/mm/yy' => '%E%f %B %Y',
1076 ];
1077
1078 return array_key_exists($format, $supportableFormats) ? $supportableFormats[$format] : self::pickBestSmartyFormat($format);
1079 }
1080
1081 /**
1082 * Pick the smarty format from settings that best matches the time string we have.
1083 *
1084 * For view purposes we historically use the setting that most closely matches the data
1085 * in the format from our settings, as opposed to the setting configured for the field.
1086 *
1087 * @param $format
1088 * @return mixed
1089 */
1090 public static function pickBestSmartyFormat($format) {
1091 if (stristr($format, 'h')) {
1092 return Civi::settings()->get('dateformatDatetime');
1093 }
1094 if (stristr($format, 'd') || stristr($format, 'j')) {
1095 return Civi::settings()->get('dateformatFull');
1096 }
1097 if (stristr($format, 'm')) {
1098 return Civi::settings()->get('dateformatPartial');
1099 }
1100 return Civi::settings()->get('dateformatYear');
1101 }
1102
1103 /**
1104 * Map date plugin and actual format that is used by PHP.
1105 *
1106 * @return array
1107 */
1108 public static function datePluginToPHPFormats() {
1109 $dateInputFormats = [
1110 "mm/dd/yy" => 'm/d/Y',
1111 "dd/mm/yy" => 'd/m/Y',
1112 "yy-mm-dd" => 'Y-m-d',
1113 "dd-mm-yy" => 'd-m-Y',
1114 "dd.mm.yy" => 'd.m.Y',
1115 "M d" => 'M j',
1116 "M d, yy" => 'M j, Y',
1117 "d M yy" => 'j M Y',
1118 "MM d, yy" => 'F j, Y',
1119 "d MM yy" => 'j F Y',
1120 "DD, d MM yy" => 'l, j F Y',
1121 "mm/dd" => 'm/d',
1122 "dd-mm" => 'd-m',
1123 "yy-mm" => 'Y-m',
1124 "M yy" => 'M Y',
1125 "M Y" => 'M Y',
1126 "yy" => 'Y',
1127 ];
1128 return $dateInputFormats;
1129 }
1130
1131 /**
1132 * Resolves the given relative time interval into finite time limits.
1133 *
1134 * @param string $relativeTerm
1135 * Relative time frame: this, previous, previous_1.
1136 * @param int $unit
1137 * Frequency unit like year, month, week etc.
1138 *
1139 * @return array
1140 * start date and end date for the relative time frame
1141 */
1142 public static function relativeToAbsolute($relativeTerm, $unit) {
1143 $now = getdate();
1144 $from = $to = $dateRange = [];
1145 $from['H'] = $from['i'] = $from['s'] = 0;
1146 $relativeTermParts = explode('_', $relativeTerm);
1147 $relativeTermPrefix = $relativeTermParts[0];
1148 $relativeTermSuffix = $relativeTermParts[1] ?? '';
1149
1150 switch ($unit) {
1151 case 'year':
1152 switch ($relativeTerm) {
1153 case 'previous':
1154 $from['M'] = $from['d'] = 1;
1155 $to['d'] = 31;
1156 $to['M'] = 12;
1157 $to['Y'] = $from['Y'] = $now['year'] - 1;
1158 break;
1159
1160 case 'previous_before':
1161 $from['M'] = $from['d'] = 1;
1162 $to['d'] = 31;
1163 $to['M'] = 12;
1164 $to['Y'] = $from['Y'] = $now['year'] - 2;
1165 break;
1166
1167 case 'previous_2':
1168 $from['M'] = $from['d'] = 1;
1169 $to['d'] = 31;
1170 $to['M'] = 12;
1171 $from['Y'] = $now['year'] - 2;
1172 $to['Y'] = $now['year'] - 1;
1173 break;
1174
1175 case 'earlier':
1176 $to['d'] = 31;
1177 $to['M'] = 12;
1178 $to['Y'] = $now['year'] - 1;
1179 unset($from);
1180 break;
1181
1182 case 'greater':
1183 $from['M'] = $from['d'] = 1;
1184 $from['Y'] = $now['year'];
1185 unset($to);
1186 break;
1187
1188 case 'greater_previous':
1189 $from['d'] = 31;
1190 $from['M'] = 12;
1191 $from['Y'] = $now['year'] - 1;
1192 unset($to);
1193 break;
1194
1195 case 'ending':
1196 $to['d'] = $now['mday'];
1197 $to['M'] = $now['mon'];
1198 $to['Y'] = $now['year'];
1199 $to['H'] = 23;
1200 $to['i'] = $to['s'] = 59;
1201 $from = self::intervalAdd('year', -1, $to);
1202 $from = self::intervalAdd('second', 1, $from);
1203 break;
1204
1205 case 'current':
1206 $from['M'] = $from['d'] = 1;
1207 $from['Y'] = $now['year'];
1208 $to['H'] = 23;
1209 $to['i'] = $to['s'] = 59;
1210 $to['d'] = $now['mday'];
1211 $to['M'] = $now['mon'];
1212 $to['Y'] = $now['year'];
1213 break;
1214
1215 case 'ending_2':
1216 $to['d'] = $now['mday'];
1217 $to['M'] = $now['mon'];
1218 $to['Y'] = $now['year'];
1219 $to['H'] = 23;
1220 $to['i'] = $to['s'] = 59;
1221 $from = self::intervalAdd('year', -2, $to);
1222 $from = self::intervalAdd('second', 1, $from);
1223 break;
1224
1225 case 'ending_3':
1226 $to['d'] = $now['mday'];
1227 $to['M'] = $now['mon'];
1228 $to['Y'] = $now['year'];
1229 $to['H'] = 23;
1230 $to['i'] = $to['s'] = 59;
1231 $from = self::intervalAdd('year', -3, $to);
1232 $from = self::intervalAdd('second', 1, $from);
1233 break;
1234
1235 case 'less':
1236 $to['d'] = 31;
1237 $to['M'] = 12;
1238 $to['Y'] = $now['year'];
1239 unset($from);
1240 break;
1241
1242 case 'next':
1243 $from['M'] = $from['d'] = 1;
1244 $to['d'] = 31;
1245 $to['M'] = 12;
1246 $to['Y'] = $from['Y'] = $now['year'] + 1;
1247 break;
1248
1249 case 'starting':
1250 $from['d'] = $now['mday'];
1251 $from['M'] = $now['mon'];
1252 $from['Y'] = $now['year'];
1253 $to['d'] = $now['mday'] - 1;
1254 $to['M'] = $now['mon'];
1255 $to['Y'] = $now['year'] + 1;
1256 break;
1257
1258 default:
1259 switch ($relativeTermPrefix) {
1260
1261 case 'ending':
1262 $to['d'] = $now['mday'];
1263 $to['M'] = $now['mon'];
1264 $to['Y'] = $now['year'];
1265 $to['H'] = 23;
1266 $to['i'] = $to['s'] = 59;
1267 $from = self::intervalAdd('year', -$relativeTermSuffix, $to);
1268 $from = self::intervalAdd('second', 1, $from);
1269 break;
1270
1271 case 'this':
1272 $from['d'] = $from['M'] = 1;
1273 $to['d'] = 31;
1274 $to['M'] = 12;
1275 $to['Y'] = $from['Y'] = $now['year'];
1276 if (is_numeric($relativeTermSuffix)) {
1277 $from['Y'] -= ($relativeTermSuffix - 1);
1278 }
1279 break;
1280 }
1281 break;
1282 }
1283 break;
1284
1285 case 'fiscal_year':
1286 $config = CRM_Core_Config::singleton();
1287 $from['d'] = $config->fiscalYearStart['d'];
1288 $from['M'] = $config->fiscalYearStart['M'];
1289 $fYear = self::calculateFiscalYear($from['d'], $from['M']);
1290 switch ($relativeTermPrefix) {
1291 case 'this':
1292 $from['Y'] = $fYear;
1293 $fiscalYear = mktime(0, 0, 0, $from['M'], $from['d'] - 1, $from['Y'] + 1);
1294 $fiscalEnd = explode('-', date("Y-m-d", $fiscalYear));
1295 $to['d'] = $fiscalEnd['2'];
1296 $to['M'] = $fiscalEnd['1'];
1297 $to['Y'] = $fiscalEnd['0'];
1298 $to['H'] = 23;
1299 $to['i'] = $to['s'] = 59;
1300 if (is_numeric($relativeTermSuffix)) {
1301 $from = self::intervalAdd('year', (-$relativeTermSuffix), $to);
1302 $from = self::intervalAdd('second', 1, $from);
1303 }
1304 break;
1305
1306 case 'previous':
1307 if (!is_numeric($relativeTermSuffix)) {
1308 $from['Y'] = ($relativeTermSuffix === 'before') ? $fYear - 2 : $fYear - 1;
1309 $fiscalYear = mktime(0, 0, 0, $from['M'], $from['d'] - 1, $from['Y'] + 1);
1310 $fiscalEnd = explode('-', date("Y-m-d", $fiscalYear));
1311 $to['d'] = $fiscalEnd['2'];
1312 $to['M'] = $fiscalEnd['1'];
1313 $to['Y'] = $fiscalEnd['0'];
1314 $to['H'] = 23;
1315 $to['i'] = $to['s'] = 59;
1316 }
1317 else {
1318 $from['Y'] = $fYear - $relativeTermSuffix;
1319 $fiscalYear = mktime(0, 0, 0, $from['M'], $from['d'] - 1, $from['Y'] + 1);
1320 $fiscalEnd = explode('-', date("Y-m-d", $fiscalYear));
1321 $to['d'] = $fiscalEnd['2'];
1322 $to['M'] = $fiscalEnd['1'];
1323 $to['Y'] = $fYear;
1324 $to['H'] = 23;
1325 $to['i'] = $to['s'] = 59;
1326 }
1327 break;
1328
1329 case 'next':
1330 $from['Y'] = $fYear + 1;
1331 $fiscalYear = mktime(0, 0, 0, $from['M'], $from['d'] - 1, $from['Y'] + 1);
1332 $fiscalEnd = explode('-', date("Y-m-d", $fiscalYear));
1333 $to['d'] = $fiscalEnd['2'];
1334 $to['M'] = $fiscalEnd['1'];
1335 $to['Y'] = $fiscalEnd['0'];
1336 break;
1337 }
1338 break;
1339
1340 case 'quarter':
1341 switch ($relativeTerm) {
1342 case 'this':
1343
1344 $quarter = ceil($now['mon'] / 3);
1345 $from['d'] = 1;
1346 $from['M'] = (3 * $quarter) - 2;
1347 $to['M'] = 3 * $quarter;
1348 $to['Y'] = $from['Y'] = $now['year'];
1349 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $now['year']));
1350 break;
1351
1352 case 'previous':
1353 $difference = 1;
1354 $quarter = ceil($now['mon'] / 3);
1355 $quarter = $quarter - $difference;
1356 $subtractYear = 0;
1357 if ($quarter <= 0) {
1358 $subtractYear = 1;
1359 $quarter += 4;
1360 }
1361 $from['d'] = 1;
1362 $from['M'] = (3 * $quarter) - 2;
1363 $to['M'] = 3 * $quarter;
1364 $to['Y'] = $from['Y'] = $now['year'] - $subtractYear;
1365 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1366 break;
1367
1368 case 'previous_before':
1369 $difference = 2;
1370 $quarter = ceil($now['mon'] / 3);
1371 $quarter = $quarter - $difference;
1372 $subtractYear = 0;
1373 if ($quarter <= 0) {
1374 $subtractYear = 1;
1375 $quarter += 4;
1376 }
1377 $from['d'] = 1;
1378 $from['M'] = (3 * $quarter) - 2;
1379 $to['M'] = 3 * $quarter;
1380 $to['Y'] = $from['Y'] = $now['year'] - $subtractYear;
1381 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1382 break;
1383
1384 case 'previous_2':
1385 $difference = 2;
1386 $quarter = ceil($now['mon'] / 3);
1387 $current_quarter = $quarter;
1388 $quarter = $quarter - $difference;
1389 $subtractYear = 0;
1390 if ($quarter <= 0) {
1391 $subtractYear = 1;
1392 $quarter += 4;
1393 }
1394 $from['d'] = 1;
1395 $from['M'] = (3 * $quarter) - 2;
1396 switch ($current_quarter) {
1397 case 1:
1398 $to['M'] = (4 * $quarter);
1399 break;
1400
1401 case 2:
1402 $to['M'] = (4 * $quarter) + 3;
1403 break;
1404
1405 case 3:
1406 $to['M'] = (4 * $quarter) + 2;
1407 break;
1408
1409 case 4:
1410 $to['M'] = (4 * $quarter) + 1;
1411 break;
1412 }
1413 $to['Y'] = $from['Y'] = $now['year'] - $subtractYear;
1414 if ($to['M'] > 12) {
1415 $to['M'] = 3 * ($quarter - 3);
1416 $to['Y'] = $now['year'];
1417 }
1418 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1419 break;
1420
1421 case 'earlier':
1422 $quarter = ceil($now['mon'] / 3) - 1;
1423 $subtractYear = 0;
1424 if ($quarter <= 0) {
1425 $subtractYear = 1;
1426 $quarter += 4;
1427 }
1428 $to['M'] = 3 * $quarter;
1429 $to['Y'] = $from['Y'] = $now['year'] - $subtractYear;
1430 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1431 unset($from);
1432 break;
1433
1434 case 'greater':
1435 $quarter = ceil($now['mon'] / 3);
1436 $from['d'] = 1;
1437 $from['M'] = (3 * $quarter) - 2;
1438 $from['Y'] = $now['year'];
1439 unset($to);
1440 break;
1441
1442 case 'greater_previous':
1443 $quarter = ceil($now['mon'] / 3) - 1;
1444 $subtractYear = 0;
1445 if ($quarter <= 0) {
1446 $subtractYear = 1;
1447 $quarter += 4;
1448 }
1449 $from['M'] = 3 * $quarter;
1450 $from['Y'] = $from['Y'] = $now['year'] - $subtractYear;
1451 $from['d'] = date('t', mktime(0, 0, 0, $from['M'], 1, $from['Y']));
1452 unset($to);
1453 break;
1454
1455 case 'ending':
1456 $to['d'] = $now['mday'];
1457 $to['M'] = $now['mon'];
1458 $to['Y'] = $now['year'];
1459 $to['H'] = 23;
1460 $to['i'] = $to['s'] = 59;
1461 $from = self::intervalAdd('day', -90, $to);
1462 $from = self::intervalAdd('second', 1, $from);
1463 break;
1464
1465 case 'current':
1466 $quarter = ceil($now['mon'] / 3);
1467 $from['d'] = 1;
1468 $from['M'] = (3 * $quarter) - 2;
1469 $from['Y'] = $now['year'];
1470 $to['d'] = $now['mday'];
1471 $to['M'] = $now['mon'];
1472 $to['Y'] = $now['year'];
1473 $to['H'] = 23;
1474 $to['i'] = $to['s'] = 59;
1475 break;
1476
1477 case 'less':
1478 $quarter = ceil($now['mon'] / 3);
1479 $to['M'] = 3 * $quarter;
1480 $to['Y'] = $now['year'];
1481 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $now['year']));
1482 unset($from);
1483 break;
1484
1485 case 'next':
1486 $difference = -1;
1487 $subtractYear = 0;
1488 $quarter = ceil($now['mon'] / 3);
1489 $quarter = $quarter - $difference;
1490 // CRM-14550 QA Fix
1491 if ($quarter > 4) {
1492 $now['year'] = $now['year'] + 1;
1493 $quarter = 1;
1494 }
1495 if ($quarter <= 0) {
1496 $subtractYear = 1;
1497 $quarter += 4;
1498 }
1499 $from['d'] = 1;
1500 $from['M'] = (3 * $quarter) - 2;
1501 $to['M'] = 3 * $quarter;
1502 $to['Y'] = $from['Y'] = $now['year'] - $subtractYear;
1503 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1504 break;
1505
1506 case 'starting':
1507 $from['d'] = $now['mday'];
1508 $from['M'] = $now['mon'];
1509 $from['Y'] = $now['year'];
1510 $from['H'] = 00;
1511 $from['i'] = $to['s'] = 00;
1512 $to = self::intervalAdd('day', 90, $from);
1513 $to = self::intervalAdd('second', -1, $to);
1514 break;
1515
1516 default:
1517 if ($relativeTermPrefix === 'ending') {
1518 $to['d'] = $now['mday'];
1519 $to['M'] = $now['mon'];
1520 $to['Y'] = $now['year'];
1521 $to['H'] = 23;
1522 $to['i'] = $to['s'] = 59;
1523 $from = self::intervalAdd('month', -($relativeTermSuffix * 3), $to);
1524 $from = self::intervalAdd('second', 1, $from);
1525 }
1526 }
1527 break;
1528
1529 case 'month':
1530 switch ($relativeTerm) {
1531 case 'this':
1532 $from['d'] = 1;
1533 $to['d'] = date('t', mktime(0, 0, 0, $now['mon'], 1, $now['year']));
1534 $from['M'] = $to['M'] = $now['mon'];
1535 $from['Y'] = $to['Y'] = $now['year'];
1536 break;
1537
1538 case 'previous':
1539 $from['d'] = 1;
1540 if ($now['mon'] == 1) {
1541 $from['M'] = $to['M'] = 12;
1542 $from['Y'] = $to['Y'] = $now['year'] - 1;
1543 }
1544 else {
1545 $from['M'] = $to['M'] = $now['mon'] - 1;
1546 $from['Y'] = $to['Y'] = $now['year'];
1547 }
1548 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1549 break;
1550
1551 case 'previous_before':
1552 $from['d'] = 1;
1553 if ($now['mon'] < 3) {
1554 $from['M'] = $to['M'] = 10 + $now['mon'];
1555 $from['Y'] = $to['Y'] = $now['year'] - 1;
1556 }
1557 else {
1558 $from['M'] = $to['M'] = $now['mon'] - 2;
1559 $from['Y'] = $to['Y'] = $now['year'];
1560 }
1561 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1562 break;
1563
1564 case 'previous_2':
1565 $from['d'] = 1;
1566 if ($now['mon'] < 3) {
1567 $from['M'] = 10 + $now['mon'];
1568 $from['Y'] = $now['year'] - 1;
1569 }
1570 else {
1571 $from['M'] = $now['mon'] - 2;
1572 $from['Y'] = $now['year'];
1573 }
1574
1575 if ($now['mon'] == 1) {
1576 $to['M'] = 12;
1577 $to['Y'] = $now['year'] - 1;
1578 }
1579 else {
1580 $to['M'] = $now['mon'] - 1;
1581 $to['Y'] = $now['year'];
1582 }
1583
1584 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1585 break;
1586
1587 case 'earlier':
1588 // before end of past month
1589 if ($now['mon'] == 1) {
1590 $to['M'] = 12;
1591 $to['Y'] = $now['year'] - 1;
1592 }
1593 else {
1594 $to['M'] = $now['mon'] - 1;
1595 $to['Y'] = $now['year'];
1596 }
1597
1598 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1599 unset($from);
1600 break;
1601
1602 case 'greater':
1603 $from['d'] = 1;
1604 $from['M'] = $now['mon'];
1605 $from['Y'] = $now['year'];
1606 unset($to);
1607 break;
1608
1609 case 'greater_previous':
1610 // from end of past month
1611 if ($now['mon'] == 1) {
1612 $from['M'] = 12;
1613 $from['Y'] = $now['year'] - 1;
1614 }
1615 else {
1616 $from['M'] = $now['mon'] - 1;
1617 $from['Y'] = $now['year'];
1618 }
1619
1620 $from['d'] = date('t', mktime(0, 0, 0, $from['M'], 1, $from['Y']));
1621 unset($to);
1622 break;
1623
1624 case 'ending_2':
1625 $to['d'] = $now['mday'];
1626 $to['M'] = $now['mon'];
1627 $to['Y'] = $now['year'];
1628 $to['H'] = 23;
1629 $to['i'] = $to['s'] = 59;
1630 $from = self::intervalAdd('day', -60, $to);
1631 $from = self::intervalAdd('second', 1, $from);
1632 break;
1633
1634 case 'ending':
1635 $to['d'] = $now['mday'];
1636 $to['M'] = $now['mon'];
1637 $to['Y'] = $now['year'];
1638 $to['H'] = 23;
1639 $to['i'] = $to['s'] = 59;
1640 $from = self::intervalAdd('day', -30, $to);
1641 $from = self::intervalAdd('second', 1, $from);
1642 break;
1643
1644 case 'current':
1645 $from['d'] = 1;
1646 $from['M'] = $now['mon'];
1647 $from['Y'] = $now['year'];
1648 $to['d'] = $now['mday'];
1649 $to['M'] = $now['mon'];
1650 $to['Y'] = $now['year'];
1651 $to['H'] = 23;
1652 $to['i'] = $to['s'] = 59;
1653 break;
1654
1655 case 'less':
1656 // CRM-14550 QA Fix
1657 $to['Y'] = $now['year'];
1658 $to['M'] = $now['mon'];
1659 $to['d'] = date('t', mktime(0, 0, 0, $now['mon'], 1, $now['year']));
1660 unset($from);
1661 break;
1662
1663 case 'next':
1664 $from['d'] = 1;
1665 if ($now['mon'] == 12) {
1666 $from['M'] = $to['M'] = 1;
1667 $from['Y'] = $to['Y'] = $now['year'] + 1;
1668 }
1669 else {
1670 $from['M'] = $to['M'] = $now['mon'] + 1;
1671 $from['Y'] = $to['Y'] = $now['year'];
1672 }
1673 $to['d'] = date('t', mktime(0, 0, 0, $to['M'], 1, $to['Y']));
1674 break;
1675
1676 case 'starting':
1677 $from['d'] = $now['mday'];
1678 $from['M'] = $now['mon'];
1679 $from['Y'] = $now['year'];
1680 $from['H'] = 00;
1681 $from['i'] = $to['s'] = 00;
1682 $to = self::intervalAdd('day', 30, $from);
1683 $to = self::intervalAdd('second', -1, $to);
1684 break;
1685
1686 case 'starting_2':
1687 $from['d'] = $now['mday'];
1688 $from['M'] = $now['mon'];
1689 $from['Y'] = $now['year'];
1690 $from['H'] = 00;
1691 $from['i'] = $to['s'] = 00;
1692 $to = self::intervalAdd('day', 60, $from);
1693 $to = self::intervalAdd('second', -1, $to);
1694 break;
1695
1696 default:
1697 if ($relativeTermPrefix === 'ending') {
1698 $to['d'] = $now['mday'];
1699 $to['M'] = $now['mon'];
1700 $to['Y'] = $now['year'];
1701 $to['H'] = 23;
1702 $to['i'] = $to['s'] = 59;
1703 $from = self::intervalAdd($unit, -$relativeTermSuffix, $to);
1704 $from = self::intervalAdd('second', 1, $from);
1705 }
1706 }
1707 break;
1708
1709 case 'week':
1710 $weekFirst = Civi::settings()->get('weekBegins');
1711 $thisDay = $now['wday'];
1712 if ($weekFirst > $thisDay) {
1713 $diffDay = $thisDay - $weekFirst + 7;
1714 }
1715 else {
1716 $diffDay = $thisDay - $weekFirst;
1717 }
1718 switch ($relativeTerm) {
1719 case 'this':
1720 $from['d'] = $now['mday'];
1721 $from['M'] = $now['mon'];
1722 $from['Y'] = $now['year'];
1723 $from = self::intervalAdd('day', -1 * ($diffDay), $from);
1724 $to = self::intervalAdd('day', 6, $from);
1725 break;
1726
1727 case 'previous':
1728 $from['d'] = $now['mday'];
1729 $from['M'] = $now['mon'];
1730 $from['Y'] = $now['year'];
1731 $from = self::intervalAdd('day', -1 * ($diffDay) - 7, $from);
1732 $to = self::intervalAdd('day', 6, $from);
1733 break;
1734
1735 case 'previous_before':
1736 $from['d'] = $now['mday'];
1737 $from['M'] = $now['mon'];
1738 $from['Y'] = $now['year'];
1739 $from = self::intervalAdd('day', -1 * ($diffDay) - 14, $from);
1740 $to = self::intervalAdd('day', 6, $from);
1741 break;
1742
1743 case 'previous_2':
1744 $from['d'] = $now['mday'];
1745 $from['M'] = $now['mon'];
1746 $from['Y'] = $now['year'];
1747 $from = self::intervalAdd('day', -1 * ($diffDay) - 14, $from);
1748 $to = self::intervalAdd('day', 13, $from);
1749 break;
1750
1751 case 'earlier':
1752 $to['d'] = $now['mday'];
1753 $to['M'] = $now['mon'];
1754 $to['Y'] = $now['year'];
1755 $to = self::intervalAdd('day', -1 * ($diffDay) - 1, $to);
1756 unset($from);
1757 break;
1758
1759 case 'greater':
1760 $from['d'] = $now['mday'];
1761 $from['M'] = $now['mon'];
1762 $from['Y'] = $now['year'];
1763 $from = self::intervalAdd('day', -1 * ($diffDay), $from);
1764 unset($to);
1765 break;
1766
1767 case 'greater_previous':
1768 $from['d'] = $now['mday'];
1769 $from['M'] = $now['mon'];
1770 $from['Y'] = $now['year'];
1771 $from = self::intervalAdd('day', -1 * ($diffDay) - 1, $from);
1772 unset($to);
1773 break;
1774
1775 case 'ending':
1776 $to['d'] = $now['mday'];
1777 $to['M'] = $now['mon'];
1778 $to['Y'] = $now['year'];
1779 $to['H'] = 23;
1780 $to['i'] = $to['s'] = 59;
1781 $from = self::intervalAdd('day', -7, $to);
1782 $from = self::intervalAdd('second', 1, $from);
1783 break;
1784
1785 case 'current':
1786 $from['d'] = $now['mday'];
1787 $from['M'] = $now['mon'];
1788 $from['Y'] = $now['year'];
1789 $from = self::intervalAdd('day', -1 * ($diffDay), $from);
1790 $to['d'] = $now['mday'];
1791 $to['M'] = $now['mon'];
1792 $to['Y'] = $now['year'];
1793 $to['H'] = 23;
1794 $to['i'] = $to['s'] = 59;
1795 break;
1796
1797 case 'less':
1798 $to['d'] = $now['mday'];
1799 $to['M'] = $now['mon'];
1800 $to['Y'] = $now['year'];
1801 // CRM-14550 QA Fix
1802 $to = self::intervalAdd('day', -1 * ($diffDay) + 6, $to);
1803 unset($from);
1804 break;
1805
1806 case 'next':
1807 $from['d'] = $now['mday'];
1808 $from['M'] = $now['mon'];
1809 $from['Y'] = $now['year'];
1810 $from = self::intervalAdd('day', -1 * ($diffDay) + 7, $from);
1811 $to = self::intervalAdd('day', 6, $from);
1812 break;
1813
1814 case 'starting':
1815 $from['d'] = $now['mday'];
1816 $from['M'] = $now['mon'];
1817 $from['Y'] = $now['year'];
1818 $from['H'] = 00;
1819 $from['i'] = $to['s'] = 00;
1820 $to = self::intervalAdd('day', 7, $from);
1821 $to = self::intervalAdd('second', -1, $to);
1822 break;
1823
1824 default:
1825 if ($relativeTermPrefix === 'ending') {
1826 $to['d'] = $now['mday'];
1827 $to['M'] = $now['mon'];
1828 $to['Y'] = $now['year'];
1829 $to['H'] = 23;
1830 $to['i'] = $to['s'] = 59;
1831 $from = self::intervalAdd($unit, -$relativeTermSuffix, $to);
1832 $from = self::intervalAdd('second', 1, $from);
1833 }
1834 }
1835 break;
1836
1837 case 'day':
1838 switch ($relativeTerm) {
1839 case 'this':
1840 $from['d'] = $to['d'] = $now['mday'];
1841 $from['M'] = $to['M'] = $now['mon'];
1842 $from['Y'] = $to['Y'] = $now['year'];
1843 break;
1844
1845 case 'previous':
1846 $from['d'] = $now['mday'];
1847 $from['M'] = $now['mon'];
1848 $from['Y'] = $now['year'];
1849 $from = self::intervalAdd('day', -1, $from);
1850 $to['d'] = $from['d'];
1851 $to['M'] = $from['M'];
1852 $to['Y'] = $from['Y'];
1853 break;
1854
1855 case 'previous_before':
1856 $from['d'] = $now['mday'];
1857 $from['M'] = $now['mon'];
1858 $from['Y'] = $now['year'];
1859 $from = self::intervalAdd('day', -2, $from);
1860 $to['d'] = $from['d'];
1861 $to['M'] = $from['M'];
1862 $to['Y'] = $from['Y'];
1863 break;
1864
1865 case 'previous_2':
1866 $from['d'] = $to['d'] = $now['mday'];
1867 $from['M'] = $to['M'] = $now['mon'];
1868 $from['Y'] = $to['Y'] = $now['year'];
1869 $from = self::intervalAdd('day', -2, $from);
1870 $to = self::intervalAdd('day', -1, $to);
1871 break;
1872
1873 case 'earlier':
1874 $to['d'] = $now['mday'];
1875 $to['M'] = $now['mon'];
1876 $to['Y'] = $now['year'];
1877 $to = self::intervalAdd('day', -1, $to);
1878 unset($from);
1879 break;
1880
1881 case 'greater':
1882 $from['d'] = $now['mday'];
1883 $from['M'] = $now['mon'];
1884 $from['Y'] = $now['year'];
1885 unset($to);
1886 break;
1887
1888 case 'starting':
1889 $to['d'] = $now['mday'];
1890 $to['M'] = $now['mon'];
1891 $to['Y'] = $now['year'];
1892 $to = self::intervalAdd('day', 1, $to);
1893 $from['d'] = $to['d'];
1894 $from['M'] = $to['M'];
1895 $from['Y'] = $to['Y'];
1896 break;
1897
1898 default:
1899 if ($relativeTermPrefix === 'ending') {
1900 $to['d'] = $now['mday'];
1901 $to['M'] = $now['mon'];
1902 $to['Y'] = $now['year'];
1903 $to['H'] = 23;
1904 $to['i'] = $to['s'] = 59;
1905 $from = self::intervalAdd($unit, -$relativeTermSuffix, $to);
1906 $from = self::intervalAdd('second', 1, $from);
1907 }
1908 }
1909 break;
1910 }
1911
1912 $dateRange['from'] = empty($from) ? NULL : self::format($from);
1913 $dateRange['to'] = empty($to) ? NULL : self::format($to);
1914 return $dateRange;
1915 }
1916
1917 /**
1918 * Calculate current fiscal year based on the fiscal month and day.
1919 *
1920 * @param int $fyDate
1921 * Fiscal start date.
1922 *
1923 * @param int $fyMonth
1924 * Fiscal Start Month.
1925 *
1926 * @return int
1927 * $fy Current Fiscal Year
1928 */
1929 public static function calculateFiscalYear($fyDate, $fyMonth) {
1930 $date = date("Y-m-d");
1931 $currentYear = date("Y");
1932
1933 // recalculate the date because month 4::04 make the difference
1934 $fiscalYear = explode('-', date("Y-m-d", mktime(0, 0, 0, $fyMonth, $fyDate, $currentYear)));
1935 $fyDate = $fiscalYear[2];
1936 $fyMonth = $fiscalYear[1];
1937 $fyStartDate = date("Y-m-d", mktime(0, 0, 0, $fyMonth, $fyDate, $currentYear));
1938
1939 if ($fyStartDate > $date) {
1940 $fy = intval(intval($currentYear) - 1);
1941 }
1942 else {
1943 $fy = intval($currentYear);
1944 }
1945 return $fy;
1946 }
1947
1948 /**
1949 * Function to process date, convert to mysql format
1950 *
1951 * @param string $date
1952 * Date string.
1953 * @param string $time
1954 * Time string.
1955 * @param bool|string $returnNullString 'null' needs to be returned
1956 * so that db oject will set null in db
1957 * @param string $format
1958 * Expected return date format.( default is mysql ).
1959 *
1960 * @return string
1961 * date format that is excepted by mysql
1962 */
1963 public static function processDate($date, $time = NULL, $returnNullString = FALSE, $format = 'YmdHis') {
1964 $mysqlDate = NULL;
1965
1966 if ($returnNullString) {
1967 $mysqlDate = 'null';
1968 }
1969
1970 if (trim($date ?? '')) {
1971 $mysqlDate = date($format, strtotime($date . ' ' . $time));
1972 }
1973
1974 return $mysqlDate;
1975 }
1976
1977 /**
1978 * Add the metadata about a date field to the field.
1979 *
1980 * This metadata will work with the call $form->add('datepicker', ...
1981 *
1982 * @param array $fieldMetaData
1983 * @param array $field
1984 *
1985 * @return array
1986 */
1987 public static function addDateMetadataToField($fieldMetaData, $field) {
1988 if (isset($fieldMetaData['html'])) {
1989 $field['html_type'] = $fieldMetaData['html']['type'];
1990 if ($field['html_type'] === 'Select Date') {
1991 if (!isset($field['date_format'])) {
1992 $dateAttributes = CRM_Core_SelectValues::date($fieldMetaData['html']['formatType'], NULL, NULL, NULL, 'Input');
1993 $field['start_date_years'] = $dateAttributes['minYear'];
1994 $field['end_date_years'] = $dateAttributes['maxYear'];
1995 $field['date_format'] = $dateAttributes['format'];
1996 $field['is_datetime_field'] = TRUE;
1997 $field['time_format'] = $dateAttributes['time'];
1998 $field['smarty_view_format'] = $dateAttributes['smarty_view_format'];
1999 }
2000 $field['datepicker']['extra'] = self::getDatePickerExtra($field);
2001 $field['datepicker']['attributes'] = self::getDatePickerAttributes($field);
2002 }
2003 }
2004 return $field;
2005 }
2006
2007 /**
2008 * Get the fields required for the 'extra' parameter when adding a datepicker.
2009 *
2010 * @param array $field
2011 *
2012 * @return array
2013 */
2014 public static function getDatePickerExtra($field) {
2015 $extra = [];
2016 if (isset($field['date_format'])) {
2017 $extra['date'] = $field['date_format'];
2018 $extra['time'] = $field['time_format'];
2019 }
2020 $thisYear = date('Y');
2021 if (isset($field['start_date_years'])) {
2022 $extra['minDate'] = date('Y-m-d', strtotime('-' . ($thisYear - $field['start_date_years']) . ' years'));
2023 }
2024 if (isset($field['end_date_years'])) {
2025 $extra['maxDate'] = date('Y-m-d', strtotime('-' . ($thisYear - $field['end_date_years']) . ' years'));
2026 }
2027 return $extra;
2028 }
2029
2030 /**
2031 * Get the attributes parameters required for datepicker.
2032 *
2033 * @param array $field
2034 * Field metadata
2035 *
2036 * @return array
2037 * Array ready to pass to $this->addForm('datepicker' as attributes.
2038 */
2039 public static function getDatePickerAttributes(&$field) {
2040 $attributes = [];
2041 $dateAttributes = [
2042 'start_date_years' => 'minYear',
2043 'end_date_years' => 'maxYear',
2044 'date_format' => 'format',
2045 ];
2046 foreach ($dateAttributes as $dateAttribute => $mapTo) {
2047 if (isset($field[$dateAttribute])) {
2048 $attributes[$mapTo] = $field[$dateAttribute];
2049 }
2050 }
2051 return $attributes;
2052 }
2053
2054 /**
2055 * Function to convert mysql to date plugin format.
2056 *
2057 * @param string $mysqlDate
2058 * Date string.
2059 *
2060 * @param null $formatType
2061 * @param null $format
2062 * @param null $timeFormat
2063 *
2064 * @return array
2065 * and time
2066 */
2067 public static function setDateDefaults($mysqlDate = NULL, $formatType = NULL, $format = NULL, $timeFormat = NULL) {
2068 // if date is not passed assume it as today
2069 if (!$mysqlDate) {
2070 $mysqlDate = date('Y-m-d G:i:s');
2071 }
2072
2073 $config = CRM_Core_Config::singleton();
2074 if ($formatType) {
2075 // get actual format
2076 $params = ['name' => $formatType];
2077 $values = [];
2078 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, $values);
2079
2080 if ($values['date_format']) {
2081 $format = $values['date_format'];
2082 }
2083
2084 if (isset($values['time_format'])) {
2085 $timeFormat = $values['time_format'];
2086 }
2087 }
2088
2089 $dateFormat = 'm/d/Y';
2090 $date = date($dateFormat, strtotime($mysqlDate));
2091
2092 if (!$timeFormat) {
2093 $timeFormat = $config->timeInputFormat;
2094 }
2095
2096 $actualTimeFormat = "g:iA";
2097 $appendZeroLength = 7;
2098 if ($timeFormat > 1) {
2099 $actualTimeFormat = "G:i";
2100 $appendZeroLength = 5;
2101 }
2102
2103 $time = date($actualTimeFormat, strtotime($mysqlDate));
2104
2105 // need to append zero for hours < 10
2106 if (strlen($time) < $appendZeroLength) {
2107 $time = '0' . $time;
2108 }
2109
2110 return [$date, $time];
2111 }
2112
2113 /**
2114 * Function get date format.
2115 *
2116 * @param string $formatType
2117 * Date name e.g. birth.
2118 *
2119 * @return string
2120 */
2121 public static function getDateFormat($formatType = NULL) {
2122 $format = NULL;
2123 if ($formatType) {
2124 $format = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_PreferencesDate',
2125 $formatType, 'date_format', 'name'
2126 );
2127 }
2128
2129 if (!$format) {
2130 $config = CRM_Core_Config::singleton();
2131 $format = $config->dateInputFormat;
2132 }
2133 return $format;
2134 }
2135
2136 /**
2137 * Date formatting for imports where date format is specified.
2138 *
2139 * Note this is used for imports (only) because the importer can
2140 * specify the format.
2141 *
2142 * Tests are in CRM_Utils_DateTest::testFormatDate
2143 *
2144 * @param $date
2145 * Date string as entered.
2146 * @param $dateType
2147 * One of the constants like CRM_Core_Form_Date::DATE_yyyy_mm_dd.
2148 *
2149 * @return null|string
2150 */
2151 public static function formatDate($date, $dateType) {
2152 if (empty($date)) {
2153 return NULL;
2154 }
2155
2156 // 1. first convert date to default format.
2157 // 2. append time to default formatted date (might be removed during format)
2158 // 3. validate date / date time.
2159 // 4. If date and time then convert to default date time format.
2160
2161 $dateKey = 'date';
2162 $dateParams = [$dateKey => $date];
2163
2164 if (CRM_Utils_Date::convertToDefaultDate($dateParams, $dateType, $dateKey)) {
2165 $dateVal = $dateParams[$dateKey];
2166 if ($dateType == 1) {
2167 $matches = [];
2168 // The seconds part of this regex is not quite right - but it does succeed
2169 // in clarifying whether there is a time component or not - which is all it is meant
2170 // to do.
2171 if (preg_match('/(\s(([01]\d)|[2][0-3]):([0-5]\d):?[0-5]?\d?)$/', $date, $matches)) {
2172 if (strpos($date, '-') !== FALSE) {
2173 $dateVal .= array_shift($matches);
2174 }
2175 if (!CRM_Utils_Rule::dateTime($dateVal)) {
2176 return NULL;
2177 }
2178 $dateVal = CRM_Utils_Date::customFormat(preg_replace("/(:|\s)?/", '', $dateVal), '%Y%m%d%H%i%s');
2179 return $dateVal;
2180 }
2181 }
2182
2183 // validate date.
2184 return CRM_Utils_Rule::date($dateVal) ? $dateVal : NULL;
2185 }
2186
2187 return NULL;
2188 }
2189
2190 /**
2191 * Function to return days of the month.
2192 *
2193 * @return array
2194 */
2195 public static function getCalendarDayOfMonth() {
2196 $month = [];
2197 for ($i = 1; $i <= 31; $i++) {
2198 $month[$i] = $i;
2199 if ($i == 31) {
2200 $month[$i] = $i . ' / Last day of month';
2201 }
2202 }
2203 return $month;
2204 }
2205
2206 /**
2207 * Convert a relative date format to an api field.
2208 *
2209 * @param array $params
2210 * @param string $dateField
2211 * @param bool $isDatePicker
2212 * Non datepicker fields are deprecated. Exterminate Exterminate.
2213 * (but for now handle them).
2214 */
2215 public static function convertFormDateToApiFormat(&$params, $dateField, $isDatePicker = TRUE) {
2216 if (!empty($params[$dateField . '_relative'])) {
2217 $dates = CRM_Utils_Date::getFromTo($params[$dateField . '_relative'], NULL, NULL);
2218 unset($params[$dateField . '_relative']);
2219 }
2220 if (!empty($params[$dateField . '_low'])) {
2221 $dates[0] = $isDatePicker ? $params[$dateField . '_low'] : date('Y-m-d H:i:s', strtotime($params[$dateField . '_low']));
2222 unset($params[$dateField . '_low']);
2223 }
2224 if (!empty($params[$dateField . '_high'])) {
2225 $dates[1] = $isDatePicker ? $params[$dateField . '_high'] : date('Y-m-d 23:59:59', strtotime($params[$dateField . '_high']));
2226 unset($params[$dateField . '_high']);
2227 }
2228 if (empty($dates)) {
2229 return;
2230 }
2231 if (empty($dates[0])) {
2232 $params[$dateField] = ['<=' => $dates[1]];
2233 }
2234 elseif (empty($dates[1])) {
2235 $params[$dateField] = ['>=' => $dates[0]];
2236 }
2237 else {
2238 $params[$dateField] = ['BETWEEN' => $dates];
2239 }
2240 }
2241
2242 /**
2243 * Print out a date object in specified format in local timezone
2244 *
2245 * @param DateTimeObject $dateObject
2246 * @param string $format
2247 * @return string
2248 */
2249 public static function convertDateToLocalTime($dateObject, $format = 'YmdHis') {
2250 $systemTimeZone = new DateTimeZone(CRM_Core_Config::singleton()->userSystem->getTimeZoneString());
2251 $dateObject->setTimezone($systemTimeZone);
2252 return $dateObject->format($format);
2253 }
2254
2255 /**
2256 * Check if the value returned by a date picker has a date section (ie: includes
2257 * a '-' character) if it includes a time section (ie: includes a ':').
2258 *
2259 * @param string $value
2260 * A date/time string input from a datepicker value.
2261 *
2262 * @return bool
2263 * TRUE if valid, FALSE if there is a time without a date.
2264 */
2265 public static function datePickerValueWithTimeHasDate($value) {
2266 // If there's no : (time) or a : and a - (date) then return true
2267 return (
2268 strpos($value, ':') === FALSE
2269 || strpos($value, ':') !== FALSE && strpos($value, '-') !== FALSE
2270 );
2271 }
2272
2273 /**
2274 * Validate start and end dates entered on a form to make sure they are
2275 * logical. Expects the form keys to be start_date and end_date.
2276 *
2277 * @param string $startFormKey
2278 * The form element key of the 'start date'
2279 * @param string $startValue
2280 * The value of the 'start date'
2281 * @param string $endFormKey
2282 * The form element key of the 'end date'
2283 * @param string $endValue
2284 * The value of the 'end date'
2285 *
2286 * @return array|bool
2287 * TRUE if valid, an array of the erroneous form key, and error message to
2288 * use otherwise.
2289 */
2290 public static function validateStartEndDatepickerInputs($startFormKey, $startValue, $endFormKey, $endValue) {
2291
2292 // Check date as well as time is set
2293 if (!empty($startValue) && !self::datePickerValueWithTimeHasDate($startValue)) {
2294 return ['key' => $startFormKey, 'message' => ts('Please enter a date as well as a time.')];
2295 }
2296 if (!empty($endValue) && !self::datePickerValueWithTimeHasDate($endValue)) {
2297 return ['key' => $endFormKey, 'message' => ts('Please enter a date as well as a time.')];
2298 }
2299
2300 // Check end date is after start date
2301 if (!empty($startValue) && !empty($endValue) && $endValue < $startValue) {
2302 return ['key' => $endFormKey, 'message' => ts('The end date should be after the start date.')];
2303 }
2304
2305 return TRUE;
2306 }
2307
2308 }