Your IP : 18.117.83.104
<?
/**
* Bitrix Framework
* @package bitrix
* @subpackage tasks
* @copyright 2001-2016 Bitrix
*
* @access private
*
* This class is almost exact port of javascript GanttCalendar class
*/
namespace Bitrix\Tasks\Util;
use Bitrix\Tasks\Util\Type\DateTime;
final class Calendar
{
protected $holidays = array();
protected $weekEnds = array();
protected $weekStart = false;
protected $workTime = array();
protected static $instance = array();
/**
* @return static
*/
public static function getInstance()
{
$class = get_called_class();
if(!array_key_exists($class, static::$instance))
{
static::$instance[$class] = new static();
}
return static::$instance[$class];
}
public function __construct(array $settings = array())
{
if(!empty($settings))
{
$this->setSettings($settings);
}
else
{
$this->setSettings(static::getSettingsCached());
}
}
// calendar functions below
public function getClosestWorkTime(DateTime $oldDate, $isForward = true)
{
$date = clone $oldDate;
$startDate = $isForward ? $date : null;
$endDate = $isForward ? null : $date;
$cb = function (DateTime $start, DateTime $end) use(&$date, $isForward)
{
$date = $isForward ? $start : $end;
return false;
};
$this->processEachDay($startDate, $endDate, $isForward, $cb);
return $date;
}
public function calculateDuration(DateTime $startDate, DateTime $endDate)
{
$duration = 0;
if ($startDate->getTimestamp() < $endDate->getTimestamp())
{
$cb = function (DateTime $start, DateTime $end) use(&$duration)
{
$duration += ($end->getTimestamp() - $start->getTimestamp());
};
$this->processEachDay($startDate, $endDate, true, $cb);
}
else
{
$cb = function (DateTime $start, DateTime $end) use(&$duration)
{
$duration -= ($end->getTimestamp() - $start->getTimestamp());
};
$this->processEachDay($endDate, $startDate, true, $cb);
}
return $duration;
}
public function calculateStartDate($endDate, $duration)
{
$newDate = null;
$cb = function (DateTime $start, DateTime $end) use(&$newDate, &$duration)
{
$interval = $end->getTimestamp() - $start->getTimestamp();
if ($interval >= $duration)
{
$newDate = DateTime::createFromTimestampGmt($end->getTimestamp() - $duration);
return false;
}
else
{
$duration -= $interval;
}
};
$this->processEachDay(null, $endDate, false, $cb);
return $newDate;
}
/**
* @param DateTime $startDate
* @param $duration
* @return DateTime
*/
public function calculateEndDate($startDate, $duration)
{
$newDate = null;
$cb = function (DateTime $start, DateTime $end) use(&$newDate, &$duration)
{
$interval = $end->getTimestamp() - $start->getTimestamp();
if ($interval >= $duration)
{
$newDate = DateTime::createFromTimestampGmt($start->getTimestamp() + $duration);
return false;
}
else
{
$duration -= $interval;
}
};
$this->processEachDay($startDate, null, true, $cb);
return $newDate;
}
protected function processEachDay(DateTime $startDate = null, DateTime $endDate = null, $isForward = false, $callback = false)
{
if(!is_callable($callback))
{
return 0;
}
// you can not use DateTime::createFromTimestamp() here, because in this case, you`ll loose information about the timezone (was UTC, will be local zone)
$currentDate = DateTime::createFromTimestampGmt($isForward ? $startDate->getTimestamp() : $endDate->getTimestamp());
$endless = $isForward ? !$endDate : !$startDate;
/*
$intervals = $this->getWorkHours($currentDate);
print_r('iv:'.PHP_EOL);
print_r($intervals);
*/
/*
$n = DateTime::createFromTimeStructGmt(array(
'hours' => 10,
'minutes' => 30,
'seconds' => 0,
'mon' => 9,
'day' => 20,
'year' => 2015
), true);
print_r('currentDate: '.$n->getInfoGmt().PHP_EOL);
*/
while ($endless || ($isForward ? ($currentDate->getTimestamp() < $endDate->getTimestamp()) : ($currentDate->getTimestamp() > $startDate->getTimestamp())))
{
//print_r('currentDate: '.$currentDate->getInfoGmt().PHP_EOL);
$intervals = $this->getWorkHours($currentDate);
// walk on $intervals from start to end or visa-versa
for ($i = ($isForward ? 0 : count($intervals) - 1); ($isForward ? $i < count($intervals) : $i >= 0); ($isForward ? $i++ : $i--))
{
$interval = $intervals[$i];
$intervalStart = $interval['startDate'];
$intervalEnd = $interval['endDate'];
//print_r('Interval:'.PHP_EOL);
//print_r($intervalStart->getInfoGmt().' - '.$intervalEnd->getInfoGmt().PHP_EOL);
if (($endDate !== null && $intervalStart->checkGT($endDate)) || ($startDate !== null && $intervalEnd->checkLT($startDate)))
{
continue;
}
$availableStart = ($startDate !== null && $intervalStart->checkLT($startDate)) ? $startDate : $intervalStart;
$availableEnd = ($endDate !== null && $intervalEnd->checkGT($endDate)) ? $endDate : $intervalEnd;
//print_r('once: '.$availableStart->getInfoGmt().' '.$availableEnd->getInfoGmt().PHP_EOL);
if(call_user_func_array($callback, array($availableStart, $availableEnd)) === false)
{
return false;
}
}
$currentDate->stripTime();
$currentDate->addDay($isForward ? 1 : -1);
}
}
public function getWorkHours(DateTime $date)
{
$hours = array();
if ($this->isWeekend($date) || $this->isHoliday($date))
{
return $hours; // no work hours for $date
}
$year = $date->getYearGmt();
$month = $date->getMonthGmt(true);
$day = $date->getDayGmt();
for ($i = 0; $i < count($this->workTime); $i++)
{
$time = $this->workTime[$i];
array_push($hours, array(
'startDate' => DateTime::createFromTimeStructGmt(array(
'hours' => $time['start']['hours'],
'minutes' => $time['start']['minutes'],
'seconds' => 0,
'mon' => $month,
'day' => $day,
'year' => $year
), true),
'endDate' => DateTime::createFromTimeStructGmt(array(
'hours' => $time['end']['hours'],
'minutes' => $time['end']['minutes'],
'seconds' => 0,
'mon' => $month,
'day' => $day,
'year' => $year
), true)
));
}
return $hours;
}
public function isWorkTime(DateTime $date)
{
if ($this->isWeekend($date) || $this->isHoliday($date))
{
return false;
}
$isWorkTime = null;
$cb = function (DateTime $start, DateTime $end) use(&$isWorkTime, $date)
{
$isWorkTime = $date->checkGT($start, false) && $date->checkLT($end, false);
return false;
};
$this->processEachDay($date, null, true, $cb);
return $isWorkTime;
}
public function isHoliday(DateTime $date)
{
$month = $date->getMonthGmt(true);
$day = $date->getDayGmt();
return $this->holidays[$month.'_'.$day] ? true : false;
}
public function isWeekend(DateTime $date)
{
$day = $date->getWeekDayGmt();
return $this->weekEnds[$day] ? true : false;
}
/**
* @return DateTime
*/
public function getStartOfCurrentDayGmt()
{
$dateTime = DateTime::createFromUserTimeGmt((string) new DateTime());
$dateTime->stripTime(); // this will only work on GMT zone
return $dateTime;
}
/**
* @return DateTime
*/
public function getEndOfCurrentDayGmt()
{
$dateTime = $this->getStartOfCurrentDayGmt();
$dateTime->addDay(1);
return $dateTime;
}
// util
public function setSettings(array $settings)
{
if(is_array($settings['HOURS']) && !empty($settings['HOURS']))
{
$h = $settings['HOURS'];
$this->workTime = array(
// currently one interval, no time for lunch
array(
'start' => array(
'hours' => (int) $h['START']['H'],
'minutes' => (int) $h['START']['M'],
'time' => ((int) $h['START']['H']) * 60 + ((int) $h['START']['M'])
),
'end' => array(
'hours' => (int) $h['END']['H'],
'minutes' => (int) $h['END']['M'],
'time' => ((int) $h['END']['H']) * 60 + ((int) $h['START']['M'])
)
)
);
}
// holidays
if(is_array($settings['HOLIDAYS']))
{
foreach($settings['HOLIDAYS'] as $day)
{
$this->holidays[(intval($day['M']) - 1).'_'.intval($day['D'])] = true;
}
}
// week settings
$dayMap = array(
'MO' => 1,
'TU' => 2,
'WE' => 3,
'TH' => 4,
'FR' => 5,
'SA' => 6,
'SU' => 0,
);
$this->weekEnds = array();
if(is_array($settings['WEEKEND']))
{
foreach($settings['WEEKEND'] as $day)
{
$this->weekEnds[$dayMap[$day]] = true;
}
}
if(count($this->weekEnds) == 7) // wtf? the entire week is a one big weekend? fall back to "safe defaults"
{
$this->weekEnds = array($dayMap['SA'] => true, $dayMap['SU'] => true);
}
$this->weekStart = $dayMap[$settings['WEEK_START']];
/*
print_r('WeekEnds');
print_r($this->weekEnds);
print_r('weekStart');
print_r($this->weekStart);
print_r('holidays');
print_r($this->holidays);
print_r('worktime');
print_r($this->workTime);
*/
}
protected static function getSettingsCached()
{
static $settings;
if($settings == null)
{
$settings = static::getSettings();
}
return $settings;
}
public static function getDefaultSettings()
{
return array(
'HOURS' => array(
'START' => array('H' => 9, 'M' => 0, 'S' => 0),
'END' => array('H' => 19, 'M' => 0, 'S' => 0),
),
'HOLIDAYS' => array(),
'WEEKEND' => array('SA', 'SU'),
'WEEK_START' => 'MO'
);
}
public static function getSettings($siteId = false)
{
$result = static::getDefaultSettings();
if($siteId === false)
{
$siteId = SITE_ID;
}
$site = \CSite::GetByID($siteId)->fetch();
$weekDay = $site['WEEK_START'];
$weekDaysMap = array(
'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'
);
if((string) $weekDay != '' && isset($weekDaysMap[$weekDay]))
{
$result['WEEK_START'] = $weekDaysMap[$weekDay];
}
$calendarSettings = \Bitrix\Tasks\Integration\Calendar::getSettings();
if(!empty($calendarSettings))
{
if(is_array($calendarSettings['week_holidays']))
{
$result['WEEKEND'] = $calendarSettings['week_holidays'];
}
/*
if((string) $calendarSettings['week_start'] != '')
{
$result['WEEK_START'] = $calendarSettings['week_start'];
}
*/
if((string) $calendarSettings['year_holidays'] != '')
{
$holidays = explode(',', $calendarSettings['year_holidays']);
if(is_array($holidays) && !empty($holidays))
{
foreach($holidays as $day)
{
$day = trim($day);
list($day, $month) = explode('.', $day);
$day = intval($day);
$month = intval($month);
if($day && $month)
{
$result['HOLIDAYS'][] = array('M' => $month, 'D' => $day);
}
}
}
}
[$startHours, $startMinutes] = explode('.', (string)$calendarSettings['work_time_start']);
if(isset($startHours))
{
$result['HOURS']['START']['H'] = (int)$startHours;
}
if(isset($startMinutes))
{
$result['HOURS']['START']['M'] = (int)$startMinutes;
}
[$endHours, $endMinutes] = explode('.', (string)$calendarSettings['work_time_end']);
if(isset($endHours))
{
$result['HOURS']['END']['H'] = (int)$endHours;
}
if(isset($endMinutes))
{
$result['HOURS']['END']['M'] = (int)$endMinutes;
}
}
return $result;
}
}