Skip to content

Commit

Permalink
Merge pull request #122 from heiglandreas/addMultipleObservanceShifts
Browse files Browse the repository at this point in the history
  • Loading branch information
heiglandreas committed Oct 21, 2022
2 parents ec0f682 + 31706f4 commit b41cf02
Show file tree
Hide file tree
Showing 18 changed files with 737 additions and 75 deletions.
8 changes: 7 additions & 1 deletion share/holidays.xsd
Expand Up @@ -90,10 +90,14 @@
<xs:attributeGroup name="forward">
<xs:attribute name="forwardto" type="daynames" use="optional"/>
<xs:attribute name="forwardwhen" type="daynamelist" use="optional" />
<xs:attribute name="alternateforwardto" type="daynames"/>
<xs:attribute name="alternateforwardwhen" type="daynamelist"/>
</xs:attributeGroup>
<xs:attributeGroup name="rewind">
<xs:attribute name="rewindto" type="daynames" use="optional"/>
<xs:attribute name="rewindwhen" type="daynamelist" use="optional" />
<xs:attribute name="alternaterewindto" type="daynames"/>
<xs:attribute name="alternaterewindwhen" type="daynamelist"/>
</xs:attributeGroup>
<xs:element name="resources">

Expand Down Expand Up @@ -208,7 +212,9 @@
<xs:extension base="xs:string">
<xs:attributeGroup ref="relativeAttributes"/>
<xs:attributeGroup ref="restriction"/>
<xs:attributeGroup ref="observanceRestrictions"/>
<xs:attributeGroup ref="rewind"/>
<xs:attributeGroup ref="forward"/>
<xs:attributeGroup ref="observanceRestrictions"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
Expand Down
19 changes: 11 additions & 8 deletions src/CalendarDay.php
Expand Up @@ -91,16 +91,21 @@ public function isSameDay(DateTimeInterface $dateTime): bool
return $cal->get(IntlCalendar::FIELD_DAY_OF_MONTH) === $this->calendar->get(IntlCalendar::FIELD_DAY_OF_MONTH);
}

public function getCalendar(): IntlCalendar
{
return clone $this->calendar;
}

public function hasYearSet(): bool
{
return null !== $this->year;
}

public function isFollowUpDay(DateTimeInterface $dateTime, string $followUpDay): bool
{
return $this->isModifiedDate($dateTime, $followUpDay, 'next');
}

public function isPreviousDay(DateTimeInterface $dateTime, string $previousDay): bool
{
return $this->isModifiedDate($dateTime, $previousDay, 'previous');
}

private function isModifiedDate(DateTimeInterface $dateTime, string $modifiedDay, string $direction): bool
{
$cal = clone $this->calendar;
Expand Down Expand Up @@ -135,8 +140,6 @@ private function getDayForGregorianYear(int $gregorianYear): IntlCalendar
$cal->set(IntlCalendar::FIELD_MONTH, $this->month - 1);
$cal->set(IntlCalendar::FIELD_DAY_OF_MONTH, $this->day);

$cal = self::setGregorianYearForDate($gregorianYear, $cal);

return $cal;
return self::setGregorianYearForDate($gregorianYear, $cal);
}
}
9 changes: 3 additions & 6 deletions src/Factory/DateFactory.php
Expand Up @@ -14,7 +14,6 @@
use Org_Heigl\Holidaychecker\CalendarDayFactory;
use Org_Heigl\Holidaychecker\HolidayIteratorItemInterface;
use Org_Heigl\Holidaychecker\IteratorItem\Date;
use function explode;

class DateFactory implements ItemFromDomElementCreator
{
Expand All @@ -34,14 +33,12 @@ public function itemFromDomElement(DOMElement $element): ?HolidayIteratorItemInt
$day->setYear((int) $element->getAttribute('year'));
}

return new Date(
$date = new Date(
$element->textContent,
$element->getAttribute('free') === "true",
$day,
$element->hasAttribute('forwardto') ? $element->getAttribute('forwardto') : '',
$element->hasAttribute('forwardwhen') ? explode(' ', $element->getAttribute('forwardwhen')) : [],
$element->hasAttribute('rewindto') ? $element->getAttribute('rewindto') : '',
$element->hasAttribute('rewindwhen') ? explode(' ', $element->getAttribute('rewindwhen')) : [],
);

return $date;
}
}
78 changes: 78 additions & 0 deletions src/Factory/SwapDecoratorFactory.php
@@ -0,0 +1,78 @@
<?php

/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/

declare(strict_types=1);

namespace Org_Heigl\Holidaychecker\Factory;

use DOMElement;
use Org_Heigl\Holidaychecker\CalendarDayFactory;
use Org_Heigl\Holidaychecker\GregorianWeekday;
use Org_Heigl\Holidaychecker\HolidayIteratorItemInterface;
use Org_Heigl\Holidaychecker\IteratorItem\SwapDecorator;
use Org_Heigl\Holidaychecker\SwapDirection;
use Org_Heigl\Holidaychecker\SwapRule;
use function array_map;
use function explode;

