I stumbled upon another question that asked a similar question and started writing code that solved his problem, but then found this question. Since I already have the code, I figure it won't be of any harm to post it here, someone will probably find it useful. I believe it is better/more flexible than most answers to this question.
My class is available here: https://gist.github.com/jurchiks/62ee8c4267dde48a296a8c2ab18965df
And its usage is as follows:
use Cake\Chronos\Date;
$weekdayCalculator = new WeekdayCalculator();
$weekdayCalculator->addSpecialWorkday(new Date('2022-01-08')); // Override Saturday as a workday.
// Sunday stays a normal holiday as usual.
$weekdayCalculator->addSpecialHoliday(new Date('2022-01-10')); // Override the following Monday as a holiday.
// As a result, we've shifted the workday one day back.
var_dump(
$weekdayCalculator->isWorkday(new Date('2022-01-08')), // Returns TRUE - we manually set this date as a workday.
$weekdayCalculator->isHoliday(new Date('2022-01-09')), // Returns TRUE - normal holiday.
$weekdayCalculator->getNextWorkday(new Date('2022-01-07')), // Returns Date(2022-01-08).
$weekdayCalculator->getNextWorkday(new Date('2022-01-08')), // Returns Date(2022-01-11) - 9, 10 = holidays, 11 = normal workday.
$weekdayCalculator->getNextHoliday(new Date('2022-01-08')), // Returns Date(2022-01-09).
$weekdayCalculator->countDays(new Date('2022-01-01'), new Date('2022-02-01')), // Returns int(31) - January has 31 days, 02-01 is excluded.
$weekdayCalculator->countWorkdays(new Date('2022-01-01'), new Date('2022-02-01')), // Returns int(21).
$weekdayCalculator->countHolidays(new Date('2022-01-01'), new Date('2022-02-01')), // Returns int(10).
$weekdayCalculator->getDays(new Date('2022-01-01'), new Date('2022-02-01')), // Returns a Generator instance for all days between specified dates.
$weekdayCalculator->getWorkdays(new Date('2022-01-01'), new Date('2022-02-01')), // Returns a Generator instance for all workdays.
$weekdayCalculator->getHolidays(new Date('2022-01-01'), new Date('2022-02-01')), // Returns a Generator instance for all holidays.
iterator_to_array($weekdayCalculator->getWorkdays(new Date('2022-01-01'), new Date('2022-02-01'))), // Returns an array of 21 Date instances.
);
You will still have to write your own code to add your country's specific work/holidays, but there are examples of how to do that in other answers here, and besides, that is not quite in the scope of a class like this.
Yes, it is dependent on cakephp/chronos because it's a very nice immutable-first PHP DateTime alternative; I much prefer it to Carbon, which is mutable-first.