Skip to content

Commit

Permalink
Integrate mll-lab/holidays
Browse files Browse the repository at this point in the history
  • Loading branch information
spawnia authored Feb 1, 2024
1 parent cbaf32c commit 063e30d
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:

- run: composer install --no-interaction --no-progress --no-suggest

- run: vendor/bin/php-cs-fixer fix
- run: vendor/bin/php-cs-fixer fix --using-cache=no

- uses: stefanzweifel/git-auto-commit-action@v4
with:
Expand Down
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/.build
/.idea
/vendor
/composer.lock
/.idea
/.php-cs-fixer.cache
/.phpunit.result.cache
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ See [GitHub releases](https://github.com/mll-lab/php-utils/releases).

## Unreleased

## v1.11.0

### Added

- Integrate `mll-lab/holidays`

## v1.10.0

### Added
Expand Down
19 changes: 13 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,35 @@ help: ## Displays this list of targets with descriptions

.PHONY: coverage
coverage: vendor ## Collects coverage from running unit tests with phpunit
mkdir -p .build/phpunit
mkdir --parents .build/phpunit
vendor/bin/phpunit --dump-xdebug-filter=.build/phpunit/xdebug-filter.php
vendor/bin/phpunit --coverage-text --prepend=.build/phpunit/xdebug-filter.php

.PHONY: fix
fix: vendor
fix: rector php-cs-fixer

.PHONY: rector
rector: vendor
vendor/bin/rector process
vendor/bin/php-cs-fixer fix

.PHONY: php-cs-fixer
php-cs-fixer:
mkdir --parents .build/php-cs-fixer
vendor/bin/php-cs-fixer fix --cache-file=.build/php-cs-fixer/cache

.PHONY: infection
infection: vendor ## Runs mutation tests with infection
mkdir -p .build/infection
mkdir --parents .build/infection
vendor/bin/infection --ignore-msi-with-no-mutations --min-covered-msi=60 --min-msi=60

.PHONY: stan
stan: vendor ## Runs a static analysis with phpstan
mkdir -p .build/phpstan
mkdir --parents .build/phpstan
vendor/bin/phpstan analyse --configuration=phpstan.neon

.PHONY: test
test: vendor ## Runs auto-review, unit, and integration tests with phpunit
mkdir -p .build/phpunit
mkdir --parents .build/phpunit
vendor/bin/phpunit --cache-result-file=.build/phpunit/result.cache

vendor: composer.json
Expand Down
9 changes: 3 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@
},
"require": {
"php": "^7.4 || ^8",
"ext-calendar": "*",
"illuminate/support": "^8.73 || ^9 || ^10",
"mll-lab/microplate": "^6",
"mll-lab/str_putcsv": "^1",
"nesbot/carbon": "^2.62.1",
"thecodingmachine/safe": "^1 || ^2"
},
"require-dev": {
"ergebnis/composer-normalize": "^2",
"infection/infection": "^0.26 || ^0.27",
"jangregor/phpstan-prophecy": "^1",
"mll-lab/php-cs-fixer-config": "^5",
"nesbot/carbon": "^2.62.1",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^1",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-strict-rules": "^1",
"phpunit/phpunit": "^9 || ^10",
"rector/rector": "^0.17",
"symfony/var-dumper": "^5 || ^6",
"thecodingmachine/phpstan-safe-rule": "^1.2"
},
"autoload": {
Expand All @@ -45,10 +45,7 @@
"autoload-dev": {
"psr-4": {
"MLL\\Utils\\Tests\\": "tests/"
},
"files": [
"vendor/symfony/var-dumper/Resources/functions/dump.php"
]
}
},
"config": {
"allow-plugins": {
Expand Down
145 changes: 145 additions & 0 deletions src/BavarianHolidays.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php declare(strict_types=1);

namespace MLL\Utils;

use Carbon\Carbon;

/**
* Some definitions:
* Holiday: special occasions on a mostly fixed date where there is no work
* Weekend Day: Saturday and Sunday
* Business Day: any day that is neither a Holiday nor a Weekend Day.
*/
class BavarianHolidays
{
public const HOLIDAYS_STATIC = [
'01.01' => 'Neujahrstag',
'06.01' => 'Heilige Drei Könige',
'01.05' => 'Tag der Arbeit',
'15.08' => 'Maria Himmelfahrt',
'03.10' => 'Tag der Deutschen Einheit',
'01.11' => 'Allerheiligen',
'24.12' => 'Heilig Abend',
'25.12' => 'Erster Weihnachtstag',
'26.12' => 'Zweiter Weihnachtstag',
'31.12' => 'Sylvester',
];

public const SAMSTAG = 'Samstag';
public const SONNTAG = 'Sonntag';
public const KARFREITAG = 'Karfreitag';
public const OSTERSONNTAG = 'Ostersonntag';
public const OSTERMONTAG = 'Ostermontag';
public const CHRISTI_HIMMELFAHRT = 'Christi Himmelfahrt';
public const PFINGSTSONNTAG = 'Pfingstsonntag';
public const PFINGSTMONTAG = 'Pfingstmontag';
public const FRONLEICHNAM = 'Fronleichnam';
public const REFORMATIONSTAG_500_JAHRE_REFORMATION = 'Reformationstag (500 Jahre Reformation)';

/**
* Optionally allows users to define extra holidays for a given year.
*
* The returned array is expected to be a map from the day of the year
* (format with @see self::dayOfTheYear()) to holiday names.
*
* @example ['23.02' => 'Day of the Tentacle']
*
* @var (callable(int): array<string, string>)|null
*/
public static $loadUserDefinedHolidays;

/** Checks if given date is a business day. */
public static function isBusinessDay(Carbon $date): bool
{
return ! self::isHoliday($date)
&& ! $date->isWeekend();
}

/** Checks if given date is a holiday. */
public static function isHoliday(Carbon $date): bool
{
return is_string(self::nameHoliday($date));
}

/**
* Returns the name of the holiday if the date happens to land on one.
* Saturday and Sunday are not evaluated as holiday.
*/
public static function nameHoliday(Carbon $date): ?string
{
$holidayMap = self::buildHolidayMap($date);

return $holidayMap[self::dayOfTheYear($date)] ?? null;
}

/** Returns a new carbon instance with the given number of business days added. */
public static function addBusinessDays(Carbon $date, int $days): Carbon
{
return DateModification::addDays(
$date,
$days,
fn (Carbon $date): bool => self::isBusinessDay($date)
);
}

/** Returns a new carbon instance with the given number of business days subtracted. */
public static function subBusinessDays(Carbon $date, int $days): Carbon
{
return DateModification::subDays(
$date,
$days,
fn (Carbon $date): bool => self::isBusinessDay($date)
);
}

/**
* Returns a map from day/month to named holidays.
*
* @return array<string, string>
*/
protected static function buildHolidayMap(Carbon $date): array

Check warning on line 100 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "ProtectedVisibility": --- Original +++ New @@ @@ * * @return array<string, string> */ - protected static function buildHolidayMap(Carbon $date) : array + private static function buildHolidayMap(Carbon $date) : array { $holidays = self::HOLIDAYS_STATIC; $year = $date->year;
{
$holidays = self::HOLIDAYS_STATIC;

$year = $date->year;

// dynamic holidays
// easter_days avoids issues with timezones and is not limited to UNIX timestamps, see https://github.com/briannesbitt/Carbon/pull/1052#issuecomment-381178494
$easter = Carbon::createMidnightDate($year, 3, 21)
->addDays(easter_days($year));
$holidays[self::dateFromEaster($easter, -2)] = self::KARFREITAG;
$holidays[self::dateFromEaster($easter, 0)] = self::OSTERSONNTAG;
$holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG;

Check warning on line 112 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ $easter = Carbon::createMidnightDate($year, 3, 21)->addDays(easter_days($year)); $holidays[self::dateFromEaster($easter, -2)] = self::KARFREITAG; $holidays[self::dateFromEaster($easter, 0)] = self::OSTERSONNTAG; - $holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG; + $holidays[self::dateFromEaster($easter, 2)] = self::OSTERMONTAG; $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG;
$holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT;

Check warning on line 113 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, -2)] = self::KARFREITAG; $holidays[self::dateFromEaster($easter, 0)] = self::OSTERSONNTAG; $holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG; - $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; + $holidays[self::dateFromEaster($easter, 38)] = self::CHRISTI_HIMMELFAHRT; $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM;

Check warning on line 113 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, -2)] = self::KARFREITAG; $holidays[self::dateFromEaster($easter, 0)] = self::OSTERSONNTAG; $holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG; - $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; + $holidays[self::dateFromEaster($easter, 40)] = self::CHRISTI_HIMMELFAHRT; $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM;
$holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG;

Check warning on line 114 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, 0)] = self::OSTERSONNTAG; $holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG; $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; - $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; + $holidays[self::dateFromEaster($easter, 48)] = self::PFINGSTSONNTAG; $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM; // exceptional holidays

