12 private static $weekHolidays;
13 private static $yearHolidays;
14 private static $startWorkDay;
15 private static $endWorkDay;
16 private static $yearWorkdays;
18 private const ONE_HOUR = 3600;
19 private const TWELVE_HOURS = 12 * self::ONE_HOUR;
27 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_DATE_DESCRIPTION'),
31 'func' =>
'callDateAdd',
32 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_DATEADD_DESCRIPTION'),
36 'func' =>
'callDateDiff',
37 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_DATEDIFF_DESCRIPTION'),
41 'func' =>
'callAddWorkDays',
42 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_ADDWORKDAYS_DESCRIPTION'),
46 'func' =>
'callWorkDateAdd',
47 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_WORKDATEADD_DESCRIPTION'),
51 'func' =>
'callIsWorkDay',
52 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_ISWORKDAY_DESCRIPTION'),
56 'func' =>
'callIsWorkTime',
57 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_ISWORKTIME_DESCRIPTION'),
61 'func' =>
'callToUserDate',
62 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_TOUSERDATE_DESCRIPTION'),
64 'getuserdateoffset' => [
66 'func' =>
'callGetUserDateOffset',
67 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_GETUSERDATEOFFSET_DESCRIPTION'),
71 'func' =>
'callStrtotime',
72 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_STRTOTIME_DESCRIPTION'),
76 'func' =>
'callLocDate',
77 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_LOCDATE_DESCRIPTION'),
81 'func' =>
'callSetTime',
82 'description' => Loc::getMessage(
'BIZPROC_CALC_FUNCTION_SETTIME_DESCRIPTION'),
90 $offset = $this->getDateTimeOffset($date);
91 $date = $this->makeTimestamp($date);
94 if ($date ===
false || ($interval && !is_scalar($interval)))
106 $interval = trim($interval);
108 if (mb_substr($interval, 0, 1) ===
"-")
110 $interval = mb_substr($interval, 1);
138 while (preg_match(
'/\s*([\d]+)\s*([a-z]+)\s*/i', $interval, $match))
140 $match2 = mb_strtolower($match[2]);
141 if (array_key_exists($match2, $arMap))
143 $arInterval[$arMap[$match2]] = ($bMinus ? -intval($match[1]) : intval($match[1]));
146 $p = mb_strpos($interval, $match[0]);
147 $interval = mb_substr($interval,
$p + mb_strlen($match[0]));
162 $offset = $this->getDateTimeOffset($date);
168 $userArgs = clone $args;
172 $offset = $this->getDateTimeOffset($date);
175 $date = $this->makeTimestamp($date,
true);
177 if ($date ===
false || ($paramInterval && !is_scalar($paramInterval)))
182 if (empty($paramInterval) || !
Main\Loader::includeModule(
'calendar'))
187 $paramInterval = trim($paramInterval);
189 if (mb_substr($paramInterval, 0, 1) ===
"-")
191 $paramInterval = mb_substr($paramInterval, 1);
195 $workDayInterval = $this->getWorkDayInterval();
197 "d" => $workDayInterval,
198 "day" => $workDayInterval,
199 "days" => $workDayInterval,
210 while (preg_match(
'/\s*([\d]+)\s*([a-z]+)\s*/i', $paramInterval, $match))
212 $match2 = mb_strtolower($match[2]);
213 if (array_key_exists($match2, $intervalMap))
215 $interval += intval($match[1]) * $intervalMap[$match2];
218 $p = mb_strpos($paramInterval, $match[0]);
219 $paramInterval = mb_substr($paramInterval,
$p + mb_strlen($match[0]));
222 if (date(
'H:i:s', $date) ===
'00:00:00')
225 $date += $this->getCalendarWorkTime()[0];
228 $date = $this->getNearestWorkTime($date, $multiplier);
231 $days = (int)floor($interval / $workDayInterval);
232 $hours = $interval % $workDayInterval;
234 $remainTimestamp = $this->getWorkDayRemainTimestamp($date, $multiplier);
238 $date = $this->addWorkDay($date, $days * $multiplier);
241 if (
$hours > $remainTimestamp)
243 $date += $multiplier < 0 ? -$remainTimestamp - 60 : $remainTimestamp + 60;
244 $date = $this->getNearestWorkTime($date, $multiplier) + ((
$hours - $remainTimestamp) * $multiplier);
248 $date += $multiplier *
$hours;
260 $offset = $this->getDateTimeOffset($date);
263 if (($date = $this->makeTimestamp($date)) ===
false)
268 if ($days === 0 || !
Main\Loader::includeModule(
'calendar'))
273 $date = $this->addWorkDay($date, $days);
280 if (!
Main\Loader::includeModule(
'calendar'))
290 $userArgs = clone $args;
296 if (($date = $this->makeTimestamp($date,
true)) ===
false)
301 return !$this->isHoliday($date);
306 if (!
Main\Loader::includeModule(
'calendar'))
316 $userArgs = clone $args;
322 if (($date = $this->makeTimestamp($date,
true)) ===
false)
327 return !$this->isHoliday($date) && $this->isWorkTime($date);
336 if (!$date1 || !$date2 || !is_scalar($format))
341 $date1Formatted = $this->getDateTimeObject($date1);
342 $date2Formatted = $this->getDateTimeObject($date2);
344 if ($date1Formatted ===
false || $date2Formatted ===
false)
349 $interval = $date1Formatted->diff($date2Formatted);
351 return $interval ===
false ? null : $interval->format($format);
359 if (!$format || !is_string($format))
364 $ts = $date ? $this->makeTimestamp($date,
true) : time();
371 return date($format, $ts);
388 elseif (($date = $this->makeTimestamp($date)) ===
false)
415 return \CTimeZone::GetOffset(
$userId,
true);
423 $baseTimestamp = $baseDate ? $this->makeTimestamp($baseDate,
true) : time();
425 if (!$baseTimestamp || !is_scalar($datetime))
430 $timestamp = strtotime($datetime, (
int)$baseTimestamp);
432 if ($timestamp ===
false)
445 if (!$format || !is_string($format))
450 $reformFormat = $this->frameSymbolsInDateFormat($format);
451 $timestamp = $date ? $this->makeTimestamp($date,
true) : time();
458 $formattedDate = date($reformFormat, $timestamp);
460 if ($formattedDate ===
false)
465 return $this->replaceDateToLocDate($formattedDate, $reformFormat);
471 $currentOffset = $this->getDateTimeOffset($date);
476 $timeOffset = $hour * self::ONE_HOUR + $minute * 60;
478 -1 * self::TWELVE_HOURS,
482 if ($baseOffset !== 0)
484 $timeOffset -= $baseOffset;
487 if (($date = $this->makeTimestamp($date,
true)) ===
false)
493 $dateTime->setTime(0, 0, 0, 0);
495 $date = $dateTime->getTimestamp() + $timeOffset;
500 private function makeTimestamp($date, $appendOffset =
false)
502 if (!$date || (!is_scalar($date) && !is_object($date)))
508 if (is_string($date) &&
Bizproc\
BaseType\Value\Date::isSerialized($date))
515 return $date->getTimestamp() + ($appendOffset ? $date->getOffset() : 0);
518 if (intval($date) .
"!" === $date .
"!")
536 private function getWorkDayTimestamp($date)
538 return date(
'H', $date) * 3600 + date(
'i', $date) * 60;
541 private function getWorkDayRemainTimestamp($date, $multiplier = 1)
543 $dayTs = $this->getWorkDayTimestamp($date);
544 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
545 return $multiplier < 0 ? $dayTs - $startSeconds : $endSeconds - $dayTs;
548 private function getWorkDayInterval()
550 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
551 return $endSeconds - $startSeconds;
554 private function isHoliday($date)
556 [$yearWorkdays] = $this->getCalendarWorkdays();
557 [$weekHolidays, $yearHolidays] = $this->getCalendarHolidays();
559 $dayOfYear = date(
'j.n', $date);
560 if (in_array($dayOfYear, $yearWorkdays,
true))
565 $dayOfWeek = date(
'w', $date);
566 if (in_array($dayOfWeek, $weekHolidays))
571 $dayOfYear = date(
'j.n', $date);
572 if (in_array($dayOfYear, $yearHolidays,
true))
580 private function isWorkTime($date)
582 $dayTs = $this->getWorkDayTimestamp($date);
583 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
584 return ($dayTs >= $startSeconds && $dayTs <= $endSeconds);
587 private function getNearestWorkTime($date, $multiplier = 1)
589 $reverse = $multiplier < 0;
590 [$startSeconds, $endSeconds] = $this->getCalendarWorkTime();
591 $dayTimeStamp = $this->getWorkDayTimestamp($date);
593 if ($this->isHoliday($date))
595 $date -= $dayTimeStamp;
596 $date += $reverse ? -86400 + $endSeconds : $startSeconds;
597 $dayTimeStamp = $reverse ? $endSeconds : $startSeconds;
600 if (!$this->isWorkTime($date))
602 $date -= $dayTimeStamp;
604 if ($dayTimeStamp < $startSeconds)
606 $date += $reverse ? -86400 + $endSeconds : $startSeconds;
610 $date += $reverse ? $endSeconds : 86400 + $startSeconds;
614 if ($this->isHoliday($date))
616 $date = $this->addWorkDay($date, $reverse ? -1 : 1);
622 private function addWorkDay($date, $days)
633 while ($days > 0 && $iterations < 1000)
638 if ($this->isHoliday($date))
648 private function getCalendarHolidays()
650 if (static::$yearHolidays ===
null)
652 $calendarSettings = \CCalendar::GetSettings();
653 $weekHolidays = [0, 6];
656 if (isset($calendarSettings[
'week_holidays']))
658 $weekDays = [
'SU' => 0,
'MO' => 1,
'TU' => 2,
'WE' => 3,
'TH' => 4,
'FR' => 5,
'SA' => 6];
660 foreach ($calendarSettings[
'week_holidays'] as $day)
662 $weekHolidays[] = $weekDays[$day];
666 if (isset($calendarSettings[
'year_holidays']))
668 foreach (explode(
',', $calendarSettings[
'year_holidays']) as $yearHoliday)
670 $date = explode(
'.', trim($yearHoliday));
671 if (
count($date) == 2 && $date[0] && $date[1])
673 $yearHolidays[] = (int)$date[0] .
'.' . (
int)$date[1];
677 static::$weekHolidays = $weekHolidays;
678 static::$yearHolidays = $yearHolidays;
681 return [static::$weekHolidays, static::$yearHolidays];
684 private function getCalendarWorkTime()
686 if (static::$startWorkDay ===
null)
689 $endSeconds = 24 * 3600 - 1;
691 $calendarSettings = \CCalendar::GetSettings();
692 if (!empty($calendarSettings[
'work_time_start']))
694 $time = explode(
'.', $calendarSettings[
'work_time_start']);
695 $startSeconds =
$time[0] * 3600;
696 if (!empty(
$time[1]))
698 $startSeconds +=
$time[1] * 60;
702 if (!empty($calendarSettings[
'work_time_end']))
704 $time = explode(
'.', $calendarSettings[
'work_time_end']);
705 $endSeconds =
$time[0] * 3600;
706 if (!empty(
$time[1]))
708 $endSeconds +=
$time[1] * 60;
711 static::$startWorkDay = $startSeconds;
712 static::$endWorkDay = $endSeconds;
714 return [static::$startWorkDay, static::$endWorkDay];
717 private function getCalendarWorkdays()
719 if (static::$yearWorkdays ===
null)
723 $calendarSettings = \CCalendar::GetSettings();
724 $calendarYearWorkdays = $calendarSettings[
'year_workdays'] ??
'';
726 foreach (explode(
',', $calendarYearWorkdays) as $yearWorkday)
728 $date = explode(
'.', trim($yearWorkday));
729 if (
count($date) === 2 && $date[0] && $date[1])
731 $yearWorkdays[] = (int)$date[0] .
'.' . (
int)$date[1];
735 static::$yearWorkdays = $yearWorkdays;
738 return [static::$yearWorkdays];
741 private function getDateTimeObject($date)
743 if ($date instanceof Bizproc\BaseType\Value\Date)
745 return (
new \DateTime())->setTimestamp($date->getTimestamp());
749 $date = \CBPHelper::flatten($date);
752 if (!is_scalar($date))
757 $df = Main\Type\DateTime::getFormat();
758 $df2 = Main\Type\Date::getFormat();
759 $date1Formatted = \DateTime::createFromFormat($df, $date);
760 if ($date1Formatted ===
false)
762 $date1Formatted = \DateTime::createFromFormat($df2, $date);
765 $date1Formatted->setTime(0, 0);
768 return $date1Formatted;
771 private function getDateTimeOffset($date)
773 if ($date instanceof Bizproc\BaseType\Value\Date)
775 return $date->getOffset();
781 private function frameSymbolsInDateFormat($format)
783 $complexSymbols = [
'j F',
'd F',
'jS F'];
784 $symbols = [
'D',
'l',
'F',
'M',
'r'];
787 foreach ($symbols as $symbol)
789 $frameRule[$symbol] =
'#' . $symbol .
'#';
790 $frameRule[
'\\' . $symbol] =
'\\' . $symbol;
792 foreach ($complexSymbols as $symbol)
794 $frameRule[$symbol] = substr($symbol, 0, -1) .
'#' . $symbol[-1] .
'_1#';
795 $frameRule[
'\\' . $symbol] =
'\\' . substr($symbol, 0, -1) .
'#' . $symbol[-1] .
'#';
798 return strtr($format, $frameRule);
801 private function frameNamesInFormattedDateRFC2822($formattedDate)
804 $pattern =
"/#(\w{3}), \d{2} (\w{3}) \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}#/";
812 $reformMatch = str_replace(
814 [
'#' . $day .
'#',
'#' . $month .
'#'],
817 $reformMatch = substr($reformMatch, 1, -1);
819 $formattedDate = str_replace($match, $reformMatch, $formattedDate);
823 return $formattedDate;
826 private function replaceDateToLocDate($formattedDate, $format)
829 $dayNames = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'];
845 if (strpos($format,
'#r#') !==
false)
847 $formattedDate = $this->frameNamesInFormattedDateRFC2822($formattedDate);
850 $replacementRule = [];
851 foreach (array_merge($dayNames, $monthNames) as
$name)
853 $replacementRule[
'#' .
$name .
'#'] = Loc::getMessage(
854 'BIZPROC_CALC_FUNCTION_LOCDATE_' . strtoupper(
$name)
856 $shortName = substr(
$name, 0, $lenShortName);
857 $replacementRule[
'#' . $shortName .
'#'] = Loc::getMessage(
858 'BIZPROC_CALC_FUNCTION_LOCDATE_' . strtoupper($shortName) .
'_SHORT'
861 foreach ($monthNames as $monthName)
863 $replacementRule[
'#' . $monthName .
'_1' .
'#'] = Loc::getMessage(
864 'BIZPROC_CALC_FUNCTION_LOCDATE_' . strtoupper($monthName) .
'_1'
868 return strtr($formattedDate, $replacementRule);
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)