fork download
  1. <?php
  2.  
  3.  
  4. /**
  5.  * Класс Тревога
  6.  */
  7. class Alarm
  8. {
  9.  
  10. const MONDAY = 'Monday';
  11. const TUESDAY = 'Tuesday';
  12. const WEDNESDAY = 'Wednesday';
  13. const THURSDAY = 'Thursday';
  14. const FRIDAY = 'Friday';
  15. const SATURDAY = 'Saturday';
  16. const SUNDAY = 'Sunday';
  17. const EVERYDAY = 'Everyday';
  18.  
  19. const TYPE_REPEATING = 1;
  20. const TYPE_NON_REPEATING = 2;
  21.  
  22. const STATUS_ACTIVE = true;
  23. const STATUS_INACTIVE = false;
  24.  
  25.  
  26. /** @var int */
  27. private $id;
  28. /** @var DateTime */
  29. private $time;
  30. /** @var array */
  31. private $daysOfWeek = [self::EVERYDAY];
  32. /** @var int */
  33. private $type = self::TYPE_REPEATING;
  34. /** @var bool */
  35. private $status = self::STATUS_ACTIVE;
  36.  
  37. /**
  38.   * Конструктор тревоги.
  39.   *
  40.   * @param int $id
  41.   * @param DateTime $alarmTime
  42.   * @param DateTime $currentTime
  43.   *
  44.   * @throws InvalidArgumentException
  45.   */
  46. public function __construct(int $id, DateTime $alarmTime, DateTime $currentTime)
  47. {
  48. $this->id = $id;
  49. $this->setTime($alarmTime, $currentTime);
  50. }
  51.  
  52. /**
  53.   * Устанавливает время тревоги.
  54.   *
  55.   * @param DateTime $alarmTime
  56.   * @param DateTime $currentTime
  57.   *
  58.   * @throws InvalidArgumentException
  59.   */
  60. public function setTime(DateTime $alarmTime, DateTime $currentTime): void
  61. {
  62.  
  63. if ($alarmTime->diff($currentTime)->format('%d') > 7) {
  64. throw new InvalidArgumentException('Время тревоги больше текущего на неделю');
  65. }
  66. if ($alarmTime->format('Ymd') < $currentTime->format('Ymd')) {
  67. throw new InvalidArgumentException('Нельзя устанавливать прошедшую дату');
  68. }
  69.  
  70. $this->time = $alarmTime;
  71. }
  72.  
  73. /**
  74.   * Устанавливает дни недели по которым срабатывает тревога.
  75.   *
  76.   * @param array $daysOfWeek
  77.   *
  78.   * @throws InvalidArgumentException
  79.   */
  80. public function setDaysOfWeek(array $daysOfWeek): void
  81. {
  82.  
  83. $namesOfDaysOfWeek = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
  84.  
  85. foreach ($daysOfWeek as $dayOfWeek) {
  86.  
  87. if (count($daysOfWeek) > 1 && $dayOfWeek == Alarm::EVERYDAY) {
  88. throw new InvalidArgumentException('К ежедневному параметру нельзя добавлять другие');
  89. }
  90. if (!in_array($dayOfWeek, $namesOfDaysOfWeek) && $dayOfWeek != Alarm::EVERYDAY) {
  91. throw new InvalidArgumentException('Неправильное значение в массиве с днями недели');
  92. }
  93.  
  94. }
  95.  
  96. $this->daysOfWeek = $daysOfWeek;
  97. }
  98.  
  99. /**
  100.   * Устанавливает тип тревоги - одноразовый или многоразовый.
  101.   *
  102.   * @param int $type
  103.   *
  104.   * @throws InvalidArgumentException
  105.   */
  106. public function setType(int $type): void
  107. {
  108.  
  109. if ($type != self::TYPE_REPEATING && $type != self::TYPE_NON_REPEATING) {
  110. throw new InvalidArgumentException('Неправильный тип, используйте константы');
  111. }
  112. if ($this->status == Alarm::STATUS_INACTIVE && $type == Alarm::TYPE_NON_REPEATING) {
  113. throw new InvalidArgumentException('Нельзя делать одноразовой неактивную тревогу');
  114. }
  115.  
  116. $this->type = $type;
  117. }
  118.  
  119. /**
  120.   * Устанавливает статус тревоги.
  121.   *
  122.   * @param bool $status
  123.   *
  124.   * @throws InvalidArgumentException
  125.   */
  126. public function setStatus(bool $status): void
  127. {
  128.  
  129. if ($status == self::STATUS_INACTIVE && $this->type == self::TYPE_NON_REPEATING) {
  130. throw new InvalidArgumentException('Нельзя изменять статус одноразовой тревоги');
  131. }
  132.  
  133. $this->status = $status;
  134. }
  135.  
  136. /**
  137.   * Возвращает идентификатор тревоги.
  138.   *
  139.   * @return int
  140.   */
  141. public function getId(): int
  142. {
  143. return $this->id;
  144. }
  145.  
  146. /**
  147.   * Возвращает время тревоги.
  148.   *
  149.   * @return DateTime
  150.   */
  151. public function getTime(): DateTime
  152. {
  153. return clone $this->time;
  154. }
  155.  
  156. /**
  157.   * Возвращает дни недели по которым срабатывает тревога.
  158.   *
  159.   * @return array
  160.   */
  161. public function getDaysOfWeek(): array
  162. {
  163. return $this->daysOfWeek;
  164. }
  165.  
  166. /**
  167.   * Возвращает тип тревоги.
  168.   *
  169.   * @return int
  170.   */
  171. public function getType(): int
  172. {
  173. return $this->type;
  174. }
  175.  
  176. /**
  177.   * Возвращает статус тревоги.
  178.   *
  179.   * @return bool
  180.   */
  181. public function getStatus(): bool
  182. {
  183. return $this->status;
  184. }
  185. }
  186.  
  187. /**
  188.  * Класс Будильник
  189.  */
  190. class AlarmClock
  191. {
  192. /** @var int */
  193. private $id;
  194. /** @var array */
  195. private $alarms;
  196.  
  197. /**
  198.   * Добавляет новую тревогу
  199.   *
  200.   * @param DateTime $alarmTime
  201.   * @param DateTime $currentTime
  202.   *
  203.   * @throws InvalidArgumentException
  204.   */
  205. public function addAlarm(DateTime $alarmTime, DateTime $currentTime): void
  206. {
  207. $this->id++;
  208. $this->alarms[] = new Alarm($this->id, $alarmTime, $currentTime);
  209. }
  210.  
  211. /**
  212.   * Удаляет тревогу
  213.   *
  214.   * @param int $id
  215.   *
  216.   * @throws Exception|InvalidArgumentException
  217.   */
  218. public function deleteAlarm(int $id): void
  219. {
  220. if (!$this->alarms) {
  221. throw new Exception('Нет тревог');
  222. }
  223.  
  224. $yesOrNot = null;
  225.  
  226. foreach ($this->alarms as $key => $alarm) {
  227.  
  228. if ($alarm->getId() == $id) {
  229.  
  230. unset($this->alarms[$key]);
  231. $yesOrNot = true;
  232. break;
  233.  
  234. }
  235.  
  236. }
  237.  
  238. if (!$yesOrNot) {
  239. throw new InvalidArgumentException('Неправильный номер тревоги');
  240. }
  241.  
  242. }
  243.  
  244. /**
  245.   * Настраивает тревогу
  246.   *
  247.   * @param int $id
  248.   * @param DateTime|null $alarmTime
  249.   * @param array|null $daysOfWeek
  250.   * @param int|null $type
  251.   * @param bool|null $status
  252.   *
  253.   * @throws Exception|InvalidArgumentException
  254.   */
  255. public function setAlarm(int $id, ?DateTime $alarmTime, ?array $daysOfWeek, ?int $type, ?bool $status): void
  256. {
  257.  
  258. if (!$this->alarms) {
  259. throw new Exception('Нет тревог');
  260. }
  261.  
  262. $yesOrNot = null;
  263.  
  264. foreach ($this->alarms as $alarm) {
  265.  
  266. if ($alarm->getId() == $id) {
  267.  
  268. if ($alarmTime) {
  269. $alarm->setTime($alarmTime, new DateTime);
  270. }
  271. if ($daysOfWeek) {
  272. $alarm->setDaysOfWeek($daysOfWeek);
  273. }
  274. if ($status !== null) {
  275. $alarm->setStatus($status);
  276. }
  277. if ($type) {
  278. $alarm->setType($type);
  279. }
  280.  
  281. $yesOrNot = true;
  282. break;
  283. }
  284.  
  285. }
  286.  
  287. if (!$yesOrNot) {
  288. throw new InvalidArgumentException('Неправильный номер тревоги');
  289. }
  290. }
  291.  
  292. /**
  293.   * Находит ближайшее время тревоги
  294.   *
  295.   * @param Alarm $alarm
  296.   * @param DateTime $currentTime
  297.   *
  298.   * @return DateTime
  299.   *
  300.   * @throws InvalidArgumentException
  301.   */
  302. public function findNearestAlarmTime(Alarm $alarm, DateTime $currentTime): DateTime
  303. {
  304.  
  305. $alarmTimeIsLess = $alarm->getTime()->format('Ymd H:i') < $currentTime->format('Ymd H:i');
  306. $isEveryday = $alarm->getDaysOfWeek()[0] == Alarm::EVERYDAY;
  307.  
  308. if ($alarmTimeIsLess && $isEveryday) {
  309.  
  310. /*
  311.   * Если время тревоги меньше, чем текущее и
  312.   * тревога ежедневная - добавляем к объекту
  313.   * времени тревоги интервал, передвигающий её
  314.   * на следующий день
  315.   */
  316. $alarmTime = $alarm->getTime()->add(
  317. DateInterval::createFromDateString('tomorrow')
  318. );
  319. // Обновляем время тревоги
  320. $alarm->setTime($alarmTime, new DateTime);
  321.  
  322. return $alarmTime;
  323.  
  324. } elseif (!$isEveryday) {
  325.  
  326. /*
  327.   * Если у тревоги указаны дни недели в $daysOfWeek
  328.   * - получаем время тревоги для каждого дня недели.
  329.   * Полученные объекты кладем в массив $datesAndTimes
  330.   */
  331. foreach ($alarm->getDaysOfWeek() as $dayOfWeek) {
  332.  
  333. $datesAndTimes[] = $alarm->getTime()->add(
  334. (
  335. DateInterval::createFromDateString(($alarmTimeIsLess) ? "next $dayOfWeek" : "$dayOfWeek")
  336. )
  337. );
  338.  
  339. }
  340.  
  341. // Сортируем DatesTimes по возрастанию к текущей дате
  342. sort($datesAndTimes);
  343. // Ближайшем временем тревоги становится первостоящая в массиве
  344. $alarmTime = array_shift($datesAndTimes);
  345. // Также, не забываем перезаписать время тревоги
  346. $alarm->setTime($alarmTime, new DateTime);
  347.  
  348. return $alarmTime;
  349.  
  350. } else {
  351.  
  352. // В остальных случаях, просто возвращаем объект даты-времени тревоги
  353. return $alarm->getTime();
  354.  
  355. }
  356.  
  357. }
  358.  
  359. /**
  360.   * Находит ближайшую к текущему времени тревогу
  361.   *
  362.   * @param DateTime $currentTime
  363.   *
  364.   * @return Alarm
  365.   *
  366.   * @throws Exception|InvalidArgumentException
  367.   */
  368. public function findNearestAlarm(DateTime $currentTime): Alarm
  369. {
  370.  
  371. if (!$this->alarms) {
  372. throw new Exception('Нет тревог');
  373. }
  374.  
  375. $alarms = [];
  376.  
  377. foreach ($this->alarms as $alarm) {
  378.  
  379. $alarmTime = $this->findNearestAlarmTime($alarm, $currentTime);
  380. $alarms[] = [$alarmTime, $alarm];
  381.  
  382. }
  383.  
  384. sort($alarms);
  385.  
  386. return $alarms[0][1];
  387.  
  388. }
  389.  
  390. /**
  391.   * Находит тревогу равную текущему времени
  392.   *
  393.   * @param $currentTime
  394.   *
  395.   * @return Alarm|null
  396.   *
  397.   * @throws Exception|InvalidArgumentException
  398.   */
  399. public function findTriggeredAlarm(DateTime $currentTime): ?Alarm
  400. {
  401.  
  402. if (!$this->alarms) {
  403. throw new Exception('Нет тревог');
  404. }
  405.  
  406. foreach ($this->alarms as $alarm) {
  407.  
  408. // На всякий случай обновим время срабатывания тревоги
  409. $alarmTime = $this->findNearestAlarmTime($alarm, new DateTime);
  410.  
  411. if ($alarmTime->format('Ymd H:i') == $currentTime->format('Ymd H:i')) {
  412. return $alarm;
  413. }
  414.  
  415. }
  416.  
  417. return null;
  418.  
  419. }
  420.  
  421. /**
  422.   * Удаляет активные, сработавшие, одноразовые тревоги для текущего времени
  423.   *
  424.   * @param DateTime $currentTime
  425.   *
  426.   * @throws Exception
  427.   */
  428. public function deleteProcessedNonRepeatingAlarms(DateTime $currentTime): void
  429. {
  430.  
  431. if (!$this->alarms) {
  432. throw new Exception('Нет тревог');
  433. }
  434.  
  435. foreach ($this->alarms as $alarm) {
  436.  
  437. // Определяем равно ли время тревоги текущему
  438. $alarmTimeIsEqualTo = $currentTime->format('N H:i') == $alarm->getTime()->format('N H:i');
  439.  
  440. /*
  441.   * Если время срабатывания тревоги равно текущему,
  442.   * а тип тревоги одноразовый - удаляем её
  443.   */
  444. if ($alarmTimeIsEqualTo && $alarm->getType() == Alarm::TYPE_NON_REPEATING) {
  445. $this->deleteAlarm($alarm->getId());
  446. }
  447.  
  448. }
  449.  
  450. }
  451.  
  452. }
  453.  
  454. /**
  455.  * Проигрывает мелодию
  456.  *
  457.  * @param AlarmClock $alarmClock
  458.  * @param DateTime $currentTime
  459.  *
  460.  * @throws Exception
  461.  */
  462. function playMelody(AlarmClock $alarmClock, DateTime $currentTime)
  463. {
  464.  
  465. $nearestAlarm = $alarmClock->findNearestAlarm(new DateTime);
  466.  
  467. echo "Ближайшая тревога №{$nearestAlarm->getId()}. Время тревоги {$nearestAlarm->getTime()->format('Y-m-d H:i')}\n";
  468.  
  469. // Получаем разницу в секундах между текущим временем и временем тревоги
  470. $waitingTime = $nearestAlarm->getTime()->getTimestamp() - $currentTime->getTimestamp();
  471.  
  472. // Ожидаем время тревоги
  473. if ($waitingTime > 0) {
  474. sleep($waitingTime);
  475. }
  476.  
  477. // Находим тревогу срабатывающую ровно в это время
  478. $triggeredAlarm = $alarmClock->findTriggeredAlarm(new DateTime);
  479.  
  480. if ($triggeredAlarm && $triggeredAlarm->getStatus()) {
  481.  
  482. // Проигрываем мелодию {}
  483.  
  484. echo "Сработала тревога №{$triggeredAlarm->getId()}\n";
  485.  
  486. // Удаляем сработавшие одноразовые тревоги
  487. $alarmClock->deleteProcessedNonRepeatingAlarms(new DateTime);
  488. }
  489.  
  490. // Ожидаем минуту, из-за особенностей работы метода поиска ближайшей тревоги
  491. sleep(60);
  492. }
  493.  
  494. $alarmClock = new AlarmClock;
  495.  
  496. $alarmClock->addAlarm(new DateTime('18:24'), new DateTime);
  497. $alarmClock->addAlarm(new DateTime('18:25'), new DateTime);
  498. $alarmClock->setAlarm(1, null, [Alarm::SUNDAY], Alarm::TYPE_NON_REPEATING, null);
  499. $alarmClock->setAlarm(2, null, [Alarm::SUNDAY, Alarm::MONDAY], Alarm::TYPE_NON_REPEATING, null);
  500.  
  501. // Код будет выполнятся пока не закончатся тревоги
  502. while ($alarmClock->findNearestAlarm(new DateTime)) {
  503. playMelody($alarmClock, new DateTime);
  504. }
  505.  
  506.  
Time limit exceeded #stdin #stdout 5s 222656KB
stdin
Standard input is empty
stdout
Ближайшая тревога №1. Время тревоги 2018-11-25 18:24