Check warning on line 114 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, 0)] = self::OSTERSONNTAG; $holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG; $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; - $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; + $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTSONNTAG; $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM; // exceptional holidays
$holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG;

Check warning on line 115 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG; $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; - $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; + $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTMONTAG; $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM; // exceptional holidays if ($year === 2017) {

Check warning on line 115 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, 1)] = self::OSTERMONTAG; $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; - $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; + $holidays[self::dateFromEaster($easter, 51)] = self::PFINGSTMONTAG; $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM; // exceptional holidays if ($year === 2017) {
$holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM;

Check warning on line 116 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; - $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM; + $holidays[self::dateFromEaster($easter, 59)] = self::FRONLEICHNAM; // exceptional holidays if ($year === 2017) { $holidays['31.10'] = self::REFORMATIONSTAG_500_JAHRE_REFORMATION;

Check warning on line 116 in src/BavarianHolidays.php

View workflow job for this annotation

GitHub Actions / mutation-tests

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ $holidays[self::dateFromEaster($easter, 39)] = self::CHRISTI_HIMMELFAHRT; $holidays[self::dateFromEaster($easter, 49)] = self::PFINGSTSONNTAG; $holidays[self::dateFromEaster($easter, 50)] = self::PFINGSTMONTAG; - $holidays[self::dateFromEaster($easter, 60)] = self::FRONLEICHNAM; + $holidays[self::dateFromEaster($easter, 61)] = self::FRONLEICHNAM; // exceptional holidays if ($year === 2017) { $holidays['31.10'] = self::REFORMATIONSTAG_500_JAHRE_REFORMATION;

