Skip to content

Instantly share code, notes, and snippets.

@paunin
Last active December 7, 2016 03:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paunin/04ba2a52c273f5312cce7f5c031d28b5 to your computer and use it in GitHub Desktop.
Save paunin/04ba2a52c273f5312cce7f5c031d28b5 to your computer and use it in GitHub Desktop.
<?php
/**
* Class WeekTimestamp
*/
final class WeekTimestamp
{
const SECONDS_IN_MINUTE = 60;
const SECONDS_IN_HOUR = self::SECONDS_IN_MINUTE * 60;
const SECONDS_IN_DAY = self::SECONDS_IN_HOUR * 24;
const SECONDS_IN_WEEK = self::SECONDS_IN_DAY * 7;
/**
* @param DateTime $dateTime
*
* @return int amount of seconds since the beginning of the week
*/
public static function createFromDateTime(\DateTime $dateTime): int
{
return self::create(
intval($dateTime->format('w')),
intval($dateTime->format('H')),
intval($dateTime->format('i')),
intval($dateTime->format('s'))
);
}
/**
* @param int $weekDay Day of the week 0 - Sunday, 6 - Saturday
* @param int $hour 0-23
* @param int $minute 0-59
* @param int $second 0-59
*
* @return int amount of seconds since the beginning of the week
*/
public static function create(
int $weekDay,
int $hour = 0,
int $minute = 0,
int $second = 0
): int {
$timestamp =
$weekDay * self::SECONDS_IN_DAY +
$hour * self::SECONDS_IN_HOUR +
$minute * self::SECONDS_IN_MINUTE +
$second;
if($timestamp >= self::SECONDS_IN_WEEK){
throw new \RuntimeException('Can\'t create WeekTimestamp');
}
return $timestamp;
}
}
/**
* Class Interval
*/
class Interval
{
private $startWeekTimestamp;
private $endWeekTimestamp;
/**
* Interval constructor.
*
* @param int $startDay of the week 0 - Sunday, 6 - Saturday
* @param int $startHour 0-23
* @param int $startMinute 0-59
* @param int $endDay of the week 0 - Sunday, 6 - Saturday
* @param int $endHour 0-23
* @param int $endMinute 0-59
*/
public function __construct(
int $startDay,
int $startHour,
int $startMinute,
int $endDay,
int $endHour,
int $endMinute
) {
$startWeekTimestamp = WeekTimestamp::create($startDay, $startHour, $startMinute);
$endWeekTimestamp = WeekTimestamp::create($endDay, $endHour, $endMinute);
if ($startWeekTimestamp >= $endWeekTimestamp) {
throw new \RuntimeException('Start of interval should be before end of it');
}
$this->startWeekTimestamp = $startWeekTimestamp;
$this->endWeekTimestamp = $endWeekTimestamp;
}
/**
* @return int
*/
public function getStartWeekTimestamp(): int
{
return $this->startWeekTimestamp;
}
/**
* @return int
*/
public function getEndWeekTimestamp(): int
{
return $this->endWeekTimestamp;
}
/**
* @param int $weekTimestamp
*
* @return bool
*/
public function isWithin(int $weekTimestamp):bool
{
return (
$weekTimestamp >= $this->getStartWeekTimestamp() &&
$weekTimestamp < $this->getEndWeekTimestamp()
);
}
}
/**
* Class Scheduler
*/
class Scheduler
{
/**
* @var Interval[]
*/
private $intervals = [];
/**
* @var \DateTimeZone
*/
private $timezone;
/**
* Scheduler constructor.
*
* @param Interval[] $intervals
* @param DateTimeZone $timezone
*/
public function __construct(array $intervals = [], \DateTimeZone $timezone)
{
$this->timezone = $timezone;
if (!count($intervals)) {
throw new \RuntimeException('You should have intervals');
}
foreach ($intervals as $interval) {
$this->intervals[$interval->getStartWeekTimestamp()] = $interval;
}
// we want to make sure intervals are ordered to be able iterate it from the firs to last
ksort($this->intervals);
// we need to check overlaps
/** @var Interval $previousInterval */
$previousInterval = null;
foreach ($this->intervals as $interval) {
if (
$previousInterval &&
$previousInterval->getEndWeekTimestamp() > $interval->getStartWeekTimestamp()
) {
throw new \RuntimeException('Overlaps in intervals are not allowed');
}
$previousInterval = $interval;
}
}
/**
* @param DateTime $date
*
* @return int <=0 amount of seconds to the end of current interval,
* >0 - number of seconds to the next interval
*/
public function getDelay(\DateTime $date): int
{
$date->setTimezone($this->timezone);
$weekTimestamp = WeekTimestamp::createFromDateTime($date);
$i = 1;
foreach ($this->intervals as $interval) {
if ( // We are in an interval
$interval->isWithin($weekTimestamp)
) {
return ($interval->getEndWeekTimestamp() - $weekTimestamp) * (-1);
} elseif ( // we by passed needed interval
$weekTimestamp < $interval->getStartWeekTimestamp()
) {
return $interval->getStartWeekTimestamp() - $weekTimestamp;
} elseif ( // Last Interval in the collection, need to get time to the first interval
count($this->intervals) == $i
) {
$timeToFirstInterval = array_values($this->intervals)[0]->getStartWeekTimestamp();
return WeekTimestamp::SECONDS_IN_WEEK - $weekTimestamp + $timeToFirstInterval;
}
$i++;
}
}
}
//===================================Tests===================================//
$scheduler = new Scheduler(
[
new Interval(0, 10, 0, 0, 18, 0), // Sunday 10:00 - Sunday 18:00 - normal interval
new Interval(1, 10, 0, 5, 18, 0), // Monday 10:00 - Friday 18:00 - over midnight
],
new DateTimeZone('Asia/Ho_Chi_minh')
);
date_default_timezone_set('Asia/Ho_Chi_minh');
echo $scheduler->getDelay(new \DateTime('Sunday 09:00')).PHP_EOL; // 1 hour to time 1*60*60 = 3600
echo $scheduler->getDelay(new \DateTime('Monday 09:00')).PHP_EOL; // 1 hour to time 1*60*60 = 3600
echo $scheduler->getDelay(new \DateTime('Friday 17:59')).PHP_EOL; // 1 min to the end -60
echo $scheduler->getDelay(new \DateTime('Friday 17:59:59')).PHP_EOL; // 1 sec to the end -1
echo $scheduler->getDelay(new \DateTime('Friday 17:58')).PHP_EOL; // 2 mins to the end -120
echo $scheduler->getDelay(new \DateTime('Saturday 10:00')).PHP_EOL; // 1 day to next week interval 24*60*60 = 86400
echo $scheduler->getDelay(new \DateTime('now')).PHP_EOL; // ?
// Check Exceptions::Overlaps in intervals are not allowed
try {
$scheduler = new Scheduler(
[
new Interval(0, 10, 0, 0, 18, 0), // Sunday 10:00 - Sunday 18:00 - normal interval
new Interval(0, 17, 0, 5, 18, 0), // Sunday 17:00 - Friday 18:00 - over midnight
],
new DateTimeZone('Asia/Ho_Chi_minh')
);
} catch (\Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
// Check Exceptions::Overlaps in intervals are not allowed
try {
$scheduler = new Scheduler(
[
],
new DateTimeZone('Asia/Ho_Chi_minh')
);
} catch (\Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
// Check Exceptions::Start of interval should be before end of it
try {
$scheduler = new Scheduler(
[
new Interval(1, 10, 0, 0, 18, 0), // Sunday 10:00 - Sunday 18:00 - normal interval
],
new DateTimeZone('Asia/Ho_Chi_minh')
);
} catch (\Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
// Check Exceptions::Can't create WeekTimestamp
try {
WeekTimestamp::create(8,99,99);
} catch (\Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment