Skip to content

Commit

Permalink
Init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jakub Miskech authored and Jakub Miskech committed Jun 30, 2020
1 parent cf64816 commit 67bbc74
Show file tree
Hide file tree
Showing 10 changed files with 1,158 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/vendor/
/build/
/.vscode/

composer.phar
composer.lock
22 changes: 22 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
language: php
php:
- 7.3

install:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --no-interaction

# Testing the app (see phpunit.xml) for configs, generating Code Coverage report
script:
- mkdir -p build/logs
- php vendor/bin/phpunit -c phpunit.xml.dist

# Submit coverage report
after_success:
- travis_retry php vendor/bin/php-coveralls

# Monitor only these branches
branches:
only:
- master
- dev
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
# working-time
# working-time

> Working time
**Build status**

master: [![Build Status](https://travis-ci.com/Yakubko/working-time.svg?branch=master)](https://travis-ci.com/Yakubko/working-time)
[![Coverage Status](https://coveralls.io/repos/github/Yakubko/working-time/badge.svg?branch=master)](https://coveralls.io/github/Yakubko/working-time?branch=master)

dev: [![Build Status](https://travis-ci.com/Yakubko/working-time.svg?branch=dev)](https://travis-ci.com/Yakubko/working-time)
[![Coverage Status](https://coveralls.io/repos/github/Yakubko/working-time/badge.svg?branch=dev)](https://coveralls.io/github/Yakubko/working-time?branch=dev)
21 changes: 21 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "yakub/working-time",
"type": "library",
"description": "",
"license": "Apache-2.0",
"autoload": {
"psr-4": {
"Yakub\\WorkingTime\\": "src"
}
},
"scripts": {
"test": "phpunit"
},
"require": {
"php": "^7.1"
},
"require-dev": {
"phpunit/phpunit": "^9",
"php-coveralls/php-coveralls": "^2.2"
}
}
34 changes: 34 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap = "vendor/autoload.php"
backupGlobals = "false"
backupStaticAttributes = "false"
colors = "true"
convertErrorsToExceptions = "true"
convertNoticesToExceptions = "true"
convertWarningsToExceptions = "true"
processIsolation = "false"
stopOnFailure = "false">

<testsuites>
<testsuite name="Test Suite for simple-templating">
<directory suffix=".php">tests/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>

<php>
<env name="APP_ENV" value="testing"/>
</php>

<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>
<log type="coverage-html" target="build/coverage" />
</logging>

</phpunit>
237 changes: 237 additions & 0 deletions src/Main.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
<?php
namespace Yakub\WorkingTime;

/**
* Main class
*
* @author yakub
*/
final class Main {

private $crons = [];

public static function create(array $crons = []): Main {
return new static($crons);
}

protected function __construct(array $crons = []) {
$this->setCrons($crons);
}

public function setCrons(array $crons = []): void {
$this->crons = [];
$autoOpenInterval = true;

foreach ($crons as $cron) {
$cronObject = new Model\Cron($cron);
$autoOpenInterval = ! (! $autoOpenInterval || $cronObject->isOpen());

$this->crons[] = $cronObject;
}

if ($autoOpenInterval) {
array_unshift($this->crons, new Model\Cron());
}
}

public function isExecutable(string $time = null): bool {
try {
$date = new \DateTime($time ?? 'now');
} catch (\Exception $e) {
throw new \InvalidArgumentException('Invalid time value: '.$time);
}

$ret = false;
$dateTime = $this->timeToSec($date->format('H:i:s'));

$this->walkThroughDays($date, $date, function ($currentYMD, $currentTimes) use (& $ret, $dateTime) {
foreach ($currentTimes as $time) {
if ($dateTime <= $time['to'] && $dateTime >= $time['from']) {
$ret = true;
return true;
}
}
});

return $ret;
}

public function getOpenDuration(string $from, string $to = null): int {
try {
$toDate = new \DateTime(is_null($to) ? 'now' : $to);
$fromDate = new \DateTime($from);
} catch (\Exception $e) {
throw new \InvalidArgumentException('Invalid from or to value: '.$from.' '.$to);
}

$toYMD = $toDate->format('Y-m-d');
$fromYMD = $fromDate->format('Y-m-d');
$ret = 0;

$this->walkThroughDays($fromDate, $toDate, function ($currentYMD, $currentTimes) use (& $ret, $fromYMD, $toYMD, $fromDate, $toDate) {
$fromTime = 0;
if ($currentYMD == $fromYMD) {
$fromTime = $this->timeToSec($fromDate->format('H:i:s'));
}
$toTime = 86399;
if ($currentYMD == $toYMD) {
$tmpToTime = $this->timeToSec($toDate->format('H:i:s'));
if ($tmpToTime > 0) { $toTime = $tmpToTime; }
}

foreach ($currentTimes as $time) {
if ($fromTime <= $time['to'] && $toTime >= $time['from']) {
$ret+= min($toTime, $time['to'] + 1) - max($fromTime, $time['from']);
}
}
});

return $ret;
}

public function getFutureOpenDateTime(int $forSeconds = 0, string $from = null): ?\DateTime {
try {
$fromDate = new \DateTime(is_null($from) ? 'now' : $from);
} catch (\Exception $e) {
throw new \InvalidArgumentException('Invalid from value: '.$from);
}

$ret = null;
$mySeconds = 0;
$fromYMD = $fromDate->format('Y-m-d');
$safeBreak = 0;

do {
$toDate = clone $fromDate;
$toDate->add(new \DateInterval('P7D'));
$this->walkThroughDays($fromDate, $toDate, function ($dateYMD, $times) use (& $ret, & $mySeconds, $forSeconds, $fromYMD, $fromDate) {

$fromTime = 0;
if ($dateYMD == $fromYMD) {
$fromTime = $this->timeToSec($fromDate->format('H:i:s'));
}

foreach ($times as $time) {
if ($fromTime) {
if ($time['to'] <= $fromTime) {
continue;
}
else if ($fromTime > $time['from'] && $fromTime < $time['to']) {
$time['from'] = $fromTime;
}
}

$timeDuration = $time['to'] - $time['from'];
if ($timeDuration + $mySeconds < $forSeconds) {
$mySeconds+= $timeDuration + 1;
} else {
$ret = new \DateTime($dateYMD.' +'.($time['from']+($forSeconds-$mySeconds)).' seconds');
return true;
}
}
});
$fromDate->add(new \DateInterval('P8D'));
} while (is_null($ret) && ++$safeBreak < 1000);

return $ret;
}

public function getScheduleForOpenedDays(int $closestOpenDays = 5, string $from = null): array {
try {
$fromDate = new \DateTime(is_null($from) ? 'now' : $from);
} catch (\Exception $e) {
throw new \InvalidArgumentException('Invalid from value: '.$from);
}

$ret = [];
$safeBreak = 0;

do {
$toDate = clone $fromDate;
$toDate->add(new \DateInterval('P7D'));
$this->walkThroughDays($fromDate, $toDate, function ($dateYMD, $times) use (& $ret, $closestOpenDays) {
if (count($times)) {
$ret[$dateYMD] = $times;
}

if (count($ret) == $closestOpenDays) {
return true;
}
});
$fromDate->add(new \DateInterval('P8D'));
} while (count($ret) < $closestOpenDays && ++$safeBreak < 1000);

return $ret;
}

private function walkThroughDays(\DateTime $from, \DateTime $to, callable $callback): void {
$toYMD = $to->format('Y-m-d');
$current = clone $from;
$currentYMD = $current->format('Y-m-d');
$currentIntervals = [];

while ($currentYMD <= $toYMD) {
$currentIntervals = [];
foreach ($this->crons as $cron) {
if ($cron->isExecutable($currentYMD, Model\Cron::EXECUTABLE_LEVEL_DAY)) {
foreach ($cron->getHisTimestamp() as $hmsInterval) {
$currentIntervals = $this->addNewInterval($currentIntervals, $hmsInterval, $cron->isOpen());
}
}
}

if (! is_null($callback($currentYMD, $currentIntervals))) {
break;
}

$current->add(new \DateInterval('P1D'));
$currentYMD = $current->format('Y-m-d');
}
}

private function addNewInterval(array $times, array $addTime, bool $open): array {
foreach ($times as $key => $time) {
if ($addTime['from'] <= ($time['to'] + 1) && ($addTime['to'] + 1) >= $time['from']) {
if ($open) {
$addTime['from'] = ($addTime['from'] < $time['from']) ? $addTime['from'] : $time['from'];
$addTime['to'] = ($addTime['to'] > $time['to']) ? $addTime['to'] : $time['to'];
unset($times[$key]);
} else {
// out
if ($addTime['from'] >= $time['to'] || $addTime['to'] <= $time['from']) {
continue;
}
// over
else if ($addTime['from'] <= $time['from'] && $addTime['to'] >= $time['to']) {
unset($times[$key]);
}
// in
else if ($addTime['from'] > $time['from'] && $addTime['to'] < $time['to']) {
$originTime = $time;
$originTime['from'] = $addTime['to'] + 1;

$times[$key]['to'] = $addTime['from'] - 1;
$times[] = $originTime;
break;
}
// cross
else {
$times[$key]['from'] = ($addTime['to'] < $time['to']) ? $addTime['to'] + 1 : $time['from'];
$times[$key]['to'] = ($addTime['from'] > $time['from']) ? $addTime['from'] - 1 : $time['to'];
}
}

return $this->addNewInterval($times, $addTime, $open);
}
}

if ($open) { $times[] = $addTime; }
return $times;
}

private function timeToSec(string $string): int {
$hours = $minutes = $seconds = 0;
sscanf($string, "%d:%d:%d", $hours, $minutes, $seconds);
return $hours * 3600 + $minutes * 60 + $seconds;
}
}
Loading

0 comments on commit 67bbc74

Please sign in to comment.