// exceptional holidays
if ($year === 2017) {
$holidays['31.10'] = self::REFORMATIONSTAG_500_JAHRE_REFORMATION;
}

// user-defined holidays
if (isset(self::$loadUserDefinedHolidays)) {
$holidays = array_merge(
$holidays,
(self::$loadUserDefinedHolidays)($year)
);
}

return $holidays;
}

protected static function dateFromEaster(Carbon $easter, int $daysAway): string
{
$date = $easter->clone()->addDays($daysAway);

return self::dayOfTheYear($date);
}

public static function dayOfTheYear(Carbon $date): string
{
return $date->format('d.m');
}
}
40 changes: 40 additions & 0 deletions src/DateModification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types=1);

namespace MLL\Utils;

use Carbon\Carbon;

class DateModification
{
/** @param callable(Carbon): bool $shouldCount should the given date be added? */
public static function addDays(Carbon $date, int $days, callable $shouldCount): Carbon
{
// Make sure we do not mutate the original date
$copy = $date->clone();

while ($days > 0) {
$copy->addDay();
if ($shouldCount($copy)) {
--$days;
}
}

return $copy;
}

/** @param callable(Carbon): bool $shouldCount should the given date be subtracted? */
public static function subDays(Carbon $date, int $days, callable $shouldCount): Carbon
{
// Make sure we do not mutate the original date
$copy = $date->clone();

while ($days > 0) {
$copy->subDay();
if ($shouldCount($copy)) {
--$days;
}
}

return $copy;
}
}
Loading

0 comments on commit 063e30d

Please sign in to comment.