<?php
/**
* Класс Тревога
*/
class Alarm
{
const MONDAY = 'Monday';
const TUESDAY = 'Tuesday';
const WEDNESDAY = 'Wednesday';
const THURSDAY = 'Thursday';
const FRIDAY = 'Friday';
const SATURDAY = 'Saturday';
const SUNDAY = 'Sunday';
const EVERYDAY = 'Everyday';
const TYPE_REPEATING = 1;
const TYPE_NON_REPEATING = 2;
const STATUS_ACTIVE = true;
const STATUS_INACTIVE = false;
/** @var int */
private $id;
/** @var DateTime */
private $time;
/** @var array */
private $daysOfWeek = [self::EVERYDAY];
/** @var int */
private $type = self::TYPE_REPEATING;
/** @var bool */
private $status = self::STATUS_ACTIVE;
/**
* Конструктор тревоги.
*
* @param int $id
* @param DateTime $alarmTime
* @param DateTime $currentTime
*
* @throws InvalidArgumentException
*/
public function __construct(int $id, DateTime $alarmTime, DateTime $currentTime)
{
$this->id = $id;
$this->setTime($alarmTime, $currentTime);
}
/**
* Устанавливает время тревоги.
*
* @param DateTime $alarmTime
* @param DateTime $currentTime
*
* @throws InvalidArgumentException
*/
public function setTime(DateTime $alarmTime, DateTime $currentTime): void
{
if ($alarmTime->diff($currentTime)->format('%d') > 7) {
throw new InvalidArgumentException('Время тревоги больше текущего на неделю');
}
if ($alarmTime->format('Ymd') < $currentTime->format('Ymd')) {
throw new InvalidArgumentException('Нельзя устанавливать прошедшую дату');
}
$this->time = $alarmTime; }
/**
* Устанавливает дни недели по которым срабатывает тревога.
*
* @param array $daysOfWeek
*
* @throws InvalidArgumentException
*/
public function setDaysOfWeek
(array $daysOfWeek): void
{
$namesOfDaysOfWeek = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
foreach ($daysOfWeek as $dayOfWeek) {
if (count($daysOfWeek) > 1 && $dayOfWeek == Alarm
::EVERYDAY) { throw new InvalidArgumentException('К ежедневному параметру нельзя добавлять другие');
}
if (!in_array($dayOfWeek, $namesOfDaysOfWeek) && $dayOfWeek != Alarm
::EVERYDAY) { throw new InvalidArgumentException('Неправильное значение в массиве с днями недели');
}
}
$this->daysOfWeek = $daysOfWeek;
}
/**
* Устанавливает тип тревоги - одноразовый или многоразовый.
*
* @param int $type
*
* @throws InvalidArgumentException
*/
public function setType(int
$type): void
{
if ($type != self::TYPE_REPEATING && $type != self::TYPE_NON_REPEATING) {
throw new InvalidArgumentException('Неправильный тип, используйте константы');
}
if ($this->status == Alarm::STATUS_INACTIVE && $type == Alarm::TYPE_NON_REPEATING) {
throw new InvalidArgumentException('Нельзя делать одноразовой неактивную тревогу');
}
$this->type = $type;
}
/**
* Устанавливает статус тревоги.
*
* @param bool $status
*
* @throws InvalidArgumentException
*/
public function setStatus(bool $status): void
{
if ($status == self::STATUS_INACTIVE && $this->type == self::TYPE_NON_REPEATING) {
throw new InvalidArgumentException('Нельзя изменять статус одноразовой тревоги');
}
$this->status = $status;
}
/**
* Возвращает идентификатор тревоги.
*
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Возвращает время тревоги.
*
* @return DateTime
*/
public function getTime(): DateTime
{
return clone
$this->time; }
/**
* Возвращает дни недели по которым срабатывает тревога.
*
* @return array
*/
public function getDaysOfWeek
(): array {
return $this->daysOfWeek;
}
/**
* Возвращает тип тревоги.
*
* @return int
*/
{
return $this->type;
}
/**
* Возвращает статус тревоги.
*
* @return bool
*/
public function getStatus(): bool
{
return $this->status;
}
}
/**
* Класс Будильник
*/
class AlarmClock
{
/** @var int */
private $id;
/** @var array */
private $alarms;
/**
* Добавляет новую тревогу
*
* @param DateTime $alarmTime
* @param DateTime $currentTime
*
* @throws InvalidArgumentException
*/
public function addAlarm(DateTime $alarmTime, DateTime $currentTime): void
{
$this->id++;
$this->alarms[] = new Alarm($this->id, $alarmTime, $currentTime);
}
/**
* Удаляет тревогу
*
* @param int $id
*
* @throws Exception|InvalidArgumentException
*/
public function deleteAlarm(int $id): void
{
if (!$this->alarms) {
throw new Exception('Нет тревог');
}
$yesOrNot = null;
foreach ($this->alarms as $key => $alarm) {
if ($alarm->getId() == $id) {
unset($this->alarms[$key]); $yesOrNot = true;
break;
}
}
if (!$yesOrNot) {
throw new InvalidArgumentException('Неправильный номер тревоги');
}
}
/**
* Настраивает тревогу
*
* @param int $id
* @param DateTime|null $alarmTime
* @param array|null $daysOfWeek
* @param int|null $type
* @param bool|null $status
*
* @throws Exception|InvalidArgumentException
*/
public function setAlarm
(int
$id, ?DateTime
$alarmTime, ?
array $daysOfWeek, ?int
$type, ?bool
$status): void
{
if (!$this->alarms) {
throw new Exception('Нет тревог');
}
$yesOrNot = null;
foreach ($this->alarms as $alarm) {
if ($alarm->getId() == $id) {
if ($alarmTime) {
$alarm->setTime($alarmTime, new DateTime);
}
if ($daysOfWeek) {
$alarm->setDaysOfWeek($daysOfWeek);
}
if ($status !== null) {
$alarm->setStatus($status);
}
if ($type) {
}
$yesOrNot = true;
break;
}
}
if (!$yesOrNot) {
throw new InvalidArgumentException('Неправильный номер тревоги');
}
}
/**
* Находит ближайшее время тревоги
*
* @param Alarm $alarm
* @param DateTime $currentTime
*
* @return DateTime
*
* @throws InvalidArgumentException
*/
public function findNearestAlarmTime(Alarm $alarm, DateTime $currentTime): DateTime
{
$alarmTimeIsLess = $alarm->getTime()->format('Ymd H:i') < $currentTime->format('Ymd H:i');
$isEveryday = $alarm->getDaysOfWeek()[0] == Alarm::EVERYDAY;
if ($alarmTimeIsLess && $isEveryday) {
/*
* Если время тревоги меньше, чем текущее и
* тревога ежедневная - добавляем к объекту
* времени тревоги интервал, передвигающий её
* на следующий день
*/
$alarmTime = $alarm->getTime()->add(
DateInterval::createFromDateString('tomorrow')
);
// Обновляем время тревоги
$alarm->setTime($alarmTime, new DateTime);
return $alarmTime;
} elseif (!$isEveryday) {
/*
* Если у тревоги указаны дни недели в $daysOfWeek
* - получаем время тревоги для каждого дня недели.
* Полученные объекты кладем в массив $datesAndTimes
*/
foreach ($alarm->getDaysOfWeek() as $dayOfWeek) {
$datesAndTimes[] = $alarm->getTime()->add(
(
DateInterval::createFromDateString(($alarmTimeIsLess) ? "next $dayOfWeek" : "$dayOfWeek")
)
);
}
// Сортируем DatesTimes по возрастанию к текущей дате
// Ближайшем временем тревоги становится первостоящая в массиве
// Также, не забываем перезаписать время тревоги
$alarm->setTime($alarmTime, new DateTime);
return $alarmTime;
} else {
// В остальных случаях, просто возвращаем объект даты-времени тревоги
return $alarm->getTime();
}
}
/**
* Находит ближайшую к текущему времени тревогу
*
* @param DateTime $currentTime
*
* @return Alarm
*
* @throws Exception|InvalidArgumentException
*/
public function findNearestAlarm(DateTime $currentTime): Alarm
{
if (!$this->alarms) {
throw new Exception('Нет тревог');
}
$alarms = [];
foreach ($this->alarms as $alarm) {
$alarmTime = $this->findNearestAlarmTime($alarm, $currentTime);
$alarms[] = [$alarmTime, $alarm];
}
return $alarms[0][1];
}
/**
* Находит тревогу равную текущему времени
*
* @param $currentTime
*
* @return Alarm|null
*
* @throws Exception|InvalidArgumentException
*/
public function findTriggeredAlarm(DateTime $currentTime): ?Alarm
{
if (!$this->alarms) {
throw new Exception('Нет тревог');
}
foreach ($this->alarms as $alarm) {
// На всякий случай обновим время срабатывания тревоги
$alarmTime = $this->findNearestAlarmTime($alarm, new DateTime);
if ($alarmTime->format('Ymd H:i') == $currentTime->format('Ymd H:i')) {
return $alarm;
}
}
return null;
}
/**
* Удаляет активные, сработавшие, одноразовые тревоги для текущего времени
*
* @param DateTime $currentTime
*
* @throws Exception
*/
public function deleteProcessedNonRepeatingAlarms(DateTime $currentTime): void
{
if (!$this->alarms) {
throw new Exception('Нет тревог');
}
foreach ($this->alarms as $alarm) {
// Определяем равно ли время тревоги текущему
$alarmTimeIsEqualTo = $currentTime->format('N H:i') == $alarm->getTime()->format('N H:i');
/*
* Если время срабатывания тревоги равно текущему,
* а тип тревоги одноразовый - удаляем её
*/
if ($alarmTimeIsEqualTo && $alarm->getType() == Alarm
::TYPE_NON_REPEATING) { $this->deleteAlarm($alarm->getId());
}
}
}
}
/**
* Проигрывает мелодию
*
* @param AlarmClock $alarmClock
* @param DateTime $currentTime
*
* @throws Exception
*/
function playMelody(AlarmClock $alarmClock, DateTime $currentTime)
{
$nearestAlarm = $alarmClock->findNearestAlarm(new DateTime);
echo "Ближайшая тревога №{$nearestAlarm->getId()}. Время тревоги {$nearestAlarm->getTime()->format('Y-m-d H:i')}\n";
// Получаем разницу в секундах между текущим временем и временем тревоги
$waitingTime = $nearestAlarm->getTime()->getTimestamp() - $currentTime->getTimestamp();
// Ожидаем время тревоги
if ($waitingTime > 0) {
}
// Находим тревогу срабатывающую ровно в это время
$triggeredAlarm = $alarmClock->findTriggeredAlarm(new DateTime);
if ($triggeredAlarm && $triggeredAlarm->getStatus()) {
// Проигрываем мелодию {}
echo "Сработала тревога №{$triggeredAlarm->getId()}\n";
// Удаляем сработавшие одноразовые тревоги
$alarmClock->deleteProcessedNonRepeatingAlarms(new DateTime);
}
// Ожидаем минуту, из-за особенностей работы метода поиска ближайшей тревоги
}
$alarmClock = new AlarmClock;
$alarmClock->addAlarm(new DateTime('18:24'), new DateTime);
$alarmClock->addAlarm(new DateTime('18:25'), new DateTime);
$alarmClock->setAlarm(1, null, [Alarm::SUNDAY], Alarm::TYPE_NON_REPEATING, null);
$alarmClock->setAlarm(2, null, [Alarm::SUNDAY, Alarm::MONDAY], Alarm::TYPE_NON_REPEATING, null);
// Код будет выполнятся пока не закончатся тревоги
while ($alarmClock->findNearestAlarm(new DateTime)) {
playMelody($alarmClock, new DateTime);
}