Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate mll-lab/holidays #12

Merged
merged 8 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Check warning on line 120 in src/BavarianHolidays.php

View check run for this annotation

Codecov / codecov/patch

src/BavarianHolidays.php#L120

Added line #L120 was not covered by tests
}

// 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
Loading