final class SwapDecoratorFactory implements DecorateFromDomElement
{
public function decorate(HolidayIteratorItemInterface $element, DOMElement $domElement): HolidayIteratorItemInterface
{
$rules = $this->getRulesFromDomElement($domElement);

if ($rules === []) {
return $element;
}

$day = CalendarDayFactory::createCalendarDay(
(int) $domElement->getAttribute('day'),
(int) $domElement->getAttribute('month'),
($domElement->hasAttribute('calendar') ? $domElement->getAttribute('calendar') : 'gregorian')
);

if ($domElement->hasAttribute('year')) {
$day->setYear((int) $domElement->getAttribute('year'));
}

return new SwapDecorator($element, $day, ...$rules);
}

private function createRuleFrom(string $to, string $when, SwapDirection $direction): SwapRule
{
return new SwapRule(
$direction,
GregorianWeekday::fromString($to),
...array_map(function ($item) {
return GregorianWeekday::fromString($item);
}, explode(' ', $when))
);
}

/**
* @return SwapRule[]
*/
private function getRulesFromDomElement(DOMElement $domElement): array
{
$attributes = [
'forward' => SwapDirection::forward(),
'alternateforward' => SwapDirection::forward(),
'rewind' => SwapDirection::rewind(),
'alternaterewind' => SwapDirection::rewind(),
];

$rules = [];
foreach ($attributes as $attribute => $direction) {
if ($domElement->hasAttribute($attribute . 'to') && $domElement->hasAttribute($attribute . 'when')) {
$rules[] = $this->createRuleFrom($domElement->getAttribute($attribute . 'to'), $domElement->getAttribute($attribute . 'when'), $direction);
}
}

return $rules;
}
}
155 changes: 155 additions & 0 deletions src/GregorianWeekday.php
@@ -0,0 +1,155 @@
<?php

/**
* Copyright Andreas Heigl <andreas@heigl.org>
*
* Licenses under the MIT-license. For details see the included file LICENSE.md
*/

declare(strict_types=1);

namespace Org_Heigl\Holidaychecker;

use DateTimeInterface;
use IntlCalendar;
use RuntimeException;
use UnexpectedValueException;
use function method_exists;
use function sprintf;
use function strtolower;

final class GregorianWeekday
{
/** @var string */
private $value;

/** @var array<string, GregorianWeekday> */
private static $instances = [];

private const MONDAY = 'monday';
private const TUESDAY = 'tuesday';
private const WEDNESDAY = 'wednesday';
private const THURSDAY = 'thursday';
private const FRIDAY = 'friday';
private const SATURDAY = 'saturday';
private const SUNDAY = 'sunday';

private function __construct(string $value)
{
$this->value = $value;
}

public function getValue(): string
{
return $this->value;
}

public static function monday(): self
{
if (! isset(self::$instances[self::MONDAY])) {
self::$instances[self::MONDAY] = new self(self::MONDAY);
}

return self::$instances[self::MONDAY];
}

public static function tuesday(): self
{
if (! isset(self::$instances[self::TUESDAY])) {
self::$instances[self::TUESDAY] = new self(self::TUESDAY);
}

return self::$instances[self::TUESDAY];
}

public static function wednesday(): self
{
if (! isset(self::$instances[self::WEDNESDAY])) {
self::$instances[self::WEDNESDAY] = new self(self::WEDNESDAY);
}

return self::$instances[self::WEDNESDAY];
}

public static function thursday(): self
{
if (! isset(self::$instances[self::THURSDAY])) {
self::$instances[self::THURSDAY] = new self(self::THURSDAY);
}

return self::$instances[self::THURSDAY];
}

public static function friday(): self
{
if (! isset(self::$instances[self::FRIDAY])) {
self::$instances[self::FRIDAY] = new self(self::FRIDAY);
}

return self::$instances[self::FRIDAY];
}

public static function saturday(): self
{
if (! isset(self::$instances[self::SATURDAY])) {
self::$instances[self::SATURDAY] = new self(self::SATURDAY);
}

return self::$instances[self::SATURDAY];
}

public static function sunday(): self
{
if (! isset(self::$instances[self::SUNDAY])) {
self::$instances[self::SUNDAY] = new self(self::SUNDAY);
}

return self::$instances[self::SUNDAY];
}

public static function fromString(string $weekday): self
{
if (! method_exists(self::class, strtolower($weekday))) {
throw new RuntimeException(sprintf(
'Weekday "%s" is not known',
$weekday
));
}

/** @var GregorianWeekday $gregorianWeekday */
$gregorianWeekday = [self::class, strtolower($weekday)]();

return $gregorianWeekday;
}

public static function fromDateTimeInterface(DateTimeInterface $date): self
{
return self::fromString($date->format('l'));
}

public static function fromIntlWeekday(int $weekday): self
{
$mapper = [
IntlCalendar::DOW_SUNDAY => 'sunday',
IntlCalendar::DOW_MONDAY => 'monday',
IntlCalendar::DOW_TUESDAY => 'tuesday',
IntlCalendar::DOW_WEDNESDAY => 'wednesday',
IntlCalendar::DOW_THURSDAY => 'thursday',
IntlCalendar::DOW_FRIDAY => 'friday',
IntlCalendar::DOW_SATURDAY => 'saturday',
];
if (! isset($mapper[$weekday])) {
throw new UnexpectedValueException(sprintf(
'IntlCalendar weekday %s could not be resolved',
$weekday
));
}

return self::fromString($mapper[$weekday]);
}

public function __toString(): string
{
return $this->getValue();
}
}
2 changes: 2 additions & 0 deletions src/HolidayIteratorFactory.php
Expand Up @@ -43,6 +43,7 @@
use Org_Heigl\Holidaychecker\Factory\ItemFromDomElementCreator;
use Org_Heigl\Holidaychecker\Factory\ObservanceDecoratorFactory;
use Org_Heigl\Holidaychecker\Factory\RelativeFactory;
use Org_Heigl\Holidaychecker\Factory\SwapDecoratorFactory;
use RuntimeException;
use Throwable;
use UnexpectedValueException;
Expand All @@ -69,6 +70,7 @@ public function __construct()

$this->decorators = [
new ObservanceDecoratorFactory(),
new SwapDecoratorFactory(),
];
}

Expand Down

0 comments on commit b41cf02

Please sign in to comment.