12 private static $weekHolidays;
13 private static $yearHolidays;
14 private static $startWorkDay;
15 private static $endWorkDay;
16 private static $yearWorkdays;
24 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_DATE_DESCRIPTION'),
28 'func' =>
'callDateAdd',
29 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_DATEADD_DESCRIPTION'),
33 'func' =>
'callDateDiff',
34 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_DATEDIFF_DESCRIPTION'),
38 'func' =>
'callAddWorkDays',
39 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_ADDWORKDAYS_DESCRIPTION'),
43 'func' =>
'callWorkDateAdd',
44 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_WORKDATEADD_DESCRIPTION'),
48 'func' =>
'callIsWorkDay',
49 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_ISWORKDAY_DESCRIPTION'),
53 'func' =>
'callIsWorkTime',
54 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_ISWORKTIME_DESCRIPTION'),
58 'func' =>
'callToUserDate',
59 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_TOUSERDATE_DESCRIPTION'),
61 'getuserdateoffset' => [
63 'func' =>
'callGetUserDateOffset',
64 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_GETUSERDATEOFFSET_DESCRIPTION'),
68 'func' =>
'callStrtotime',
69 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_STRTOTIME_DESCRIPTION'),
73 'func' =>
'callLocDate',
74 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_LOCDATE_DESCRIPTION'),
78 'func' =>
'callSetTime',
79 'description' =>
Loc::getMessage(
'BIZPROC_CALC_FUNCTION_SETTIME_DESCRIPTION'),
87 $offset = $this->getDateTimeOffset($date);
88 $date = $this->makeTimestamp($date);
91 if ($date ===
false || ($interval && !is_scalar($interval)))
103 $interval = trim($interval);
105 if (mb_substr($interval, 0, 1) ===
"-")
107 $interval = mb_substr($interval, 1);
135 while (preg_match(
'/\s*([\d]+)\s*([a-z]+)\s*/i', $interval, $match))
137 $match2 = mb_strtolower($match[2]);
138 if (array_key_exists($match2, $arMap))
140 $arInterval[$arMap[$match2]] = ($bMinus ? -intval($match[1]) : intval($match[1]));
143 $p = mb_strpos($interval, $match[0]);
144 $interval = mb_substr($interval, $p + mb_strlen($match[0]));
149 $newDate = AddToTimeStamp($arInterval, $date);
159 $offset = $this->getDateTimeOffset($date);
165 $userArgs = clone $args;
166 $userArgs->
setArgs([$user, $date]);
169 $offset = $this->getDateTimeOffset($date);
172 $date = $this->makeTimestamp($date,
true);
174 if ($date ===
false || ($paramInterval && !is_scalar($paramInterval)))
179 if (empty($paramInterval) || !Main\Loader::includeModule(
'calendar'))
184 $paramInterval = trim($paramInterval);
186 if (mb_substr($paramInterval, 0, 1) ===
"-")
188 $paramInterval = mb_substr($paramInterval, 1);
192 $workDayInterval = $this->getWorkDayInterval();
194 "d" => $workDayInterval,
195 "day" => $workDayInterval,
196 "days" => $workDayInterval,
207 while (preg_match(
'/\s*([\d]+)\s*([a-z]+)\s*/i', $paramInterval, $match))
209 $match2 = mb_strtolower($match[2]);
210 if (array_key_exists($match2, $intervalMap))
212 $interval += intval($match[1]) * $intervalMap[$match2];
215 $p = mb_strpos($paramInterval, $match[0]);
216 $paramInterval = mb_substr($paramInterval, $p + mb_strlen($match[0]));
219 if (date(
'H:i:s', $date) ===
'00:00:00')
222 $date += $this->getCalendarWorkTime()[0];
225 $date = $this->getNearestWorkTime($date, $multiplier);
228 $days = (int)floor($interval / $workDayInterval);
229 $hours = $interval % $workDayInterval;
231 $remainTimestamp = $this->getWorkDayRemainTimestamp($date, $multiplier);
235 $date = $this->addWorkDay($date, $days * $multiplier);
238 if ($hours > $remainTimestamp)
240 $date += $multiplier < 0 ? -$remainTimestamp - 60 : $remainTimestamp + 60;
241 $date = $this->getNearestWorkTime($date, $multiplier) + (($hours - $remainTimestamp) * $multiplier);
245 $date += $multiplier * $hours;
257 $offset = $this->getDateTimeOffset($date);
260 if (($date = $this->makeTimestamp($date)) ===
false)
265 if ($days === 0 || !Main\Loader::includeModule(
'calendar'))
270 $date = $this->addWorkDay($date, $days);
277 if (!Main\Loader::includeModule(
'calendar'))
287 $userArgs = clone $args;
288 $userArgs->
setArgs([$user, $date]);
293 if (($date = $this->makeTimestamp($date,
true)) ===
false)
298 return !$this->isHoliday($date);
303 if (!Main\Loader::includeModule(
'calendar'))
313 $userArgs = clone $args;
314 $userArgs->
setArgs([$user, $date]);
319 if (($date = $this->makeTimestamp($date,
true)) ===
false)
324 return !$this->isHoliday($date) && $this->isWorkTime($date);
333 if (!$date1 || !$date2 || !is_scalar($format))
338 $date1Formatted = $this->getDateTimeObject($date1);
339 $date2Formatted = $this->getDateTimeObject($date2);
341 if ($date1Formatted ===
false || $date2Formatted ===
false)
346 $interval = $date1Formatted->diff($date2Formatted);
348 return $interval ===
false ? null : $interval->format($format);
356 if (!$format || !is_string($format))
361 $ts = $date ? $this->makeTimestamp($date,
true) : time();
368 return date($format, $ts);
385 elseif (($date = $this->makeTimestamp($date)) ===
false)
390 $userId = \CBPHelper::extractFirstUser($user, $args->
getParser()->getActivity()->getDocumentId());
391 $offset = $userId ? \CTimeZone::GetOffset($userId,
true) : 0;
405 $userId = \CBPHelper::extractFirstUser($user, $args->
getParser()->getActivity()->getDocumentId());
412 return \CTimeZone::GetOffset($userId,
true);
420 $baseTimestamp = $baseDate ? $this->makeTimestamp($baseDate,
true) : time();
422 if (!$baseTimestamp || !is_scalar($datetime))
427 $timestamp = strtotime($datetime, (
int)$baseTimestamp);
429 if ($timestamp ===
false)
442 if (!$format || !is_string($format))
447 $reformFormat = $this->frameSymbolsInDateFormat($format);
448 $timestamp = $date ? $this->makeTimestamp($date,
true) : time();
455 $formattedDate = date($reformFormat, $timestamp);
457 if ($formattedDate ===
false)
462 return $this->replaceDateToLocDate($formattedDate, $reformFormat);
468 $offset = $this->getDateTimeOffset($date);
473 if (($date = $this->makeTimestamp($date,
true)) ===
false)
478 $dateTime = Main\Type\DateTime::createFromTimestamp($date);
479 $dateTime->setTime($hour, $minute, 0, 0);
481 $date = $dateTime->getTimestamp() - $offset;
486 private function makeTimestamp($date, $appendOffset =
false)
488 if (!$date || (!is_scalar($date) && !is_object($date)))
494 if (is_string($date) && Bizproc\BaseType\Value\Date::isSerialized($date))
499 if ($date instanceof Bizproc\BaseType\Value\
Date)
501 return $date->getTimestamp() + ($appendOffset ? $date->getOffset() : 0);
504 if (intval($date) .
"!" === $date .
"!")
509 if (($result = MakeTimeStamp($date, FORMAT_DATETIME)) ===
false)
511 if (($result = MakeTimeStamp($date, FORMAT_DATE)) ===
false)
513 if (($result = MakeTimeStamp($date,
"YYYY-MM-DD HH:MI:SS")) ===
false)
515 $result = MakeTimeStamp($date,
"YYYY-MM-DD");
522 private function getWorkDayTimestamp($date)
524 return date(
'H', $date) * 3600 + date(
'i', $date) * 60;
527 private function getWorkDayRemainTimestamp($date, $multiplier = 1)
529 $dayTs = $this->getWorkDayTimestamp($date);
530 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
531 return $multiplier < 0 ? $dayTs - $startSeconds : $endSeconds - $dayTs;
534 private function getWorkDayInterval()
536 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
537 return $endSeconds - $startSeconds;
540 private function isHoliday($date)
542 [$yearWorkdays] = $this->getCalendarWorkdays();
543 [$weekHolidays, $yearHolidays] = $this->getCalendarHolidays();
545 $dayOfYear = date(
'j.n', $date);
546 if (in_array($dayOfYear, $yearWorkdays,
true))
551 $dayOfWeek = date(
'w', $date);
552 if (in_array($dayOfWeek, $weekHolidays))
557 $dayOfYear = date(
'j.n', $date);
558 if (in_array($dayOfYear, $yearHolidays,
true))
566 private function isWorkTime($date)
568 $dayTs = $this->getWorkDayTimestamp($date);
569 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
570 return ($dayTs >= $startSeconds && $dayTs <= $endSeconds);
573 private function getNearestWorkTime($date, $multiplier = 1)
575 $reverse = $multiplier < 0;
576 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
577 $dayTimeStamp = $this->getWorkDayTimestamp($date);
579 if ($this->isHoliday($date))
581 $date -= $dayTimeStamp;
582 $date += $reverse ? -86400 + $endSeconds : $startSeconds;
583 $dayTimeStamp = $reverse ? $endSeconds : $startSeconds;
586 if (!$this->isWorkTime($date))
588 $date -= $dayTimeStamp;
590 if ($dayTimeStamp < $startSeconds)
592 $date += $reverse ? -86400 + $endSeconds : $startSeconds;
596 $date += $reverse ? $endSeconds : 86400 + $startSeconds;
600 if ($this->isHoliday($date))
602 $date = $this->addWorkDay($date, $reverse ? -1 : 1);
608 private function addWorkDay($date, $days)
619 while ($days > 0 && $iterations < 1000)
624 if ($this->isHoliday($date))
634 private function getCalendarHolidays()
636 if (static::$yearHolidays ===
null)
638 $calendarSettings = \CCalendar::GetSettings();
639 $weekHolidays = [0, 6];
642 if (isset($calendarSettings[
'week_holidays']))
644 $weekDays = [
'SU' => 0,
'MO' => 1,
'TU' => 2,
'WE' => 3,
'TH' => 4,
'FR' => 5,
'SA' => 6];
646 foreach ($calendarSettings[
'week_holidays'] as $day)
648 $weekHolidays[] = $weekDays[$day];
652 if (isset($calendarSettings[
'year_holidays']))
654 foreach (explode(
',', $calendarSettings[
'year_holidays']) as $yearHoliday)
656 $date = explode(
'.', trim($yearHoliday));
657 if (count($date) == 2 && $date[0] && $date[1])
659 $yearHolidays[] = (int)$date[0] .
'.' . (
int)$date[1];
663 static::$weekHolidays = $weekHolidays;
664 static::$yearHolidays = $yearHolidays;
667 return [static::$weekHolidays, static::$yearHolidays];
670 private function getCalendarWorkTime()
672 if (static::$startWorkDay ===
null)
675 $endSeconds = 24 * 3600 - 1;
677 $calendarSettings = \CCalendar::GetSettings();
678 if (!empty($calendarSettings[
'work_time_start']))
680 $time = explode(
'.', $calendarSettings[
'work_time_start']);
681 $startSeconds = $time[0] * 3600;
682 if (!empty($time[1]))
684 $startSeconds += $time[1] * 60;
688 if (!empty($calendarSettings[
'work_time_end']))
690 $time = explode(
'.', $calendarSettings[
'work_time_end']);
691 $endSeconds = $time[0] * 3600;
692 if (!empty($time[1]))
694 $endSeconds += $time[1] * 60;
697 static::$startWorkDay = $startSeconds;
698 static::$endWorkDay = $endSeconds;
700 return [static::$startWorkDay, static::$endWorkDay];
703 private function getCalendarWorkdays()
705 if (static::$yearWorkdays ===
null)
709 $calendarSettings = \CCalendar::GetSettings();
710 $calendarYearWorkdays = $calendarSettings[
'year_workdays'] ??
'';
712 foreach (explode(
',', $calendarYearWorkdays) as $yearWorkday)
714 $date = explode(
'.', trim($yearWorkday));
715 if (count($date) === 2 && $date[0] && $date[1])
717 $yearWorkdays[] = (int)$date[0] .
'.' . (
int)$date[1];
721 static::$yearWorkdays = $yearWorkdays;
724 return [static::$yearWorkdays];
727 private function getDateTimeObject($date)
729 if ($date instanceof Bizproc\BaseType\Value\Date)
731 return (
new \DateTime())->setTimestamp($date->getTimestamp());
733 elseif (is_array($date))
735 $date = \CBPHelper::flatten($date);
738 if (!is_scalar($date))
743 $df = Main\Type\DateTime::getFormat();
744 $df2 = Main\Type\Date::getFormat();
745 $date1Formatted = \DateTime::createFromFormat($df, $date);
746 if ($date1Formatted ===
false)
748 $date1Formatted = \DateTime::createFromFormat($df2, $date);
751 $date1Formatted->setTime(0, 0);
754 return $date1Formatted;
757 private function getDateTimeOffset($date)
759 if ($date instanceof Bizproc\BaseType\Value\Date)
761 return $date->getOffset();
767 private function frameSymbolsInDateFormat($format)
769 $complexSymbols = [
'j F',
'd F',
'jS F'];
770 $symbols = [
'D',
'l',
'F',
'M',
'r'];
773 foreach ($symbols as $symbol)
775 $frameRule[$symbol] =
'#' . $symbol .
'#';
776 $frameRule[
'\\' . $symbol] =
'\\' . $symbol;
778 foreach ($complexSymbols as $symbol)
780 $frameRule[$symbol] = substr($symbol, 0, -1) .
'#' . $symbol[-1] .
'_1#';
781 $frameRule[
'\\' . $symbol] =
'\\' . substr($symbol, 0, -1) .
'#' . $symbol[-1] .
'#';
784 return strtr($format, $frameRule);
787 private function frameNamesInFormattedDateRFC2822($formattedDate)
790 $pattern =
"/#(\w{3}), \d{2} (\w{3}) \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}#/";
791 if (preg_match_all($pattern, $formattedDate, $matches))
793 foreach ($matches[0] as $key => $match)
795 $day = $matches[1][$key];
796 $month = $matches[2][$key];
798 $reformMatch = str_replace(
800 [
'#' . $day .
'#',
'#' . $month .
'#'],
803 $reformMatch = substr($reformMatch, 1, -1);
805 $formattedDate = str_replace($match, $reformMatch, $formattedDate);
809 return $formattedDate;
812 private function replaceDateToLocDate($formattedDate, $format)
815 $dayNames = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'];
831 if (strpos($format,
'#r#') !==
false)
833 $formattedDate = $this->frameNamesInFormattedDateRFC2822($formattedDate);
836 $replacementRule = [];
837 foreach (array_merge($dayNames, $monthNames) as $name)
840 'BIZPROC_CALC_FUNCTION_LOCDATE_' . strtoupper($name)
842 $shortName = substr($name, 0, $lenShortName);
844 'BIZPROC_CALC_FUNCTION_LOCDATE_' . strtoupper($shortName) .
'_SHORT'
847 foreach ($monthNames as $monthName)
850 'BIZPROC_CALC_FUNCTION_LOCDATE_' . strtoupper($monthName) .
'_1'
854 return strtr($formattedDate, $replacementRule);