Skip to content

Commit

Permalink
Added support for DateTimeImmutable by checking for DateTimeInterface…
Browse files Browse the repository at this point in the history
… implementations for the *WorkingDay methods.

Signed-off-by: Sacha Telgenhof <[email protected]>
  • Loading branch information
stelgenhof committed Jan 24, 2018
1 parent 23ea30f commit 090010b
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 57 deletions.
17 changes: 12 additions & 5 deletions src/Yasumi/Provider/AbstractProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,26 @@ public function addHoliday(Holiday $holiday)
*
* @param mixed $date a Yasumi\Holiday or DateTime object
*
* @throws \Yasumi\Exception\InvalidDateException
*
* @return bool true if date represents a working day, otherwise false
*/
public function isWorkingDay($date)
{
// Check if date is a holiday
// Return false if given date is invalid
if (null === $date || ! $date instanceof \DateTimeInterface) {
throw new InvalidDateException($date);
}

// First check if the given date is a holiday
if ($this->isHoliday($date)) {
return false;
}

// If given date is a DateTime object; check if it falls in the weekend
// Check if given date is a falls in the weekend or not
// If no data is defined for this Holiday Provider, the function falls back to the global weekend definition.
// @TODO Ideally avoid late static binding here (static::ID)
if ($date instanceof DateTime) {
if ($date instanceof \DateTimeInterface) {
$weekend_data = self::WEEKEND_DATA;
$weekend_days = isset($weekend_data[$this::ID]) ? $weekend_data[$this::ID] : [0, 6];

Expand All @@ -196,12 +203,12 @@ public function isWorkingDay($date)
*/
public function isHoliday($date)
{
// Return false if given date is empty
// Return false if given date is invalid
if (null === $date || ! $date instanceof \DateTimeInterface) {
throw new InvalidDateException($date);
}

// If given date is a DateTime object
// Check if given date is a holiday or not
if (in_array($date->format('Y-m-d'), array_values($this->getHolidayDates()), true)) {
return true;
}
Expand Down
58 changes: 33 additions & 25 deletions src/Yasumi/Yasumi.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

namespace Yasumi;

use DateInterval;
use DateTime;
use FilesystemIterator;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
Expand Down Expand Up @@ -57,29 +55,34 @@ class Yasumi
];

/**
* @param string $class holiday provider name
* @param DateTime $startDate DateTime Start date, defaults to today
* @param int $workingDays int
* Determines the next working day based on a given start date.
*
* @TODO we should accept a timezone so we can accept int/string for $startDate
* The next working day based on a given start date excludes any holidays and weekends that may be defined
* by this Holiday Provider. The workingDays parameter can be used how far ahead (in days) the next working day
* must be searched for.
*
* @return DateTime
* @param string $class Holiday Provider name
* @param \DateTimeInterface $startDate Start date, defaults to today
* @param int $workingDays Number of days to look ahead for the (first) next working day
*
* @return \DateTimeInterface
*
* @throws \Yasumi\Exception\UnknownLocaleException
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @throws \Exception
* @throws \ReflectionException
* @throws \Exception
* @throws \Yasumi\Exception\InvalidDateException
*
* @TODO we should accept a timezone so we can accept int/string for $startDate
*
*/
public static function nextWorkingDay($class, DateTime $startDate, $workingDays = 1)
public static function nextWorkingDay($class, \DateTimeInterface $startDate, $workingDays = 1)
{
// Setup start date, if its an instance of \DateTime, clone to prevent modification to original
$date = $startDate instanceof DateTime ? clone $startDate : new DateTime($startDate);
$date = $startDate instanceof \DateTime ? clone $startDate : $startDate;

$provider = false;

while ($workingDays > 0) {
$date->add(new DateInterval('P1D'));
$date = $date->add(new \DateInterval('P1D'));
if (! $provider || $provider->getYear() != $date->format('Y')) {
$provider = self::create($class, $date->format('Y'));
}
Expand Down Expand Up @@ -239,29 +242,34 @@ public static function getProviders()
}

/**
* @param string $class holiday provider name
* @param DateTime $startDate DateTime Start date, defaults to today
* @param int $workingDays int
* Determines the previous working day based on a given start date.
*
* @TODO we should accept a timezone so we can accept int/string for $startDate
* The previous working day based on a given start date excludes any holidays and weekends that may be defined
* by this Holiday Provider. The workingDays parameter can be used how far back (in days) the previous working day
* must be searched for.
*
* @param string $class Holiday Provider name
* @param \DateTimeInterface $startDate Start date, defaults to today
* @param int $workingDays Number of days to look back for the (first) previous working day
*
* @return DateTime
* @return \DateTimeInterface
*
* @throws \Yasumi\Exception\UnknownLocaleException
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @throws \ReflectionException
* @throws \Exception
* @throws \Yasumi\Exception\InvalidDateException
*
* @TODO we should accept a timezone so we can accept int/string for $startDate
*
*/
public static function prevWorkingDay($class, DateTime $startDate, $workingDays = 1)
public static function prevWorkingDay($class, \DateTimeInterface $startDate, $workingDays = 1)
{
// Setup start date, if its an instance of \DateTime, clone to prevent modification to original
$date = $startDate instanceof DateTime ? clone $startDate : new DateTime($startDate);
$date = $startDate instanceof \DateTime ? clone $startDate : $startDate;

$provider = false;

while ($workingDays > 0) {
$date->sub(new DateInterval('P1D'));
$date = $date->sub(new \DateInterval('P1D'));
if (! $provider || $provider->getYear() != $date->format('Y')) {
$provider = self::create($class, $date->format('Y'));
}
Expand Down
51 changes: 43 additions & 8 deletions tests/Base/YasumiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

namespace Yasumi\tests\Base;

use DateTime;
use Faker\Factory;
use InvalidArgumentException;
use PHPUnit_Framework_TestCase;
Expand Down Expand Up @@ -386,34 +385,70 @@ public function testIsHolidayException()
}

/**
* Tests that the IsWorkingDay function returns a boolean true for a date that is defined as a holiday or falls in
* Tests that the isWorkingDay function returns a boolean true for a date that is defined as a holiday or falls in
* the weekend.
*
* @TODO Add additional unit tests for those holiday providers that differ from the global definition
*/
public function testIsWorkingDay()
{
$year = 2020;
$isWorkingDay = Yasumi::create('Netherlands', $year)->isWorkingDay(new DateTime($year . '-06-02'));
$year = 2020;
$provider = 'Netherlands';
$date = $year . '-06-02';

// Assertion using a DateTime instance
$isWorkingDay = Yasumi::create($provider, $year)->isWorkingDay(new \DateTime($date));
var_dump($isWorkingDay);
$this->assertInternalType('bool', $isWorkingDay);
$this->assertTrue($isWorkingDay);

// Assertion using a DateTimeImmutable instance
$isWorkingDay = Yasumi::create($provider, $year)->isWorkingDay(new \DateTimeImmutable($date));
var_dump($isWorkingDay);
$this->assertInternalType('bool', $isWorkingDay);
$this->assertTrue($isWorkingDay);

unset($isWorkingDay);
}

/**
* Tests that the IsWorkingDay function returns a boolean true for a date that is defined as a holiday or falls in
* Tests that the isWorkingDay function returns a boolean true for a date that is defined as a holiday or falls in
* the weekend.
*
* @TODO Add additional unit tests for those holiday providers that differ from the global definition
*/
public function testIsNotWorkingDay()
{
$year = 2016;
$isNotWorkingDay = Yasumi::create('Japan', $year)->isWorkingDay(new DateTime($year . '-01-11'));
$year = 2016;
$provider = 'Japan';
$date = $year . '-01-11';

// Assertion using a DateTime instance
$isNotWorkingDay = Yasumi::create($provider, $year)->isWorkingDay(new \DateTime($date));
$this->assertInternalType('bool', $isNotWorkingDay);
$this->assertFalse($isNotWorkingDay);

unset($isWorkingDay);
// Assertion using a DateTimeImmutable instance
$isNotWorkingDay = Yasumi::create($provider, $year)->isWorkingDay(new \DateTimeImmutable($date));
$this->assertInternalType('bool', $isNotWorkingDay);
$this->assertFalse($isNotWorkingDay);

unset($isNotWorkingDay);
}

/**
* Tests that the isWorkingDay function throws an InvalidDateException when the given argument is not an instance
* that implements the DateTimeInterface (e.g. DateTime or DateTimeImmutable)
*
* @TODO Add additional unit tests for those holiday providers that differ from the global definition
*/
public function testIsWorkingDayException()
{
$this->expectException(InvalidDateException::class);

Yasumi::create('SouthAfrica', Factory::create()->numberBetween(
self::YEAR_LOWER_BOUND,
self::YEAR_UPPER_BOUND
))->isWorkingDay(new \stdClass());
}
}
103 changes: 86 additions & 17 deletions tests/Base/YasumiWorkdayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,78 @@

class YasumiWorkdayTest extends PHPUnit_Framework_TestCase
{
public function testNextWorkday()
{
$startDate = new \DateTime('2016-07-01', new \DateTimeZone('America/New_York'));
$result = Yasumi::nextWorkingDay('USA', $startDate);
const FORMAT_DATE = 'Y-m-d';

/**
* Tests that the nextWorkingDay function returns an object that implements the DateTimeInterface (e.g. DateTime)
*
* @throws \Exception
* @throws \ReflectionException
*/
public function testNextWorkingDay()
{
// Includes a weekend and a holiday
$provider = 'USA';
$date = '2016-07-01';
$timezone = 'America/New_York';
$expectedDate = '2016-07-05';

// Assertion using a DateTime instance
$startDate = new \DateTime($date, new \DateTimeZone($timezone));
$result = Yasumi::nextWorkingDay($provider, $startDate);

$this->assertInstanceOf(\DateTime::class, $result);
$this->assertEquals('2016-07-05', $result->format('Y-m-d'));
$this->assertEquals($expectedDate, $result->format(self::FORMAT_DATE));

// Assertion using a DateTimeImmutable instance
$startDate = new \DateTimeImmutable($date, new \DateTimeZone($timezone));
$result = Yasumi::nextWorkingDay($provider, $startDate);

$this->assertInstanceOf(\DateTimeImmutable::class, $result);
$this->assertEquals($expectedDate, $result->format(self::FORMAT_DATE));
}

public function testPrevWorkday()
/**
* Tests that the prevWorkingDay function returns an object that implements the DateTimeInterface (e.g. DateTime)
*
* @throws \Exception
* @throws \ReflectionException
*/
public function testPreviousWorkingDay()
{
$startDate = new \DateTime('2016-07-05', new \DateTimeZone('America/New_York'));
$result = Yasumi::prevWorkingDay('USA', $startDate);

// Includes a weekend and a holiday
$provider = 'USA';
$date = '2016-07-05';
$timezone = 'America/New_York';
$expectedDate = '2016-07-01';

// Assertion using a DateTime instance
$startDate = new \DateTime($date, new \DateTimeZone($timezone));
$result = Yasumi::prevWorkingDay($provider, $startDate);

$this->assertInstanceOf(\DateTime::class, $result);
$this->assertEquals('2016-07-01', $result->format('Y-m-d'));
$this->assertEquals($expectedDate, $result->format(self::FORMAT_DATE));

// Assertion using a DateTimeImmutable instance
$startDate = new \DateTimeImmutable($date, new \DateTimeZone($timezone));
$result = Yasumi::prevWorkingDay($provider, $startDate);

$this->assertInstanceOf(\DateTimeImmutable::class, $result);
$this->assertEquals($expectedDate, $result->format(self::FORMAT_DATE));
}

/**
* Tests that the prevWorkingDay and nextWorkingDay functions returns an object that implements the
* DateTimeInterface (e.g. DateTime) when an interval is chosen that passes the year boundary (i.e. beyond 12/31)
*
* @throws \Exception
* @throws \ReflectionException
*/
public function testYearBoundary()
{
$startDate = new \DateTime('2015-12-20', new \DateTimeZone('America/New_York'));
$result = Yasumi::nextWorkingDay('USA', $startDate, 20);

/**
* Use Case (USA):
*
* 20 working days between 20th Dec and 20th Jan
* 2015-12-20 is a Sunday
* 21st - 24th (4 Workdays)
Expand All @@ -57,10 +103,33 @@ public function testYearBoundary()
*
* @see https://www.timeanddate.com/calendar/?year=2016&country=1
*/
$this->assertEquals('2016-01-20', $result->format('Y-m-d'));

$startDate = new \DateTime('2016-01-20', new \DateTimeZone('America/New_York'));
$result = Yasumi::prevWorkingDay('USA', $startDate, 20);
$this->assertEquals('2015-12-18', $result->format('Y-m-d'));
$provider = 'USA';
$timezone = 'America/New_York';
$interval = 20;
$start = '2015-12-20';
$expectedNext = '2016-01-20';
$expectedPrevious = '2015-12-18';

// Assertion using a DateTime instance
$startDate = new \DateTime($start, new \DateTimeZone($timezone));
$result = Yasumi::nextWorkingDay($provider, $startDate, $interval);

$this->assertEquals($expectedNext, $result->format(self::FORMAT_DATE));

$startDate = new \DateTime($expectedNext, new \DateTimeZone($timezone));
$result = Yasumi::prevWorkingDay($provider, $startDate, $interval);
$this->assertEquals($expectedPrevious, $result->format(self::FORMAT_DATE));


// Assertion using a DateTimeImmutable instance
$startDate = new \DateTimeImmutable($start, new \DateTimeZone($timezone));
$result = Yasumi::nextWorkingDay($provider, $startDate, $interval);

$this->assertEquals($expectedNext, $result->format(self::FORMAT_DATE));

$startDate = new \DateTimeImmutable($expectedNext, new \DateTimeZone($timezone));
$result = Yasumi::prevWorkingDay($provider, $startDate, $interval);
$this->assertEquals($expectedPrevious, $result->format(self::FORMAT_DATE));
}
}
2 changes: 0 additions & 2 deletions tests/YasumiBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ public function assertDefinedHolidays($expectedHolidays, $provider, $year, $type
*/
public function assertHoliday($provider, $shortName, $year, $expected)
{
$this->expectException(InvalidDateException::class);

$holidays = Yasumi::create($provider, $year);
$holiday = $holidays->getHoliday($shortName);

Expand Down

0 comments on commit 090010b

Please sign in to comment.