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